【带权并查集 + DP】真正的骗子
2022/2/1 23:10:11
本文主要是介绍【带权并查集 + DP】真正的骗子,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
这题属实逆天。。题面在输出格式中没有说明需要将编号排序后输出,让我困惑了半天呜呜。
分析
题目本身的思路是很简单的。
我们从一个人说 yes
和 no
能够得到什么呢?
- 假设这个人是天神,那么说
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】真正的骗子的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南