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

2022/2/1 23:10:11

本文主要是介绍【带权并查集 + DP】真正的骗子,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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

分析

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

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

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

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

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

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

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

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

\[dp(i, j) += dp(i-1, j-a_i) (\texttt{选取 $a_i$},这里 a_i\neq 0) \]

\[dp(i, j) += dp(i-1, j-b_i) (\texttt{选取 $b_i$},这里 b_i\neq 0) \]

显然,如果 \(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;
}


这篇关于【带权并查集 + DP】真正的骗子的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程