zoukankan      html  css  js  c++  java
  • 2019年湘潭大学程序设计竞赛

    <题目链接>

     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,除此之外他还想得到一组在此情况下的一组合理的解—即所有人的身份。或者确定记录本来就是矛盾的所以没有任何符合条件的解。

    解题分析:

    本题可以根据下列规则将所有人分成两个集合:
    $i$认为$j$是一个$Truthman$,那么$i$和$j$属于一个集合(一真都真一假都假)
    $i$认为$j$是一个$Fakeman$,那么$i$和$j$属于不同集合(你真我假你假我真)

    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();
        }
    }
    View Code

    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("");
            }
        }
    }
    View Code

    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]);
        }
    }
    View Code
  • 相关阅读:
    【每天都要看一下】
    【这里有别人的经验,也有好玩的发现】
    【WPF】Listbox模板内button点击选中当前listboxItem
    【WFP】弹出窗口不在win10 任务列表里显示的方法
    PSD路径转换为 WPF path 的data
    【WPF】Listbox内item的样式替换默认选中样式和鼠标滑过样式
    【WPF】ListBox1内嵌套ListBox2 2的滑轮滚动阻止1的滚动解决方法
    【C#】文本框拼音检索汉字
    【WPF】Datagrid显示最低下一跳
    【C#】绝对随机数
  • 原文地址:https://www.cnblogs.com/00isok/p/10823044.html
Copyright © 2011-2022 走看看