zoukankan      html  css  js  c++  java
  • 2021联合省选题解

    A卷Day1

    T1 card

    题意

    (n)张卡片,每张有上下(2)个值,在翻转不超过(m)张的条件下最小化极差

    数据范围(1le m<nle 10^6)

    solution

    考虑二分答案

    枚举最小值(x),那么需要把值小于(x)的翻转一下,值大于(x+mid)的翻转一下。

    判断翻转之后是否合法:

    • 小于(x)和大于(x+mid)的位置都是单调递增的,可以双指针。
    • 前后缀最大值/最小值判断翻转之后的值是否都在([x,x+mid])之内
    • 并且计算需要翻转多少位置,与(m)比较

    复杂度(O(nlog ext{值域}))

    #include <bits/stdc++.h>
    using namespace std;
    using namespace iobuff;
    const int N=2e6+10;
    int a[N],b[N],n,m,suf[N],pre[N],suf2[N],pre2[N];
    struct Node{int val,pos;bool type;}x[N];
    inline bool operator <(Node a1,Node a2){return a1.val<a2.val;}
    inline bool check(int mid){
    	int flag1=1,flag2=1;
    	for(int i=1;i<=n+n;++i){
    		int cur=x[i].val+mid,val=x[i].val;
    		while(a[flag1]<=cur&&flag1<=n)++flag1;
    		while(a[flag2]<val&&flag2<=n)++flag2;
    		int cnt=x[i].type,L=flag2-1,R=flag1;
    		if(pre[L]<val)break;
    		if(suf[R]>cur||suf2[R]<val||pre2[L]>cur)continue;
    		cnt+=L;
    		if(x[i].pos<=L){
    			if(x[i].type)--cnt;
    			else continue;
    		}
    		cnt+=n-R+1;
    		if(x[i].pos>=R){
    			if(x[i].type)--cnt;
    			else continue;
    		}
    		if(cnt<=m)return 1;
    	}
    	return 0;
    }
    int main(){
    	scan(n);scan(m);
    	int L=1e9,R=0,mid;pre[0]=2e9;
    	for(int i=1;i<=n;++i){
    		scan(a[i]);R=max(R,a[i]);L=min(L,a[i]);
    		x[i]=(Node){a[i],i,0};
    	}
    	for(int i=1;i<=n;++i){
    		scan(b[i]);R=max(R,b[i]);L=min(L,b[i]);
    		x[i+n]=(Node){b[i],i,1};
    		pre[i]=min(pre[i-1],b[i]);
    		pre2[i]=max(pre2[i-1],b[i]);
    	}
    	suf2[n+1]=2e9;
    	for(int i=n;i>=1;--i)suf[i]=max(suf[i+1],b[i]),suf2[i]=min(suf2[i+1],b[i]);
    	sort(x+1,x+1+n+n);
    	int ans=1e9;R=R-L+1;L=0;
    	while(L<=R){
    		mid=(L+R)>>1;
    		if(check(mid))ans=mid,R=mid-1;
    		else L=mid+1;
    	}
    	putint(ans,'
    ');
    	flush();
    	return 0;
    }
    

    T2 matrix

    题意

    有两个(n imes m)矩阵(a,b),已知(b),并且(b_{i,j}=a_{i,j}+a_{i+1,j}+a_{i,j+1}+a_{i+1,j+1})

    还原(a_{i,j}),要求(0le a_{i,j}le 10^6)

    数据范围(n,mle 300)

    solution

    显然是差分约束,只要但是手玩一下会发现变量个数不是(2)

    观察这个矩阵的性质:

    先钦定第(n)行,第(m)列均为(0),根据(b_{i,j})构造出(a_{i,j})。但是这个(a_{i,j})不一定合法。

    考虑进行一些变化使得依旧能满足(b)的限制:如果某一行/列的进行(+x,-x,+x,-xcdots)这样的变化,(b_{i,j})的限制依然满足

    (r_i,c_j)分别表示第(i)行/第(j)列变化的值,那么这个变化量矩阵如下

    [egin{pmatrix} r_1+c_1&-r_1+c_2&r_1+c_3&cdots\ r_2-c_1&-r_2-c_2&r_2-c_3&cdots\ r_3+c_1&-r_3+c_2&r_3+c_3&cdots\ vdots&vdots&vdots&ddots end{pmatrix} ]

    我们把这个矩阵的每一个数记作(x_{i,j}),即我们要满足(0le a_{i,j}+x_{i,j}le 10^6)

    [-a_{i,j}le x_{i,j}le 10^6-a_{i,j} ]

    其中(x_{i,j})是只关于(r_i,c_j)的变量

    但是有加号有减号没法做,考虑把一部分(r_i)和一部分(c_j)全都取相反数

    将偶数(i)(c_i)和奇数(j)(r_j)取相反数,得到的矩阵如下

    [egin{pmatrix} r_1-c_1&-r_1+c_2&r_1-c_3&cdots\ -r_2+c_1&r_2-c_2&-r_2+c_3&cdots\ r_3-c_1&-r_3+c_2&r_3-c_3&cdots\ vdots&vdots&vdots&ddots end{pmatrix} ]

    现在就都是一正一负了

    差分约束即可

    #include <bits/stdc++.h>
    using namespace std;
    using namespace iobuff;
    const int N=305,MX=1e6;
    int T,n,m,b[N][N];
    #define ll long long
    ll a[N][N];
    struct Edge{int to,next;ll len;}e[N*N*10];
    int head[N<<1],ecnt;
    inline void adde(int u,int v,ll w){e[++ecnt]=(Edge){v,head[u],w};head[u]=ecnt;}
    ll dis[N<<1];int cnt[N<<1],mx;bool inq[N<<1];
    inline bool SPFA(){
    	deque<int> q;
    	for(int i=1;i<=n+m;++i)q.push_back(i),inq[i]=1,cnt[i]=dis[i]=0;
    	while(!q.empty()){
    		int u=q.front();q.pop_front();inq[u]=0;
    		for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;
    			if(dis[v]>dis[u]+e[i].len){
    				dis[v]=dis[u]+e[i].len;
    				if(!inq[v]){
    					inq[v]=1;if((++cnt[v])>=mx)return 0;
    					if(e[i].len<=0)q.push_front(v);
    					else q.push_back(v);
    				}
    			}
    		}
    	}
    	return 1;
    }
    int main(){
    	scan(T);
    	while(T--){
    		scan(n);scan(m);
    		mx=min(40,n+m);
    		for(int i=1;i<n;++i)for(int j=1;j<m;++j)scan(b[i][j]);
    		memset(a[n]+1,0,m<<2);
    		for(int i=1;i<=n;++i)a[i][m]=0;
    		for(int i=n-1;i>0;--i)for(int j=m-1;j>0;--j){
    			a[i][j]=b[i][j]-a[i+1][j]-a[i][j+1]-a[i+1][j+1];
    		}
    		memset(head+1,0,(n+m)<<2);ecnt=0;
    		for(int i=1;i<=n;++i){
    			for(int j=1;j<=m;++j){
    				if(!((i+j)&1)){
    					adde(j+n,i,MX-a[i][j]);
    					adde(i,j+n,a[i][j]);
    				}else{
    					adde(i,j+n,MX-a[i][j]);
    					adde(j+n,i,a[i][j]);
    				}
    			}
    		}
    		if(!SPFA()){
    			pc('N');pc('O');pc('
    ');
    			continue;
    		}
    		pc('Y');pc('E');pc('S');pc('
    ');
    		for(int i=1,c,r;i<=n;++i,pc('
    '))
    			for(int j=1;j<=m;++j)
    				putint(a[i][j]+(((i+j)&1)?-dis[i]:dis[i])+(((i+j)&1)?dis[j+n]:-dis[j+n]),' ');
    	}
    	flush();
    	return 0;
    }
    

    T3 graph

    题意

    定义函数(f(u,G))(u)(G)中的顶点。

    (1sim n)枚举(v),如果(u,v)能互相到达,(++ans),并把点(v)和所有与(v)有关的边从(G)中删除

    原图(G)中有(m)条边,设删除前(i)条边之后的图为(G_i)

    [sumlimits_{i=0}^msumlimits_{j=1}^nf(j,G_i) ]

    数据范围(1le nle 1000,1le mle 2 imes 10^5)

    solution

    观察(f(u,G))

    首先可能对答案做出贡献的(vle u),不然自己都被删了

    我们有结论:

    如果(v)(f(u,G))造成贡献,那么一定存在两条路径(u o v)(v o u)满足路径上的所有点编号(ge v)

    证明,考虑反证,如果路径上存在(pin[1,v))

    • (p)造成贡献,那么这条路径会被断掉,不合法
    • (p)不造成贡献,那么只存在路径(p o v)或者(v o p),但是因为(u o v)(v o u)同时存在,这自相矛盾

    结论得证

    现在我们考虑对(m)张图求解

    我们不要每次删一条边重新做,而是从后往前加边

    (f_{u,v})表示(u o v)不存在(le min(u,v))的点的路径出现的最早时间(注意代码里时间早晚的定义不一样)

    那么(v)(u)第一次做出贡献的时间为(max(f_{v,u},f_{u,v}))。做一个前缀和即可

    现在考虑求出(f_{u,v}),这玩意长得就很像Floyd

    从高到低枚举中间点(k)(k)可以对(f_{u,v})当且仅当(min(u,v)le k)(根据上面的结论)

    转移即为

    [f_{u,v}=min(f_{u,v},max(f_{u,k},f_{k,u})) ]

    复杂度(O(n^3+m))

    Floyd 常数极小,加上上面的(frac{3}{4})的常数优化,可以通过

    #include <bits/stdc++.h>
    using namespace std;
    const int M=2e5+10,N=1005,inf=1e9;
    int f[N][N],n,m;
    inline void Max(int &a,int b){if(b>a)a=b;}
    int ans[M];
    int main(){
    	n=read();m=read();
    	for(int i=1,u,v;i<=m;++i){
    		u=read();v=read();f[u][v]=i;
    	}
    	for(int i=1;i<=n;++i)f[i][i]=m+1;
    	for(int k=n;k>=1;--k){
    		for(int i=1;i<=n;++i){
    			int R=(i<=k)?n:k;
    			for(int j=1;j<=R;++j)Max(f[i][j],min(f[i][k],f[k][j]));
    		}
    	}
    	for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)++ans[min(f[i][j],f[j][i])];
    	for(int i=m;i>=1;--i)ans[i]+=ans[i+1];
    	for(int i=1;i<=m+1;++i)printf("%d ",ans[i]);
    	return 0;
    }
    

    A卷Day2

    T1 gem

    题意

    一棵树,每个节点有(m)种宝石中的一种(一种宝石可能出现在多个节点),每次需要依次收集(P_1,P_2,P_3,cdots,P_c)的宝石。(q)次询问(s o t)的路径上能收集多少宝石。

    数据范围:(1le n,qle 2 imes 10^5,1le cle mle 5 imes 10^4)

    solution

    首先可以按照(w_i)(P)中第几个出现将收集宝石的顺序转为(1,2,3,cdots,c)

    然后从下往上走的时候,可以简单地从离(u)最近的(1)开始倍增往上跳祖先,并且祖先不能超过(LCA)

    但是往下走的时候就不能倍增了。下面给出两种处理方法

    我的超级难写的憨憨想法

    树链剖分,维护一条链上两个方向的倍增数组,在链内倍增

    巨难写,代码就不放了

    更好写的解法:二分答案

    首先处理完从下往上的答案,即为(ansl_i),将右端点与其对应的(ansl)离线下来

    对于一个询问,二分答案(x),我们现在已经处理出它的父亲中颜色为(x)的,离它最近的节点(这也就是需要离线的原因)

    我们只需判断倍增到(LCA)时能否达到(ansl_i+1)即可

    复杂度(O(nlog^2 n))

    T2 ranklist

    题意

    每队初始得分(a_i),新一轮得分(b_i)(b_i)初值为(0),按照不降的顺序更新,要求(sum b_ile m)

    (i)队排名在(j)前面当且仅当:在当前时间,(a_i+b_i>a_j+b_j)或者(a_i+b_i=a_j+b_j)(i<j)

    要求如果第(i)个更新的是(b_x),那么更新之后(x)队为榜首

    求可能的最终排名个数

    数据范围:(1le nle 13,1le mle 500,0le a_ile 10^4)

    solution

    这么小的(n)显然考虑状压

    (b_i)不降,考虑将其差分。如果(Delta_i>0),那么相对大小改变的只能是第(i)和第(i-1)个更新的两队。并且之后所有的(b)都会增加,也就是总花费要增加((n-i+1) imes Delta_i)

    定义状态:(f_{S,u,k})表示在(S)集合的(b)已经更新,最后一个更新的是(u),花费恰好为(k)的排列数

    设下一个加入的点为(v)(cost_{v,u})表示(v)在排行榜上超过(u)的最小花费((cost_{v,u}=max{0,a_u-a_v+(v>u)})

    转移为

    [f[Scup{v}][v][k]=sum_{uin S}f[S][u][k-(cost_{v,u}) imes(n-popcount(S))] ]

    #define ll long long
    const int N=14,M=503;
    int a[N],n,m,popcount[(1<<N)+10],mx,id,c;
    ll f[(1<<14)+3][N][M];
    int cost[N][N];//cost u,v : u become first after v become first
    int main(){
    	n=read();m=read();mx=-1;
    	for(int i=1;i<=n;++i){
    		a[i]=read();
    		if(a[i]>mx)mx=a[i],id=i;
    	}
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=n;++j){
    			if(i==j)continue;
    			if(a[i]>a[j])cost[i][j]=0;
    			else cost[i][j]=a[j]-a[i]+(i>j);
    		}
    	}
    	for(int i=1;i<=n;++i){
    		c=(mx-a[i]+(i>id))*n;
    		if(c<=m)f[1<<(i-1)][i][c]=1;
    	}
    	popcount[1]=popcount[2]=1;
    	for(int s=3;s<(1<<n);++s){
    		popcount[s]=popcount[s&(s-1)]+1;
    		if((s&(-s))==s)continue;
    		for(int i=1;i<=n;++i){
    			if(!(s&(1<<(i-1))))continue;
    			int t=s^(1<<(i-1));
    			for(int j=1;j<=n;++j){
    				if(!(t&(1<<(j-1))))continue;
    				int base=cost[i][j]*(n-popcount[t]);
    				for(int k=base;k<=m;++k)
    					f[s][i][k]+=f[t][j][k-base];
    			}
    		}
    	}
    	ll ans=0;
    	for(int i=1;i<=n;++i)for(int j=0;j<=m;++j)ans+=f[(1<<n)-1][i][j];
    	printf("%lld",ans);
    	return 0;
    }
    

    T3 dominator

    题意

    给定有向图(G)(1)可以到达任意节点

    定义(u)支配(v):从(1)出发到(v)的路径中一定经过(u)。定义(v)的受支配集:(u)支配(v)(u)集合

    (q)次询问:加入一条边(x o y),求支配集变化的点的数量

    数据范围:(nle 3000,1le mle 2 imes n,1le qle 2 imes 10^4)

    solution

    首先支配集只会变小或者不变,如果我们建出支配树,那么如果(u)的支配集改变,也就是可以不经过(u)在支配树上的所有祖先中的一个

    考虑简单地(O(nm))构建支配树

    首先(O(nm))跑出所有点的支配集,因为树上父亲的子树大小大于儿子的子树大小,按照支配集大小排序,最大的就是根。把受它支配的点的父亲都设为自己。如果一个节点只有一个父亲,那么这个父亲就是确定了的,然后将自己的受支配集的点的父亲更新为自己。

    说的不清楚的话就去看代码吧

    现在给出结论

    新加入 (x o y) 的边,记 (d=LCA(x,y)),那么如果存在一条 (y o u) 的路径满足:不经过(d)及其所有祖先的直接儿子,那么(u)的受支配集变小

    首先我们可能出现的新路径只可能是(1 o d o x o y o cdots( o v) o u),其中(v)是支配树上(u)的祖先之一。那么现在(LCA(d,u))的点的父亲都被经过过,可能不被经过的只可能是(LCA(d,u) o u)的路径上的一点

    只要走过(x o y)之后走到了(u)的支配树上祖先上一点,那么支配树上到(u)的路径上的所有点都是必经的,否则就不符合支配树的定义了

    如果走到了直接儿子,那么路径中间没有空出节点,所有受支配点依然会被经过

    (以上只是感性理解,严谨证明参见洛谷题解)

    直接(O(n))标记之后(O(m))BFS即可

    bool vis[N],del[N];
    int n,m,Q,sz[N],id[N];
    inline bool cmp(int a,int b){return sz[a]>sz[b];}
    inline void bfs(){
    	memset(vis+1,0,n);
    	vis[1]=1;q.clear();q.push(1);
    	while(!q.empty()){
    		int u=q.front();q.pop();
    		for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;if(vis[v]||del[v])continue;
    			vis[v]=1;q.push(v);
    		}
    	}
    }
    #define pb push_back
    vector<int> son[N];
    vector<int> tr[N];
    int Log2[N],dep[N],fa[N][18];
    inline void init(){for(int i=2;i<=n;++i)Log2[i]=Log2[i>>1]+1;}
    void dfs(int u,int fat){
    	dep[u]=dep[fat]+1;
    	for(int i=1;i<=Log2[dep[u]];++i)fa[u][i]=fa[fa[u][i-1]][i-1];
    	for(int i=0,v;i<tr[u].size();++i){
    		v=tr[u][i];if(v==fat)continue;
    		dfs(v,u);
    	}
    }
    int ans;
    void bfs2(int u){
    	memset(vis+1,0,n);
    	if(del[u])return;
    	vis[u]=1;q.clear();q.push(u);
    	while(!q.empty()){
    		int u=q.front();q.pop();++ans;
    		for(int i=head[u],v;i;i=e[i].next){
    			v=e[i].to;if(vis[v]||del[v])continue;
    			vis[v]=1;q.push(v);
    		}
    	}
    }
    inline int LCA(int x,int y){
    	if(dep[x]<dep[y])swap(x,y);
    	while(dep[x]>dep[y])x=fa[x][Log2[dep[x]-dep[y]]];
    	if(x==y)return x;
    	for(int i=Log2[dep[x]];i>=0;--i)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    	return fa[x][0];
    }
    int main(){
    	n=read();m=read();Q=read();init();
    	for(int i=1,u,v;i<=m;++i){
    		u=read();v=read();
    		adde(u,v);
    	}
    	for(int i=2;i<=n;++i)son[1].pb(i),++sz[1];
    	id[1]=1;
    	for(int i=2;i<=n;++i){
    		id[i]=i;del[i]=1;bfs();del[i]=0;
    		for(int j=1;j<=n;++j){
    			if(i==j||vis[j])continue;
    			son[i].pb(j),++sz[i];
    		}
    	}
    	sort(id+1,id+1+n,cmp);
    	for(int i=1,u;i<=n;++i){
    		u=id[i];
    		for(int j=0,v;j<son[u].size();++j)
    			fa[son[u][j]][0]=u;
    	}
    	for(int i=1;i<=n;++i)if(fa[i][0])tr[fa[i][0]].pb(i);
    	dfs(1,0);
    	int s,t,d,lca;
    	while(Q--){
    		ans=0;
    		s=read();t=read();lca=LCA(s,t);
    		if(dep[t]-dep[lca]==1||lca==t){puts("0");continue;}
    		memset(del+1,0,n);
    		for(d=lca;d;d=fa[d][0]){
    			del[d]=1;
    			for(int i=0;i<tr[d].size();++i)
    				del[tr[d][i]]=1;
    		}
    		bfs2(t);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    B卷

    pair

    题意

    给定 (n) 个正整数 (a_i) 请你求出有多少个数对 ((i, j)) 满足 (1 le i le n)(1 le j le n)(i e j)(a_i)(a_j) 的倍数。

    数据范围(2le nle 2 imes 10^5,1le a_ile 5 imes 10^5)

    solution

    记录一下每个数出现的次数,暴力枚举倍数,时间复杂度(O(nlog a_i))

    const int N=5e5+5;
    int cnt[N],n,a[N],mx;
    #define ll long long
    ll ans;
    int main(){
    	n=read();for(int i=1;i<=n;++i){
    		a[i]=read();mx=max(a[i],mx);
    		++cnt[a[i]];
    	}
    	for(int i=1;i<=n;++i){
    		for(int j=a[i];j<=mx;j+=a[i])ans+=cnt[j];
    		--ans;
    	}
    	printf("%lld",ans);
    	return 0;
    } 
    

    mod

    题意

    给定 (n) 个正整数 (a_i),请你在其中选出三个数 (i, j, k(i e j e k)),使得 ((a_i + a_j) mod a_k) 的值最大。

    数据范围(3le nle 2 imes 10^5,1le a_ile 10^8)

    solution

    首先考虑(O(n^2log n))暴力

    从大到小枚举(a_k),然后求出所有数(mod a_i)的结果(b_i),排序,有两种情况对答案造成贡献

    • 两个(b)加起来(ge a_k)之后(-a_k),显然选两个最大的(b)最优
    • 两个(b)加起来(<a_k),再枚举一个(b),找到(<a_k-b_i)的最大的(b_j),注意不能为(b_i),可以双指针(反正排序都有(O(nlog n))了再来个(upper\_bound)也没问题)

    考虑优化

    • 不枚举相同的(a_k)
    • 如果(a_kle)当前最大答案直接结束

    这两个优化很显然,下面证明复杂度是正确的

    如果我们只考虑小于(a_k)的最大的两个(a)加起来(ge a_k)之后(-a_k)的贡献,得到的应该是复杂度的上界

    假设我们一直枚举到了(a_x),即(a_{x-1}le ans<a_x)

    [forall iin[x,n],ansge (a_{i-1}+a_{i-2})-a_i ]

    两边减两个(ans)

    [a_i-ansge (a_{i-1}-ans+a_{i-2}-ans) ]

    (d_i=a_i−ans),那么 (d_i>0),又因为没有重复的(a_i)(d)的增长至少是斐波那契数列的增长速度,最多增长(O(log max(a_i)))次,所以复杂度是(O(nlog nlog max(a_i)))

    const int N=2e5+5;
    int n,a[N],b[N],ans;
    int main(){
    	n=read();for(int i=1;i<=n;++i)a[i]=read();
    	sort(a+1,a+1+n);
    	for(int i=n;i>=1&&a[i]>ans;--i){
    		if(a[i]==a[i+1])continue;
    		for(int j=1;j<=n;++j)b[j]=a[j]%a[i];
    		sort(b+1,b+1+n);
    		ans=max(ans,(b[n]+b[n-1])%a[i]);
    		for(int j=2,pos;j<=n;++j){
    			pos=upper_bound(b+1,b+n+1,a[i]-b[j]-1)-b-1;
    			if(b[j]==b[pos]&&b[pos]!=b[pos+1])--pos;
    			else if(b[pos]==0&&b[pos+1]!=0)++pos;
    			ans=max(ans,(b[j]+b[pos])%a[i]);
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }
    
  • 相关阅读:
    diary and html 文本颜色编辑,行距和其它编辑总汇
    bash coding to changeNames
    virtualbox ubuntu 网络连接 以及 连接 secureCRT
    linux 学习6 软件包安装
    linux 学习8 权限管理
    vim 使用2 转载 为了打开方便
    ubuntu
    linux 学习15 16 启动管理,备份和恢复
    linux 学习 14 日志管理
    linux 学习 13 系统管理
  • 原文地址:https://www.cnblogs.com/harryzhr/p/14654703.html
Copyright © 2011-2022 走看看