【带权并查集 + DP】AcWing 259. 真正的骗子

这题属实逆天。。题面在输出格式中没有说明需要将编号排序后输出,让我困惑了半天呜呜。

分析

题目本身的思路是很简单的。

我们从一个人说 yesno 能够得到什么呢?

  • 假设这个人是天神,那么说 yes 说明对方也是天神,否则是恶魔。
  • 假设这个人是恶魔,那么说 yes 说明对方也是恶魔,否则是天神。

这说明,yes 意味着该人和对方身份相同,no 则意味着不同

我们便可以自然地想到使用带权并查集来维护人与人的关系,如果二人身份相同,那么之间的权值应该是 0,否则为 1

使用并查集将题目给的关系处理出来后,我们可以得到 tot 个连通块,记第 k 个连通块和根节点(也就是并查集中每棵树的 root)距离为 0 的人数为 ak,距离为 1 的人数为 bk

现在的问题变成:给你一个 X(也就是题目的天神人数),问是否存在且唯一存在一种决策,使得对于每个位置 kak,bk 二选一,能够让最后选出的值和等于 X

我们采取 DP 来解决这个问题,定义 dp(i,j) 为选到第 i 个位置时能够填满容量为 j 的背包的方案数,那么转移方程是:

dp(i,j)+=dp(i1,jai)选取 aiai0

dp(i,j)+=dp(i1,jbi)选取 bibi0

显然,如果 dp(tot,X)=1 就是有解,反之无解。

这题还需要输出方案,我们再开一个 pre 数组记录一下转移的过程。

#include<bits/stdc++.h>
using namespace std;

#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dwn(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
#define all(x) (x).begin(), (x).end()

#define x first
#define y second
using pii = pair<int, int>;
using ll = long long;

inline void read(int &x){
    int s=0; x=1;
    char ch=getchar();
    while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();}
    while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    x*=s;
}

const int N=630;

int m, X, Y, n;
int f[N], d[N];

int find(int x){
	if(x==f[x]) return x;
	int root=find(f[x]);
	d[x]^=d[f[x]];
	return f[x]=root;
}

int tot;
int a[N], b[N];

int main(){
	while(cin>>m>>X>>Y, m || X || Y){
		n=X+Y;
		rep(i,1,n) f[i]=i, d[i]=0;
		rep(i,1,m){
			int u, v; read(u), read(v); string s; cin>>s;
			int pu=find(u), pv=find(v);
			if(pu!=pv){
				d[pu]=d[u]^d[v]^(s=="no");
				f[pu]=pv;
			}
		}
		
		vector<int> g[n+5];
		map<int, int> mp;
		tot=0;
		rep(i,1,n) g[find(i)].pb(i);
		rep(i,1,n) if(g[i].size()){
			mp[++tot]=i;
			a[tot]=b[tot]=0;
			for(auto j: g[i]){
				if(!d[j]) a[tot]++;
				else b[tot]++;
			}
		}
		int dp[tot+5][n+5]; memset(dp, 0, sizeof dp);
		int pre[tot+5][n+5]; memset(pre, 0, sizeof pre);
		
		dp[0][0]=1;
		rep(i,1,tot){
			rep(j,a[i],X){
				dp[i][j]+=dp[i-1][j-a[i]];
				if(dp[i-1][j-a[i]]) pre[i][j]=j-a[i];
			}
			rep(j,b[i],X){
				dp[i][j]+=dp[i-1][j-b[i]];
				if(dp[i-1][j-b[i]]) pre[i][j]=j-b[i];
			}
		}

		if(dp[tot][X]!=1){
			puts("no");
			continue;
		}

		int u=X, idx=tot;
		vector<int> buf;
		while(1){
			int go=pre[idx][u];
			if(go+a[idx]==u) buf.pb(0);
			else buf.pb(1);
			u=go;
			if(--idx==0) break;
		}
		reverse(all(buf));
		vector<int> res;

		rep(i,0,tot-1){
			int id=mp[i+1];
			if(!buf[i]){
				for(auto e: g[id]) if(!d[e]) res.pb(e);
			}
			else{
				for(auto e: g[id]) if(d[e]) res.pb(e);
			}
		}
		sort(all(res));
		for(auto i: res) cout<<i<<endl;
		puts("end");
	}
	return 0;
}
posted @   HinanawiTenshi  阅读(131)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示