CF Round 998 题解合集

here.

E

考虑对 F 先删边再加边。

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

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

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

复杂度 \(O(n \alpha(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 计算 \(f_{i,j}\) 表示在 \(j\) 个位置填非 \(1\) 数,乘积为 \(i\) 的方案数。

不难得到转移:

\[f_{d\cdot i,j+1} \to f_{i,j} \]

然后考虑计算答案为:

\[ans_k=\sum_{i=0}^{\lfloor \log k \rfloor}\sum_{j=i}^{n} f_{k,i} \binom{j}{i} \]

不难发现:

\[\sum_{i=j}^{n} \binom{i}{j}=\binom{n+1}{j+1} \]

所以原式为:

\[ans_k=\sum_{i=0}^{\lfloor \log k \rfloor} f_{k,i} \binom{n+1}{i+1} \]

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

复杂度 \(O(k \log k)\)

#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

不难发现,在 \(n\ge 3\) 时,我们可以进行以下两种高级操作:

  • 选择两行 \(i,j\) 交换;

  • 选择两行 \(i,j\) 翻转。

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

\(f_{i,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;
}
posted @ 2025-02-18 23:00  _Kenma  阅读(10)  评论(0)    收藏  举报