[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
*/
posted @ 2020-08-24 20:11  Kreap  阅读(149)  评论(0编辑  收藏  举报