人,只有自己站起来,这个世界才能属于他。|

园龄:粉丝:关注:

CF Round 998 题解合集

here.

E

考虑对 F 先删边再加边。

删边时,用并查集维护出 G 的联通性,如果 F 中的边 (x,y) 在 G 中不联通,就把它删去。

加边时,用并查集维护出 F 的联通性,如果 G 中的边 (x,y) 在 F 中不连通,就在 F 中加边 (x,y)

不难发现这样贪心决策是正确的。

复杂度 O(nα(n))

#include<bits/stdc++.h>
using namespace std;
int t,m1,m2,n,cnt,x[200005],y[200005],u[200005],v[200005];
int fa[200005],vis[200005];
void init(){
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
}
int find(int x){
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
void merge(int x,int y){
	x=find(x);
	y=find(y);
	if(x==y) return;
	fa[x]=y;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0); 
	cin>>t;
	while(t--){
		cin>>n>>m1>>m2;
		for(int i=1;i<=m1;i++){
			cin>>x[i]>>y[i];
		}
		init();
		for(int i=1;i<=m2;i++){
			cin>>u[i]>>v[i];
			merge(u[i],v[i]);
		}
		for(int i=1;i<=m1;i++){
			if(find(x[i])!=find(y[i])){
				vis[i]=1;
				cnt++;
			}
		}
		init();
		for(int i=1;i<=m1;i++){
			if(vis[i]) continue;
			merge(x[i],y[i]);
		}
		for(int i=1;i<=m2;i++){
			if(find(u[i])!=find(v[i])){
				cnt++;
				merge(u[i],v[i]);
			} 
		}
		cout<<cnt<<'\n';
		cnt=0;
		for(int i=1;i<=m1;i++){
			vis[i]=0;
		}
	}
	return 0;
}

F

考虑 DP 计算 fi,j 表示在 j 个位置填非 1 数,乘积为 i 的方案数。

不难得到转移:

fdi,j+1fi,j

然后考虑计算答案为:

ansk=i=0logkj=infk,i(ji)

不难发现:

i=jn(ij)=(n+1j+1)

所以原式为:

ansk=i=0logkfk,i(n+1i+1)

因为 n 很大但是 i 很小,所以考虑按定义求组合数。

复杂度 O(klogk)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int t,n,k,ans,f[100005][21];
int binpow(int a,int b){
	if(!b) return 1;
	int res=binpow(a,b/2);
	if(b&1) return res*res%mod*a%mod;
	else return res*res%mod;
}
int C(int n,int m){
	if(n<m) return 0;
	int ans=1;
	for(int i=0;i<m;i++){
		ans=ans*(n-i)%mod;
	}
	int fac=1;
	for(int i=1;i<=m;i++){
		fac=fac*i%mod;
	}
	ans=ans*binpow(fac,mod-2)%mod;
	return ans;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		cin>>k>>n;
		f[1][0]=1;
		for(int i=1;i<k;i++){
			for(int j=0;j<__lg(k);j++){
				for(int d=2;i*d<=k;d++){
					f[i*d][j+1]=(f[i*d][j+1]+f[i][j])%mod;
				}
			}
		}
		cout<<n%mod<<' '; 
		for(int i=2;i<=k;i++){
			ans=0;
			for(int j=0;j<=__lg(k);j++){
				ans=(ans+f[i][j]*C(n+1,j+1)%mod)%mod;
			}
			cout<<ans<<' ';
		}
		cout<<'\n';
		for(int i=1;i<=k;i++){
			for(int j=0;j<=__lg(k);j++){
				f[i][j]=0;
			}
		}
		ans=0;
	}
	return 0;
}

G

不难发现,在 n3 时,我们可以进行以下两种高级操作:

  • 选择两行 i,j 交换;

  • 选择两行 i,j 翻转。

因此,不难发现,(ai,bi) 是绑定的,所以最终序列是唯一确定的,按 min(ai,bi) 排序的。

fi,1/2/3/4 表示考虑目标序列前 i 项,是否翻转 i,是否有未配对的操作 2

然后就直接转移就行了,复杂度 O(n)

转移的细节见代码。

#include<bits/stdc++.h>
using namespace std;
int t,n,f[200005][6];
struct node{
	int a,b;
}h[200005];
bool cmp(node x,node y){
	return min(x.a,x.b)<min(y.a,y.b);
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>h[i].a;
		}
		for(int i=1;i<=n;i++){
			cin>>h[i].b;
		}
		sort(h+1,h+1+n,cmp);
		f[1][1]=f[1][4]=1;
		for(int i=2;i<=n;i++){
			if(h[i-1].a<h[i].a && h[i-1].b<h[i].b){
				f[i][1]|=f[i-1][1];
				f[i][2]|=f[i-1][2];
				f[i][3]|=f[i-1][4];
				f[i][4]|=f[i-1][3];
			}
			if(h[i-1].a<h[i].b && h[i-1].b<h[i].a){
				f[i][1]|=f[i-1][3];
				f[i][2]|=f[i-1][4];
				f[i][3]|=f[i-1][2];
				f[i][4]|=f[i-1][1];
			}
		}
		if(f[n][1]|f[n][3]) cout<<"Yes"<<'\n';
		else cout<<"No"<<'\n';
		for(int i=1;i<=n;i++){
			f[i][1]=f[i][2]=f[i][3]=f[i][4]=0; 
		}
	}
	return 0;
}

本文作者:Kenma

本文链接:https://www.cnblogs.com/Kenma/p/18722996

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _Kenma  阅读(4)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起