<题目链接>
G.Truthman or Fakeman (DFS染色 || 并查集) (好题)
题目大意:
有n个人在玩一个身份扮演的游戏。
把这n个人编号为1,2,3...n。
其中每个人会扮演下面两种身份中的一种:
Truthman:当某个人扮演Truthman时,这个人只会说真话。
Fakeman:当某个人扮演Fakeman时,这个人只会说假话。
这n个人是互相知道身份的,但是Casya作为一个旁观者不知道任何一个人的身份。
为了让Casya有可能推断这些人的身份,这n个人说了m句话。
每句话的内容只包含某人对某人身份的一条描述,且被Casya记录为以下形式:
u,v,0 -- u认为v是一个Fakeman;
u,v,1 -- u认为v是一个Truthman;
当然这些话不一定都是真话,这取决于说话的人的身份。
但是可以肯定的是身份只有两种,也就是说某个人不是Truthman就是Fakeman。
Casya想知道不违反上面的条件和记录最少有多少个Fakeman,除此之外他还想得到一组在此情况下的一组合理的解—即所有人的身份。或者确定记录本来就是矛盾的所以没有任何符合条件的解。
解题分析:
DFS染色和并查集都能做。
并查集就是将点数*2,然后用并查集来维护一下真、假两个集合,比较巧妙。
#include <bits/stdc++.h> using namespace std; template<typename T> inline void read(T&x){ x=0;int f=1;char ch=getchar(); while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } x*=f; } #define clr(a,b) memset(a,b,sizeof(a)) #define REP(i,s,t) for(int i=s;i<=t;i++) const int N = 2e5+5; int fa[N],sz[N],vis[N]; int n,m; int find(int x){ return x==fa[x]?x:fa[x]=find(fa[x]); } void Union(int u,int v){ int f1=find(u),f2=find(v); if(f1!=f2){ fa[f2]=f1; sz[f1]+=sz[f2]; } } //1~n的标号表示真,n+1~2*n表示假 inline void Solve(){ bool fp=true; REP(i,1,n){ int u=find(i),v=find(i+n); if(u==v){ fp=false;break; } if(!vis[u]){ if(sz[u]>=sz[v])vis[u]=1,vis[v]=-1; else vis[u]=-1,vis[v]=1; } } if(!fp)puts("-1"); else{ REP(i,1,n){ int f=find(i); if(vis[f]==1)printf("1"); else if(vis[f]==-1)printf("0"); }puts(""); } } int main(){ int T;cin>>T; while(T--){ read(n);read(m); REP(i,1,2*n){ fa[i]=i,vis[i]=0; i<=n?sz[i]=1:sz[i]=0; } REP(i,1,m){ int u,v,c;read(u);read(v);read(c); if(c==1)Union(u,v),Union(u+n,v+n); else Union(u,v+n),Union(u+n,v); } Solve(); } }
DFS染色的代码WA了,23333……不知道为什么。
#include <bits/stdc++.h> using namespace std; template<typename T> inline void read(T&x){ x=0;int f=1;char ch=getchar(); while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } x*=f; } #define pb push_back #define REP(i,s,t) for(int i=s;i<=t;i++) #define clr(a,b) memset(a,b,sizeof(a)) const int N = 1e5+5; struct Edge{ int to,fp; }; vector<Edge>G[N]; int n,m,col[N],vis[N]; //0代表不同集合,1表示相同集合 int isok; int cnt1,cnt2,ord1,ord2; void dfs(int u,int pre){ if(col[u]==ord1)cnt1++; else cnt2++; for(int i=0;i<G[u].size();i++){ Edge e=G[u][i]; int v=e.to,cur=e.fp; if(v==pre)continue; if(col[v]){ if(cur==0){ //如果这个点之前被标记过了,并且cur是0,说明这个点一定要和之前的点不同 if(col[v]^col[u]){ //如果这两个点异或值不同,表示合法 isok=0;return; } }else if(cur==1){ if((col[v]^col[u])!=0){ isok=0;return; } } continue; } if(cur==1)col[v]=col[u]; //如果这两个点属于同一个集合 else col[v]=col[u]^1; //用这种成对的异或数来简化操作 dfs(v,u); } } int main(){ int T;cin>>T; while(T--){ read(n);read(m); int ans=0; REP(i,1,m){ int u,v,c;read(u);read(v);read(c); G[u].pb(Edge{v,c}); G[v].pb(Edge{u,c}); } ord1=2,ord2=3;isok=1; clr(col,0);clr(vis,0); REP(i,1,n) if(!col[i]){ //可能不连通 cnt1=cnt2=0; col[i]=ord1; dfs(i,-1); if(!isok)break; if(cnt1>=cnt2)vis[ord1]=1; //vis记录下每次遍历的时候,数量较多的那个色块,因为要使faskman最少,所以尽可能使得fakeman是人数少的这一块 else vis[ord2]=1; ord1++;ord2++; } if(!isok)puts("-1"); else{ REP(i,1,n){ if(vis[col[i]])printf("1"); else printf("0"); }puts(""); } } }
H.Chat (分组背包)
题目大意:
在Casya生活的世界里,一天由m个小时组成。
最近Casya的女神终于答应在接下来的n天中与Casya聊天,Casya非常激动。
在这n天中的每一天的每一个小时中女神可能会在线或者不在线,
某个小时如果女神如果在线且Casya在线的话他们就会开心的聊一个小时;
反之如果女神在线Casya没有在线的话女神就会认为Casya放了她的鸽子而积累一点生气度。
而Casya是个很懒惰的人,他每天只愿意上线一次,当他某天下线后就不愿意再上线了。
换句话说,他每天在线的时间必须是连续的。
现在Casya已经知道每一天的每个小时女神是否会在线
Casya希望在这n天中女神的总生气度不超过k,在此前提下Casya希望他的总上线时间最小。
假设每个小时Casya和女神要么完整在线要么完整不在线,请问Casya在这n天中最小的总上线时间是多少小时?
解题分析:
分组背包,$dp[i][j]$表示有i个1不选时,能够节省最多的上线时间。
每一行删除不同数量的1都可以看成一个物品,物品的体积就是删除1的个数,物品的价值就是能够节省的最大上线时间,预处理稍微注意一下。
#include <bits/stdc++.h> using namespace std; const int N = 205; #define REP(i,s,t) for(int i=s;i<=t;i++) int dp[N],val[N][N],sum[N]; char s[N]; int n,m,k; inline void init(){ memset(val,0,sizeof(val)); REP(i,1,n){ memset(sum,0,sizeof(sum)); scanf("%s",s+1); for(int j=1;j<=m;j++){ sum[j]=sum[j-1]+s[j]-'0'; } val[i][sum[m]]=m; REP(j,1,m) REP(g,1,j){ //g为起点,j为终点 int len=j-g+1; int num=sum[m]-(sum[j]-sum[g-1]); //表示有num个1不选的时候,能够节省的最多时间 val[i][num]=max(val[i][num],m-len); } } } int main(){ int T;cin>>T; while(T--){ cin>>n>>m>>k; init(); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++){ for(int j=k;j>=0;j--){ for(int g=0;g<=m;g++){ //g表示物品的序号,同时也是它的体积 if(j>=g)dp[j]=max(dp[j],dp[j-g]+val[i][g]); } } } printf("%d ",n*m-dp[k]); } }