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

园龄:粉丝:关注:

CF Round 999 题解合集

here.

感觉出的都很不错,做起来很舒服。

C

考虑直接 DP。

fi,0/1 表示考虑前 i 个人,第 i 个人是否说谎的方案数。

枚举第 i 个人是否说谎,得到转移:

fi,0=fi1,1

fi,1=fi1,0[ai=ai2+1]+fi1,1[ai1=ai]

注意边界条件。

复杂度 O(n)

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int t,n,a[200005],f[200005][2];
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>>a[i];
		}
		if(a[1]) f[1][0]=1;
		else f[1][0]=f[1][1]=1;
		for(int i=2;i<=n;i++){
			if(a[i]-1==a[i-2]) f[i][1]=(f[i][1]+f[i-1][0])%mod;
			if(a[i]==a[i-1]) f[i][1]=(f[i][1]+f[i-1][1])%mod;
			f[i][0]=f[i-1][1]; 
		}
		cout<<(f[n][0]+f[n][1])%mod<<'\n'; 
		for(int i=1;i<=n;i++){
			f[i][0]=f[i][1]=0;
		}
	}
	return 0;
}

D

考虑从大到小考虑目标序列中的每一个数,贪心地进行操作。

如果当前数 ai 在初始序列中出现过,那么直接删去就行。否则,把 ai 拆成 x,y,继续向下考虑。

如果目标序列中的数比初始序列多,或者当前的 ai 不能再拆了,就无解。否则一定有解。

实现上考虑使用 mutiset,复杂度 O(nlogn)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m,a[200005],b[200005],ansa,ansb,flag;
multiset<int> sa,sb; 
void init(){
	ansa=ansb=flag=0;
	sa.clear();
	sb.clear();
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		init();
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			ansa+=a[i]; 
			sa.insert(a[i]);
		} 
		for(int i=1;i<=m;i++){
			cin>>b[i];
			ansb+=b[i]; 
			sb.insert(b[i]);
		} 
		if(ansa!=ansb){
			cout<<"No"<<'\n';
			continue;
		}
		for(int i=1;i<=n;i++){
			sa.insert(a[i]);
		}
		for(int i=1;i<=m;i++){
			sb.insert(b[i]);
		}
		while(sb.size()){
			multiset<int>::iterator it=prev(sb.end());
			if(sa.find(*it)!=sa.end()){
				sa.erase(sa.find(*it));
			}else{
				if((*it)==1){
					flag=true;
					break;
				}else{
					sb.insert((*it)/2);
					sb.insert((*it)-(*it)/2);
				}
			}
			sb.erase(it);
			if(sb.size()>sa.size()){
				flag=true;
				break;
			}
		}
		if(flag) cout<<"No"<<'\n';
		else cout<<"Yes"<<'\n';
	} 
	return 0;
}

E

考虑操作顺序是假的,我们只关心对每个数进行了几次操作。

因为 m 很小,我们可以对于每一个 ai 暴力算出操作 j 次的最小值,记为 fi,j

然后就是经典的多路归并贪心了,用堆维护。

