• [PA2010]Riddle


    题目描述

    \(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。

    请选择一些关键点,使得每个部分有一个关键点,且每条边至少有一个端点是关键点。

    解法

    有一些限制条件,问你是否有合法方案,容易想到 2-SAT 。

    思考如何建立新图,一共有两种限制:对于原图的每条边来说,它连接的两个点至少有一个点被选;对于一组相关联的点来说,他们之中只有一个被选择。可以看到对于每个点,它的状态只有选与不选这两种。

    那么容易想到把每个点都拆成两个点,一个表示选(记为 \(u_1\)),另一个表示不选(记为 \(u_2\))。对于第一种限制,若原图中一条边连接了 \(u\)\(v\) 两个点,那么连接 \(u_2\to v_1\)\(v_2\to u_1\),因为若 \(u\) 不选必选 \(v\) ,后者同理。对于第二种限制,连接所有 \(u_1\to v_2\),其中 \(v\) 为除 \(u\) 外同一个部分的其它所有点,表示选了 \(u\) ,那么这个部分的其它点都不能选。

    最后按套路跑一遍 \(tarjan\)

    优化

    可以看出,如果所有点都被划分在一个部分,即一个部分有 \(n\) 个点,那么总边数一定是 \(O(n^2)\) 的。

    如果按最后分组时读入节点的顺序来看的话,那么我们其实是把一个点向一段连续的区间连边,容易想到线段树优化建边。然而非常毒瘤的是数据为 \(1e6\) ,那么线段树常数稍一大就会挂掉。

    还可以怎么优化?

    再次回到题目,我们漏掉了一个重要的条件:在一组之内,每次区间的左端点或者右端点都是固定的!

    也就是说每次我们连接的其实是一个前缀和后缀,于是想到了前缀后缀优化。

    如图,节点 \(u\) 只需要连接一个节点实际上就连接了整个前缀,后缀同理。而对于每个节点都只需要连接前缀后缀两个节点,故空间复杂度为 \(O(n)\)。至此可以很好地通过此题。

    #include<stdio.h>
    #define lid id<<1
    #define rid id<<1|1
    using namespace std;
    #define N 5000007
    
    template<class T>
    inline void read(T &x){
        T flag=1;x=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
        x*=flag;
    }
    
    struct E{
        int next,to;
    }e[N<<1];
    int n,m,K,cnt,len[N],a[N],dfn[N],c[N],low[N],tim=0,sta[N],top=0,scc_num=0,head[N];
    bool vis[N];
    
    inline void add(int id,int to){
        e[++cnt]=(E){head[id],to};
        head[id]=cnt;
    }
    
    inline int min(int x,int y){return x<y? x:y;}
    inline void tarjan(int u){
        dfn[u]=low[u]=++tim;
        sta[top++]=u;vis[u]=true;
        for(int i=head[u];i;i=e[i].next){
            int v=e[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]){
            int y;
            scc_num++;
            do{
                y=sta[--top];
                vis[y]=false;
                c[y]=scc_num;
            }while(u!=y);
        }
    }
    
    inline bool check(){
        for(int i=1;i<=(n<<2);++i)
            if(!dfn[i]) tarjan(i);
        for(int i=1;i<=n;++i)
            if(c[a[i]]==c[a[i]+n]) return false;
        return true;
    }
    
    int main(){
    //    freopen("sample2.in","r",stdin);
        read(n),read(m),read(K);
        for(int i=1;i<=m;i++){
            int u,v;
            read(u),read(v);
            add(u+n,v),add(v+n,u);
        }
        int ret=0;
        for(int i=1;i<=K;i++){
            read(len[i]);
            for(int j=1;j<=len[i];j++){
                int pos=ret+j;
                read(a[pos]);
                if(j!=1){
                    add(a[pos]+2*n,a[pos-1]+2*n);
                    add(a[pos],a[pos-1]+2*n);
                }
                add(a[pos]+2*n,a[pos]+n);
            }
            ret+=len[i];
        }
        ret=0;
        for(int i=1;i<=K;i++){
            for(int j=1;j<=len[i];j++){
                int pos=ret+j;
                if(j!=len[i]){
                    add(a[pos]+3*n,a[pos+1]+3*n);
                    add(a[pos],a[pos+1]+3*n);
                }
                add(a[pos]+3*n,a[pos]+n);
            }
            ret+=len[i];
        }
        printf("%s",check()? "TAK":"NIE");
    }
    
    /*
    5 4 1
    1 2
    1 3
    3 4
    4 5
    5 2 3 1 5 4
    */
    
  • 相关阅读:
    .NET/C#程序开发中如何更优美地实现失败任务重试的逻辑?
    ava.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
    mysql安装到这里不能下去了,怎么解决
    win10安装jdk8点击下一步没反应,点击下一步闪退,win10安装jdk8失败
    运行打包的jar,jar中没有主清单属性
    idea如何打包项目(java)
    关于idea(eclipse同样适用,都是一样的步骤)无法导入Javafx包的问题及解决方案:
    Intellij idea 报错:Error : java 不支持发行版本5
    Class.getResource("xxx.css")得到值为null
    jdk11安装没有jre文件夹
  • 原文地址:https://www.cnblogs.com/wwlwQWQ/p/13556029.html
Copyright © 2020-2023  润新知