义乌集训7.21 contest 14题解

2021.7.19 Contest 题解

T1:

Description:

​ 小A来到了一个商店,商店内有 (n) 件物品,每件物品有一个售价 (A_i) ,小A是一个奸商,因此他能够把每件物品以 (B_i) 的价格卖出。

​ 而由于小A背包空间有限,他只能买走恰好一件商品。而小A每次购买之后,小B将有可能将小A的物品摧毁,此时小A必须继续购买,并且商店不会返还购买这件物品的钱。小B至多可以摧毁 (m) 件物品。

​ 小A的目标是最大化自己的收益。

​ 小B的目标是最小化小A获得的收益,即卖出的商品所得减去购买所有物品的总花费。

​ 注意到任何时刻小B都可以选择不将小A手中的物品摧毁,这样小A就必须立刻带着这件物品离开商店。

Input:

​ 第一行一个数 (T) ,代表数据组数

​ 每组第一行两个数 (n,m) ,分别表示物品数目和小B可摧毁的物品数量。

​ 之后 (m) 行,每行两个数 (A_i,B_i)

Output:

​ 对于每组数据,输出一个数表示小A最终能获得的收益,由于小A可以直接走出商店(什么都不买),这个收益显然是非负的。

Sample1 Input:

2
3 1
1 3
10 30
10 30
3 0
1 3
10 30
100 130

Sample1 Output:

10
30

Hint:

​ 对于 (100\%) 的数据,(0 leq m <nle10^5,1 leq A_i,B_i leq 10^9,mleq 20,sum{n} leq 3 imes 10^5)

题目分析:

​ 考虑二分一个答案 (mid),也就是说一开始满足 (B_i-A_i geq mid) 的物品小A会去买,小B也会去销毁;到了第二次时,需要满足 (B_j-A_j geq mid+A_i) 小A才会去买,小B也会去销毁……一直到小B无法销毁同时仍有满足要求的 (B_k-A_k)

​ 我们考虑贪心,若 (B_i-A_i geq mid)(B_j-A_j geq mid),肯定会先购买 (B) 较小的,然后再购买 (B) 较大的。证明显然,如果先选 (j),则再选 (i) 需要满足 (B_i-A_i geq mid+A_j) ;而先选 (i),再选 (j) 需要满足 (B_j-A_j geq mid+A_i) ,移项可得,一个是 (B_i geq mid+A_j+A_i),另一个是 (B_j geq mid+A_i+A_j) ,那么显然先拿 (B) 较小的肯定更优。

​ 在确定了选择顺序之后我们就可以DP了,设 (f_{i,j}) 表示枚举到第 (i) 个物品,小B销毁了 (j) 次,此时所需的 (B_i-A_i) 的最小值,然后我们就能在 (O(nmlogV)) 的复杂度内解决这道题。

​ 当然,不用二分直接DP的做法也行,留给读者思考。

代码如下(马蜂很丑,不喜勿喷)——