复杂度 O(2mn+klogn)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int all=(1<<30)-1,inf=1e12;
int t,n,m,k,a[100005],b[100005],c[1025],f[100005][11],ans;
struct node{
	int id,cnt,val;
	bool operator <(const node &a)const{
		return a.val<val;
	}
};
priority_queue<node> q;
void init(){
	ans=0;
	for(register int i=1;i<=n;i++){
		for(register int j=1;j<=m;j++){
			f[i][j]=inf;
		}
	}
	while(q.size()) q.pop();
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n>>m>>k;
		init();
		for(int i=1;i<=n;i++){
			cin>>a[i];
			ans+=a[i];
		}
		for(int i=1;i<=m;i++){
			cin>>b[i];
		}
		for(int s=1;s<(1<<m);s++){
			c[s]=all;
			for(int i=1;i<=m;i++){
				if(s&(1<<(i-1))) c[s]&=b[i];
			}
		}
		for(register int i=1;i<=n;i++){
			for(register int s=1;s<(1<<m);s++){
				int l=__builtin_popcount(s);
				f[i][l]=min(f[i][l],a[i]&c[s]);
			}
		}
		for(int i=1;i<=n;i++){
			q.push((node){i,1,f[i][1]-a[i]});
		}
		while(k--){
			node tmp=q.top();
			q.pop();
			ans+=tmp.val;
			if(tmp.cnt<m){
				tmp.cnt++;
				tmp.val=f[tmp.id][tmp.cnt]-f[tmp.id][tmp.cnt-1];
				q.push(tmp);
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}

F1

感觉又遇见了一次 NOIP 2024 T1。

考虑最小化操作次数是假的,因为合法操作序列是唯一的。

考虑从 1n 枚举 i,假设枚举到 i 的时候 s[1,i1]t[1,i1] 已经对应好了。

  • 如果 si=ti,那么不用管这一位。

  • 否则:

    • si1=si,一定死,因为后面的数根本换不过来。
    • 否则,设 j 表示 t[i,j] 这一块,k 表示 s[i,k] 中有 ji+1ti
      • 如果 sk+1=sk 或者 k>n,一定死,因为后面的数还是换不过来。
      • 否则,更新交换次数为 s[i,k]ti 块的数量,更新 s[i,k] 的值。

然后就做到均摊 O(n) 了。

#include<bits/stdc++.h>
using namespace std;
string s1,s2;
int t,n,a[400005],b[400005],ans;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>t;
	while(t--){
		cin>>s1;
		cin>>s2;
		n=s1.size();
		for(int i=1;i<=n;i++){
			a[i]=s1[i-1]-'0';
		}
		for(int i=1;i<=n;i++){
			b[i]=s2[i-1]-'0';
		}
		for(int i=1;i<=n;i++){
			if(a[i]!=b[i]){
				if(i>1 && a[i-1]==a[i]){
					ans=-1;
					break;
				}
				int j=i;
				while(j+1<=n && b[j+1]==b[i]) j++;
				int k=i,cnt=0;
				while(k<=n && cnt<(j-i+1)) k++,cnt+=(a[k]==b[i]);
				if(k>n || (k<n && a[k]==a[k+1])){
					ans=-1;
					break;
				}
				for(int l=i+1;l<=k;l++){
					if(a[l]==b[i] && a[l]!=a[l-1]) ans++;
				}
				for(int l=i;l<=j;l++){
					a[l]=b[i];
				}
				for(int l=j+1;l<=k;l++){
					a[l]=(!b[i]);
				}
				i=j;
			}
		}
		cout<<ans<<'\n';
		ans=0;
	}
	return 0;
}

G

遇到自适应交互库先考虑它是怎么写的。

不难发现,想要使答案达到下界,只需要把点均分成 3 份,选 2 份连一个团,剩下一份当成孤立点。

此时的答案为 (n+1)3

我们断言,这个答案是一定合法的,考虑这样一种构造方法:

用一个栈维护当前的同色链,每次加入一个点。

  • 询问这个点和栈顶的边的颜色,如果颜色和链的颜色相同,那么把这个点加入栈,否则,把栈顶和次栈顶弹出,把这两条异色边都存下来。

  • 枚举完所有点之后,把当前栈清空,并记录栈内所有边。

分析一下,在最劣情况下,我们用 3 个点,2 次询问,确定了 1 条颜色边,因此,我们这么询问是可以满足 (n+1)3 个点对的。

#include<bits/stdc++.h>
using namespace std;
int t,n,k,c1,c2,x,y;
stack<int> d;
struct node{
	int x,y;
};
vector<node> ans[2];
int main(){
	cin>>t;
	while(t--){
		cin>>n;
		k=(n+1)/3;
		cout<<k<<endl;
		for(int i=1;i<=n;i++){
			if(!d.size()) d.push(i);
			else{
				x=d.top();
				cout<<"? "<<x<<' '<<i<<endl;
				cin>>c2;
				if(d.size()==1) c1=c2;
				if(c1!=c2){
					d.pop();
					y=d.top();
					d.pop();
					ans[c1].push_back((node){x,y});
					ans[c2].push_back((node){x,i});
				}else d.push(i);
			}
		}
		while(d.size()>=2){
			x=d.top();
			d.pop();
			y=d.top();
			d.pop();
			ans[c1].push_back((node){x,y});
		}
		if(ans[0].size()<ans[1].size()) swap(ans[0],ans[1]);
		cout<<"! ";
		for(int i=0;i<min(k,(int)ans[0].size());i++){
			cout<<ans[0][i].x<<' '<<ans[0][i].y<<' ';
		}
		cout<<endl;
		ans[0].clear();
		ans[1].clear();
		while(d.size()) d.pop();
	}
	return 0;
}

本文作者:Kenma

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

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

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