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;
}
原文地址:https://www.cnblogs.com/harryzhr/p/14654703.html