zoukankan      html  css  js  c++  java
  • P3825 [NOI2017]游戏

    原题链接  https://www.luogu.org/problem/P3825

    闲话时刻 

    本蒟蒻第 2 道通过的黑题 ,果然还是有人说水。。。 

    我觉得这个题出的很好,建边的时候细节很多,码量稍大,劝大家切的时候一定要细心了,不然就会像我一样把 “ = ” 打成 “;” 然后调了一天;

    题目大意

    有 n 个点,每个点有三种取值,至多只有 d 个变量可以取到三种取值,其余的都只有一种不能取,还有 m 个关系限制式,问是否有解,有解输出方案数;

    题解

    还没有掌握好 2 - SAT 的童鞋戳这里

    ①. 先考虑 d = 0 的情况:

    此时每场游戏只能选择两种不同类型的赛车,发现是一个裸的 2-SAT 问题;

    考虑怎么给点编号:

    对于一场游戏 s,如果地图是类型 a,那么可选的赛车类型为 ' B ' 和 ' C ',那么 ' B ' 的编号就是 s,' C ' 的编号就是 s+n;如果地图是类型  b,那么可选的赛车类型为 ' A ' 和 ' C ',那么 ' A ' 的编号就是 s, ' C ' 的编号就是 s+n;如果地图的类型是 c,那么可选的赛车类型为 ' A ' 和 ' B ',那么 ' A ' 的编号就是 s,' C ' 的编号就是 s+n;

    总的来说就是字母小的编号靠前,字母大的编号靠后; 

    int get(int k,char s)   //在第k个地图上,类型为s的赛车的编号是多少 
    {
        //S[k]表示第k个地图的类型 
        if(S[k]=='a')        //只能用赛车'B'和'C' 
        {
            if(s=='B') return k; 
            return k+n;
        }
        if(S[k]=='b')        //只能用赛车'A'和'C' 
        {
            if(s=='A') return k;
            else return k+n;
        }
        if(S[k]=='c')        //只能用赛车'A'和'B' 
        {
            if(s=='A') return k;
            return k+n;
        }
    }

    搞定了编号的问题,现在考虑怎么建边:

    设一场游戏能使用的两种赛车的类型分别是 i,i ' ;

    对于每个特殊要求四元组(a,ha,b,hb),表示如果第 a 场游戏用了类型为 ha 的赛车,第 b 场游戏必须使用类型为 hb 的赛车,我们要分一下三种情况考虑:

    <1> 如果第 a  场游戏由于地图的限制本来就不能使用类型为 h的赛车,那么我们不需要建任何边,直接 continue;

    <2> 如果第 a 场游戏能使用类型为 ha 的赛车,但是第 b 场游戏由于地图的限制不能使用类型为 hb 的赛车,就说明第 a 场游戏是不能使用类型为 ha 的赛车的,不然就无解了,那么我们连边 < ha , ha ' > 表示第 a 场游戏只能用类型为 ha ' 的赛车;

    <3> 如果第 a 场游戏能使用类型为 ha 的赛车,并且第 b 场游戏能使用类型为 hb 的赛车,那么我们根据限制连边 < ha , hb > 表示如果我们第 a 场游戏选择了类型为 ha 的赛车,那么第 b 场游戏必须选择类型为 hb 的赛车;反之,如果我们第 b 场游戏没有使用类型为 hb 的赛车,说明第 a 场游戏一定没有选择类型为 ha 的赛车,于是我们再连边 < hb ' , ha ' > ( 逆否命题与原命题等价 );

    建完边后,我们对所有点进行一次 tarjan 求强联通分量,然后再判断是否有解,若有解就输出强联通分量较小的那辆赛车就好了(这个不需要多说了吧qwq)

    int get(int k,char s)   //在第k个地图上,类型为s的赛车的编号是多少 
    {
        //S[k]表示第k个地图的类型 
        if(S[k]=='a')        //只能用赛车'B'和'C' 
        {
            if(s=='B') return k; 
            return k+n;
        }
        if(S[k]=='b')        //只能用赛车'A'和'C' 
        {
            if(s=='A') return k;
            else return k+n;
        }
        if(S[k]=='c')        //只能用赛车'A'和'B' 
        {
            if(s=='A') return k;
            return k+n;
        }
    }
    int fan(int a)                            //求在一场游戏中编号为a的赛车对应的另一辆赛车是多少       
    {
        if(a>n) return a-n;                   //如果这辆赛车的编号大,则另一辆赛车的编号小 
        return a+n;                           //如果这辆赛车的编号小,则另一辆赛车的编号大 
    }
    bool work()
    {
        clean();
        for(int i=1;i<=m;i++)
        {
            a=num[i][1];ha=s[i][1];           //如果第a场游戏使用了类型为ha的赛车,则第b场游戏必须使用类型为hb的赛车 
            b=num[i][2];hb=s[i][2];
            if(S[a]==ha+32) continue;         //情况<1>:如果第a场游戏本来就不能使用类型为ha的赛车,直接continue 
            int n1=get(a,ha);                 //求出在第a场游戏的地图上类型为ha的赛车的编号 
            int n2=get(b,hb);                 //求出在第b场游戏的地图上类型为hb的赛车的编号  
            if(S[b]==hb+32)                   //情况<2>:如果是第b场游戏不能使用类型为hb的赛车 
                add(n1,fan(n1));              //那么第a场游戏只能使用类型为ha'的赛车了 
            else                              //情况<3>:若第a场游戏能用类型为ha的赛车,且第b场游戏能用类型为hb的赛车 
            {
                add(n1,n2);                   //第a场游戏若用类型为ha的赛车,则第b场游戏必须用类型为hb的赛车 
                add(fan(n2),fan(n1));         //反之若第b场游戏没用类型为hb的赛车,则第a场游戏没有类型为ha的赛车 
            }
        }
        for(int i=1;i<=2*n;i++)               //tarjan求强联通分量 
        {
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=n;i++)                 //判无解情况 
        {
            if(scc[i]==scc[i+n]) return 0;
        }
        return 1;                             //否则有解 
    }

    ②. 然后考虑 d ≠ 0 的情况:

    然后部分 3-SAT?表示没见过qwq

    观察到 d 的范围很小,最大只有 8,所以推测最后的复杂度可能是一个 O ( X)的复杂度;

    考虑可以去枚举每个 x 地图上使用什么哪两个类型的赛车,换而言之我们可以枚举每个 x 地图是 a,b,c 三类地图的哪一类,时间复杂度 O((n+m)* 3d),显然过不去;

    考虑怎么优化:

    其实我们只要枚举是 a,b,c 类地图中的两种就好了,假如以 a,b 地图为例吧:若是 a 地图就说明在此 x 地图上可以用类型为 ' B ' 和 ' C ' 的两种赛车,若是 b 地图就说明在此 x 地图上可以用类型为 ' A ' 和 ' C ' 的两种赛车,这样三种类型的赛车都在此 x 地图上试过了,不就涵盖了所有的情况了?  

    dfs 部分的代码:

    void dfs(int k)                           //我们已经枚举到了第k个x型地图了 
    {
        if(k>d)                               //枚举完所有的x型跑道了
        {
            if(work()) bj=1;                  //跑2-sat看看此时有没有解 
            return ;
        } 
        for(int i=0;i<2;i++)                  //看看把它搞成哪种类型的地图(不能选哪种型号的赛车)
        {
            S[where[k]]=i+'a';                //这里将x型地图变成a,b这两种类型的地图 
            dfs(k+1);
            if(bj) return ;                   //有解直接返回 
        } 
    }

    奉上完整代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<vector>
    using namespace std;
    long long read()
    {
        char ch=getchar();
        long long a=0,x=1;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-') x=-x;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            a=(a<<1)+(a<<3)+(ch-'0');
            ch=getchar();
        }
        return a*x;
    }
    const int N=1e6;
    int n,m,d,a,b,bj,top,tim,edge_sum,scc_sum;
    int st[N],dfn[N],low[N],vis[N],scc[N],head[N],where[10],num[N][3];
    char s[N][3],S[N],ha,hb;
    
    struct node
    {
        int from,next,to;
    }A[N];
    void add(int from,int to)
    {
        edge_sum++;
        A[edge_sum].to=to;
        A[edge_sum].next=head[from];
        A[edge_sum].from=from;
        head[from]=edge_sum;
    }
    void tarjan(int u)
    {
        dfn[u]=low[u]=++tim;
        st[++top]=u;
        vis[u]=1;
        for(int i=head[u];i;i=A[i].next)
        {
            int v=A[i].to;
            if(!dfn[v])
            {
                tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(vis[v]) low[u]=min(low[u],dfn[v]);
        }
        if(dfn[u]==low[u])
        {
            scc_sum++;
            while(st[top]!=u)
            {
                vis[st[top]]=0;
                scc[st[top]]=scc_sum;
                top--;
            }
            vis[st[top]]=0;
            scc[st[top]]=scc_sum;
            top--;
        }
    }
    void clean()
    {
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(scc,0,sizeof(scc));
        memset(vis,0,sizeof(vis));
        memset(st,0,sizeof(st));
        memset(head,0,sizeof(head));
        memset(A,0,sizeof(A));
        edge_sum=0;scc_sum=0;
        tim=0;top=0;
    }
    int get(int k,char s)   //在第k个地图上,类型为s的赛车的编号是多少 
    {
        //S[k]表示第k个地图的类型 
        if(S[k]=='a')                         //只能用赛车'B'和'C' 
        {
            if(s=='B') return k; 
            return k+n;
        }
        if(S[k]=='b')                         //只能用赛车'A'和'C' 
        {
            if(s=='A') return k;
            else return k+n;
        }
        if(S[k]=='c')                         //只能用赛车'A'和'B' 
        {
            if(s=='A') return k;
            return k+n;
        }
    }
    int fan(int a)                            //求在一场游戏中编号为a的赛车对应的另一辆赛车是多少       
    {
        if(a>n) return a-n;                   //如果这辆赛车的编号大,则另一辆赛车的编号小 
        return a+n;                           //如果这辆赛车的编号小,则另一辆赛车的编号大 
    }
    bool work()
    {
        clean();
        for(int i=1;i<=m;i++)
        {
            a=num[i][1];ha=s[i][1];           //如果第a场游戏使用了类型为ha的赛车,则第b场游戏必须使用类型为hb的赛车 
            b=num[i][2];hb=s[i][2];
            if(S[a]==ha+32) continue;         //情况<1>:如果第a场游戏本来就不能使用类型为ha的赛车,直接continue 
            int n1=get(a,ha);                 //求出在第a场游戏的地图上类型为ha的赛车的编号 
            int n2=get(b,hb);                 //求出在第b场游戏的地图上类型为hb的赛车的编号  
            if(S[b]==hb+32)                   //情况<2>:如果是第b场游戏不能使用类型为hb的赛车 
                add(n1,fan(n1));              //那么第a场游戏只能使用类型为ha'的赛车了 
            else                              //情况<3>:若第a场游戏能用类型为ha的赛车,且第b场游戏能用类型为hb的赛车 
            {
                add(n1,n2);                   //第a场游戏若用类型为ha的赛车,则第b场游戏必须用类型为hb的赛车 
                add(fan(n2),fan(n1));         //反之若第b场游戏没用类型为hb的赛车,则第a场游戏没有类型为ha的赛车 
            }
        }
        for(int i=1;i<=2*n;i++)               //tarjan求强联通分量 
        {
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=n;i++)                 //判无解情况 
        {
            if(scc[i]==scc[i+n]) return 0;
        }
        return 1;                             //否则有解 
    }
    void dfs(int k)                           //我们已经枚举到了第k个x型地图了 
    {
        if(k>d)                               //枚举完所有的x型跑道了
        {
            if(work()) bj=1;                  //跑2-sat看看此时有没有解 
            return ;
        } 
        for(int i=0;i<2;i++)                  //看看把它搞成哪种类型的地图(不能选哪种型号的赛车)
        {
            S[where[k]]=i+'a';                //这里将x型地图变成a,b这两种类型的地图 
            dfs(k+1);
            if(bj) return ;                   //有解直接返回 
        } 
    }
    int main()
    {
        n=read();read();
        for(int i=1;i<=n;i++)
        {
            S[i]=getchar();
            if(S[i]=='x') where[++d]=i;       //where[i]记录第i个x地图的位置 
        }
        m=read();
        for(int i=1;i<=m;i++)                 //输入一定要处理好 
            scanf("%d %c %d %c",&num[i][1],&s[i][1],&num[i][2],&s[i][2]);
        dfs(1);                               //从第一个x型地图开始枚举 
        if(!bj)                               //无解的情况 
        {
            printf("-1");
            return 0;
        } 
        for(int i=1;i<=n;i++)                 //有解就输出方案数 
        {
            if(S[i]=='a')                     //如果是a型地图,就不能选择类型为A的赛车 
            {
                if(scc[i]<scc[i+n]) printf("B"); //选择强连通分量编号小的最为最后的选择 
                else printf("C");
            }
            if(S[i]=='b')                     //如果是b型地图,就不能选择类型为B的赛车 
            {
                if(scc[i]<scc[i+n]) printf("A"); //选择强连通分量编号小的最为最后的选择
                else printf("C");
            }
            if(S[i]=='c')                     //如果是c型地图,就不能选择类型为C的赛车 
            {
                if(scc[i]<scc[i+n]) printf("A"); //选择强连通分量编号小的最为最后的选择
                else printf("B");
            }
        }
        return 0;
    }
  • 相关阅读:
    数据结构(2)链表的实现
    vc 调试方法-2
    c语法拾遗-关于指针变量的声明
    收集的一些无聊的网站
    《将博客搬至CSDN》的文章
    黑马程序员-面向对象
    黑马程序员-类加载机制和反射。
    黑马程序员- 正则表达式
    黑马程序员-网络编程
    黑马程序员-File类+递归的简单应用
  • 原文地址:https://www.cnblogs.com/xcg123/p/11818548.html
Copyright © 2011-2022 走看看