//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,fma")
//#pragma GCC optimize("unroll-loops")
#include<bits/stdc++.h>
#define N 100005
#define LL long long
#define inf 2147483647
using namespace std;
int T,n,m,Max,rk[N],a[N],b[N],f[25]; inline bool cmp(int x,int y){if(b[x]^b[y]) return b[x]<b[y];return a[x]<a[y];} inline bool check(int x){
	f[0]=x;for(register int i=1;i<=m;i++) f[i]=inf;for(register int i=1;i<=n;i++) 
	for(register int j=m;j;j--) if(b[rk[i]]-a[rk[i]]>=f[j-1]) f[j]=min(f[j],a[rk[i]]+f[j-1]);return f[m]^inf;
//	int now=x,K=m;for(register int i=1;i<=n;i++) if(b[rk[i]]-a[rk[i]]>=now) if(!K) return 1;else K--,now+=a[rk[i]];return 0;
} 
//inline bool cmp2(int x,int y){if(a[x]^a[y]) return a[x]<a[y];return b[x]<b[y];}
//inline bool cmp3(int x,int y){return a[x]+b[x]<a[y]+b[y];}
struct FastIO{
	static const int S=1048576;
	char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
	inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('
');}
	FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='
');return *this;}
	template<typename T>FastIO& operator >> (T&ret){
		ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
		while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
	}
	FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='
'){*(s+Len)=ch;Len++;ch=nc();}}
	template<typename T>FastIO& operator << (T x){
		if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
		while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
	}
	FastIO& operator << (char ch){pc(ch);return *this;}
	FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
//	freopen("a.in","r",stdin);freopen("a.ans","w",stdout);
	fin>>T;while(T--){
		fin>>n>>m,m++,Max=0;for(register int i=1;i<=n;i++) fin>>a[i]>>b[i],Max=max(Max,b[i]-a[i]),rk[i]=i;
		sort(rk+1,rk+n+1,cmp);int l=0,r=Max,ans=0;while(l<=r){int mid=l+r>>1;if(check(mid)) ans=max(ans,mid),l=mid+1;else r=mid-1;}fout<<ans<<'
';
//		sort(rk+1,rk+n+1,cmp2);l=ans+1,r=Max;while(l<=r){int mid=l+r>>1;if(check(mid)) ans=max(ans,mid),l=mid+1;else r=mid-1;}
//		sort(rk+1,rk+n+1,cmp3);l=ans+1,r=Max;while(l<=r){int mid=l+r>>1;if(check(mid)) ans=max(ans,mid),l=mid+1;else r=mid-1;}fout<<ans<<'
';
	}
	return 0;
}
//21224751

T2:

Description:

​ 小A有一张无向图 (G(x)),图中每条边的权值可以表达为 (ax+b),现在小A想知道对于在 ([l,r]) 之间的每一个 (x),使得这张图的最小生成树最大的那一个 (x),所对应的最小生成树的权值是多少。

Input:

​ 第一行两个数 (n,m),分别表示点数和边数。

​ 之后 (m) 行,每行四个数 (x,y,a,b),表示一条边的端点和边权 ((ax+b))

​ 最后一行两个整数 (l,r)

Output:

​ 一个实数(保留三位小数),代表最小生成树的最大值。

Sample1 Input:

3 3
1 2 1 10
2 3 10 10
3 1 100 10
-23 23

Sample1 Output:

273.000

Hint:

对于 (100\%) 的数据,(n<mle10^5)(-10^9le lle rle10^9)(|a_i|,|b_i|le 10^5),保证图是联通的。

对于 (20\%) 的数据,(r-lle10)

对于另外 (10\%) 的数据,(a_i=0)

对于另外 (30\%) 的数据,(n,mle10^3)(|a_i|le1)

题目分析:

​ 对于图中的每一棵生成树,它的权值是关于 (x) 的一次函数,那么原问题实际上是求对于每个 (x) 在若干个函数中取最小值从而得到一个新的上凸函数,我们要求这个函数在 ([l,r]) 中的最大值,这个问题显然可以三分,然后这道题就结束了。

​ 具体实现的时候需要注意精度、边界以及常数等一系列问题。

代码如下(马蜂很丑,不喜勿喷)——

#include<bits/stdc++.h>
#define N 100005
#define DB double
#define LL long long
#define eps 1e-7
using namespace std;
int n,m,X[N],Y[N],A[N],B[N],f[N];double ans,l,r;
struct node{int x,y;DB v;}p[N];inline bool cmp(const node x,const node y){return x.v<y.v;} inline int gf(int x){return f[x]==x?x:f[x]=gf(f[x]);} inline DB get(DB xx){
	for(register int i=1;i<=n;i++) f[i]=i;for(register int i=1;i<=m;i++) p[i].x=X[i],p[i].y=Y[i],p[i].v=1.0*A[i]*xx+1.0*B[i];sort(p+1,p+m+1,cmp);
	DB res=0;for(register int i=1;i<=m;i++){int x=p[i].x,y=p[i].y,fx=gf(x),fy=gf(y);if(fx==fy) continue;res+=p[i].v,f[fx]=fy;}return res;
}
struct FastIO{
	static const int S=1048576;
	char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
	inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('
');}
	FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='
');return *this;}
	template<typename T>FastIO& operator >> (T&ret){
		ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
		while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
	}
	FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='
'){*(s+Len)=ch;Len++;ch=nc();}}
	template<typename T>FastIO& operator << (T x){
		if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
		while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
	}
	FastIO& operator << (char ch){pc(ch);return *this;}
	FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
	fin>>n>>m;for(register int i=1;i<=m;i++) fin>>X[i]>>Y[i]>>A[i]>>B[i];fin>>l>>r;ans=-214748364777777777;while(r-l>eps)
	{DB mid1=(2.0*l+r)/3.0,mid2=(l+2.0*r)/3.0;if(get(mid1)<=get(mid2)) l=mid1;else r=mid2;}ans=max(ans,max(get(l),get(r)));printf("%0.3lf
",ans);return 0;
}

T3:

Description:

​ 在计算机编译原理中,我们可以了解到上下文无关文法(CFG)这一概 念,我们可以用几个集合来代表一个文法,如语法 (G = (V,S,P,T))(V) 代表文法词汇表,其中 (S) 代表开始符号的集合,(P) 代表生成式的集合,(T) 代表终止符号的集合。

​ CFG 的工作流程可以这样描述:对于一个由词汇表 (T) 中的词汇构成的句子,可以按照生成式进行展开。如当 (V = {a,b,c,0}),开始符集 (S = {a}),结束符集 (T = {0}),生成式集有如下

(P = {a o bc,b o cc,c o 0,c o b})

​ 当我们输入字符串 (abb)b 时,文法分析检测到开始符,对应生成式可以推导出

(abb o bcbb o ccccccc o 0000000)

​ 此时,通过生成式展开的式子只具有终止符,我们就认定文法分析结束,当然,我们的推导也可以为

(abb o bcbb o ccccccc o bbbbbbb o ...)

​ 这样无限推导下去,可以得到任意长度的式子,但是当我们输入 (0a0) 时,发现句首句尾的终止符不会发生变化,不能由终止符推导出其他式子,我们称其为上下文无关文法,也就是说我们定义的生成式 (s_1 o s_2) 中,(s_1 otin T)

​ 我们给出此题的语法 (G = (V,S,P,T)),其中 (V = {A-Z,0,1}),即 (26) 个大写英文字母与 (0, 1) 两个数字;(T={0,1,}),即终止符为 (0, 1) 和空(长度为 (0) 的字符串)三种情况。我们认定,当推导出的式子中只剩 (0, 1) 或什么也没有时,文法分析结束。其开始符 (S={S}) ,即开始符只有 (S) 这个字母;(P) 包含的 (n) 个生成式则由测试数据给出。

​ 现在,Archies只输入了开始符 (large S),他想知道在文法分析结束后,由生成式推导出的式子(字符串)的前 (k) 个字符有多少种不同的可能,特别的,当式子的长度不超过 (k) 时,也视为一种合法的情况,请你解决Archies的疑问,并输出这些长度不超过 (k) 的前缀。

Input:

​ 第一行两个整数 (n,k),其意义见题目描述。

​ 接下来 (n) 行,每行一个生成式,其形式为 (s_1 o s_2) ,保证 (s_1) 不为 (0,1) 或空,保证 (s_2) 的长度不会超过 (10)

Output:

​ 输出第一行一个整数 (c),代表所有合法情况的数量。

​ 之后 (c) 行,每行一个长度不超过 (k) 的字符串,你可以按任意顺序输出这些字符串。你可以输出字符串 eps 代表一个空串。

Sample1 Input:

2 3
S->0S1S
S->

Sample1 Output:

5
eps
01
000
001
010

Hint:

data range:

对于 (100\%) 的数据,(n le 200,k le 7)

题目分析:

​ 考虑每个字符能变成什么,直接模拟这一个过程。。。

标程代码如下(马蜂很丑,不喜勿喷)——

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <iostream>
#include <vector>
#include <cassert>
#include <cmath>
#include <stack>
#include <queue>
#include <set>
#include <unordered_set>
#include <map>
typedef long long LL;
using namespace std;
const int N = 26;

inline void judge()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
}

int n , K;
char str[N];
vector<string> trans[N];
vector<int> prefix[N];
bool vis[26][1 << 8];
 
bool isrow(char *s) {
    while (*s) {
        if (!isdigit(*s ++))
            return 0;        
    }
    return 1;
}
 
string itos(int x) {
    string s = "";
    while (x > 1) {
        s += (x & 1) + '0';
        x >>= 1;
    }
    reverse(s.begin() , s.end());
    return s;
}
int Stoi(string s) {
    int x = 1;
    for (int i = 0 ; i < s.size() && i < K ; ++ i)
        x = x << 1 | s[i] - '0';
    return x;
}
 
int cat(int A , int B) {
    int begin = -1;	
    for (int i = K ; i >= 0 ; -- i)
        if (B >> i & 1) {
            begin = i;
            break;
        }
    while (begin > 0) {
        int tmp = A << 1 | (B >> (-- begin) & 1);
        if (tmp >= 1 << K + 1)
            break;
        A = tmp;
    }
    return A;
    string C = itos(A) + itos(B);
    if (C.size() > K)
        C = C.substr(0 , K);
    return Stoi(C);
}
 
int main() {
	judge();
    scanf("%d%d
" , &n , &K);
    //puts("233");
    for (int i = 0 ; i < n ; ++ i) {
        scanf("%s" , str);
        int c = *str - 'A';
        trans[c].push_back(str + 3);
        if (isrow(str + 3)) {
            int num = Stoi(str + 3);
            if (!vis[c][num]) {
                vis[c][num] = 1;
                prefix[c].push_back(num);
            }
        }
    }
    int mask = 1 << K + 1;
    while (1) {
        bool changed = 0;
        for (int i = 0 ; i < 26 ; ++ i) {
            for (auto &t : trans[i]) {
                static bool Hash[2][1 << 8];
                int cur = 0 , nxt = 1;
                memset(Hash[cur] , 0 , sizeof(Hash[cur]));
                Hash[cur][1] = 1;
                for (auto &ch : t) {
                    memset(Hash[nxt] , 0 , sizeof(Hash[nxt]));
                    for (int j = 1 ; j < mask ; ++ j) {
                        if (!Hash[cur][j])
                            continue;
                        if (ch == '0') {
                            Hash[nxt][cat(j , 2)] = 1;
                        } else if (ch == '1') {
                            Hash[nxt][cat(j , 3)] = 1;
                        } else {
                            for (auto &s : prefix[ch - 'A'])
                                Hash[nxt][cat(j , s)] = 1;
                        }
                    }
                    swap(cur , nxt);
                }                
                for (int s = 1 ; s < mask ; ++ s) {
                    //cout << s << endl;
                    if (Hash[cur][s] && !vis[i][s]) {
                        changed = 1;
                        vis[i][s] = 1;
                        prefix[i].push_back(s);
                    }
                }
            }
        }
        if (!changed)
            break;
    }
    cout << prefix['S' - 'A'].size() << endl;
    for (auto &s : prefix['S' - 'A']) {
        if (s == 1)
            cout << "eps" << endl;
        else
            cout << itos(s) << endl;
    }    
    return 0;
}

T4:

Description:

​ Solo生成了一个一条长度为 (n) 的链,链上的点标号 (1)(n),其中 (i) 号点和 (i + 1) 号点之间的边的权值为 (A_i)

​ 现在Solo决定从中删除 (k) 条两两之间没有公共点的子链(长度可以为 (0),即一个单独的节点),使得这 (k) 条链上涵盖的边的边权和最大。

Input:

​ 第一行两个整数 (n,k),其意义见题目描述。

​ 第二行 (n - 1) 个整数,其中第 (i) 个整数代表第 (i) 条边的 (A_i)

Output:

​ 第一行一个整数代表最大值;

​ 接下来 (k) 行每行两个数,表示最大值对应的方案中选出的条链的起点和终点。

Sample1 Input:

9 4
-10 2 1 3 6 -2 17 1

Sample1 Output:

29
1 1
2 6
7 8
9 9

Hint:

对于 (100\%) 的数据,(|Ai| lt 10^5,k le n le 100000)

题目分析:

​ 正解应该是WQS二分,然而博主没写出来,暂时先咕咕了QAQ(也许会永远咕下去

代码如下(马蜂很丑,不喜勿喷)——

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N=100005;
const ll inf=1e12;
ll n,i,j,K,p,l,t,a[N],s[N];
ll f[N][2],L[N][2],R[N][2];
ll tot,ansl[N],ansr[N],ans;
inline void read(ll &x)
{
	x=0; ll ff=1; char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') ff=-1,ch=getchar();
	while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	x*=ff;
}
void update(ll x,ll v)
{
	f[x][0]=f[x-1][0];
	f[x][0]=max(f[x][0],f[x-1][0]-v);
	f[x][0]=max(f[x][0],f[x-1][1]+a[x-1]);
	f[x][1]=f[x-1][0]-v;
	f[x][1]=max(f[x][1],f[x-1][1]+a[x-1]);
	L[x][0]=inf; R[x][0]=-1;
	L[x][1]=inf; R[x][1]=-1;
	
	if (f[x][0]==f[x-1][0]) L[x][0]=min(L[x][0],L[x-1][0]),R[x][0]=max(R[x][0],R[x-1][0]);
	if (f[x][0]==f[x-1][1]+a[x-1]) L[x][0]=min(L[x][0],L[x-1][1]),R[x][0]=max(R[x][0],R[x-1][1]);
	if (f[x][0]==f[x-1][0]-v) L[x][0]=min(L[x][0],L[x-1][0]+1),R[x][0]=max(R[x][0],R[x-1][0]+1);
	
	if (f[x][1]==f[x-1][0]-v) L[x][1]=min(L[x][1],L[x-1][0]+1),R[x][1]=max(R[x][1],R[x-1][0]+1);
	if (f[x][1]==f[x-1][1]+a[x-1]) L[x][1]=min(L[x][1],L[x-1][1]),R[x][1]=max(R[x][1],R[x-1][1]);
}
void Dp(ll x)
{
	ll i;
	f[0][0]=0; L[0][0]=0; R[0][0]=0;
	f[0][1]=-inf; L[0][1]=-1; R[0][1]=-1;
	for (i=1;i<=n;i++) update(i,x);
}
ll find0(ll x,ll NK,ll v)
{
	//printf("%lld %lld %lld %lld
",x,f[x][0],f[x-1][0]-v,v);
	if (f[x][0]==f[x-1][0]&&NK>=L[x-1][0]&&NK<=R[x-1][0]) return 1;
	else if (f[x][0]==f[x-1][0]-v&&NK>=L[x-1][0]+1&&NK<=R[x-1][0]+1) return 2;
	else if (f[x][0]==f[x-1][1]+a[x-1]&&NK>=L[x-1][1]&&NK<=R[x-1][1]) return 3;
	return -1;
}
ll find1(ll x,ll NK,ll v)
{
	if (f[x][1]==f[x-1][0]-v&&NK>=L[x-1][0]+1&&NK<=R[x-1][0]+1) return 1;
	else if (f[x][1]==f[x-1][1]+a[x-1]&&NK>=L[x-1][0]&&NK<=R[x-1][0]) return 2;
	return -1;
}
void get(ll K,ll v)
{
	ll i;
	ll NK=K,op=0;
	for (i=n;i>=1;i--)
	{
		ll A=-1;
		if (op==0) 
		{
			A=find0(i,NK,v);
			//printf("%lld %lld %lld
",i,op,A);
			if (A==1) op=0;
			else if (A==2) ansl[++tot]=i,ansr[tot]=i,NK--,op=0;
			else op=1,ansr[++tot]=i;
		}
		else 
		{
			A=find1(i,NK,v);
			//printf("%lld %lld %lld
",i,op,A);
			if (A==1) ansl[tot]=i,NK--,op=0;
			else op=1;
		}
	}
}
int main()
{
	freopen("d.in","r",stdin);
	freopen("d.out","w",stdout);
	read(n); read(K);
	//printf("%lld %lld
",n,K);
	for (i=1;i<=n-1;i++) read(a[i]),s[i+1]=s[i]+a[i];
	//for (i=1;i<=n-1;i++) printf("%lld ",a[i]); printf("
");
	ll l=-1e12,r=1e12;
	while (l<=r)
	{
		ll mi=(l+r)>>1;
		Dp(mi); 
		//printf("%lld
",mi);
		if (K>=L[n][0]&&K<=R[n][0])
		{
			//printf("%lld %lld %lld 
",K,L[n][0],R[n][0]);
			ans=f[n][0]+K*mi;
			get(K,mi);
			break;
		}
		else if (K>R[n][0]) r=mi-1;
		else l=mi+1;
	}
	printf("%lld
",ans);
	for (i=1;i<=tot;i++) printf("%lld %lld
",ansl[i],ansr[i]);
	//printf("%lld %lld
",s[89253]-s[2430],s[100000]-s[1]);
} 
原文地址:https://www.cnblogs.com/jiangxuancheng/p/15048603.html