题解 P3275 【[SCOI2011]糖果】

· · 题解

这题可以用缩点的方法做。首先我们只加上1、3、5三种情况的边,如果a<=b那么a向b连边,如果相等就两边都连。然后我们用Tarjan缩一遍环,然后可以构出一个DAG(有向无环图)。由于缩掉的都是1、3、5情况的边,那么他们构成的环就意味着环上的点必须相等。缩环之后重构图,加上2、4情况的边,这个时候要特判:如果此时有一条2、4的边构成自环(意味着它在原图中连接了两个必须相等的点),所以直接输出-1结束。然后我们给图来一遍拓扑排序,如果此时出现环(拓扑排序有些点没有访问到),必然意味着后面新加的2、4情况的边构成了环(1、3、5情况的边已经没有环了),此时也要直接输出-1结束。最后按照拓扑序来一遍dp就可以得出结果。(统计结果记得用long long)

代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=100010;
struct relationship{
    int x,a,b;
}r[maxn];
struct edge{
    int to,next;
    bool same;//记录该边是否允许两边相等  
}e[maxn<<1],e2[maxn];
int n,m,num,num2,cnt,head[maxn],head2[maxn],ltk[maxn],dfn[maxn],low[maxn];
int sta[maxn],top,size[maxn],que[maxn],du[maxn],h,tail,candy[maxn];
bool vis[maxn];
long long ans=0;
void add(int u,int v,bool k){
    e[++num].to=v;e[num].same=k;e[num].next=head[u];head[u]=num;
}
void add2(int u,int v,bool k){
    du[v]++;
    e2[++num2].to=v;e2[num2].same=k;e2[num2].next=head2[u];head2[u]=num2;
}
void dfs(int x){
    dfn[x]=low[x]=++cnt;sta[++top]=x;
    for(int i=head[x];i;i=e[i].next){
        if(!dfn[e[i].to]){
            dfs(e[i].to);
            low[x]=min(low[x],low[e[i].to]);
        }
        else if(!ltk[e[i].to]){
            low[x]=min(low[x],dfn[e[i].to]);
        }
    }
    if(dfn[x]==low[x]){
        ltk[0]++;
        while(top){
            ltk[sta[top]]=ltk[0];
            size[ltk[0]]++;
            if(sta[top--]==x)break;
        }
    }
}
void Rebuild(){
    for(int i=1;i<=n;i++){
        for(int j=head[i];j;j=e[j].next){
            if(ltk[i]!=ltk[e[j].to]){
                add2(ltk[i],ltk[e[j].to],true);
            }
        }
    }
    for(int i=1;i<=m;i++){
        if(r[i].x==2){
            if(ltk[r[i].a]==ltk[r[i].b]){
                printf("-1\n");
                exit(0);
            }
            else{
                add2(ltk[r[i].a],ltk[r[i].b],false);
            }
        }
        else if(r[i].x==4){
            if(ltk[r[i].a]==ltk[r[i].b]){
                printf("-1\n");
                exit(0);
            }
            else{
                add2(ltk[r[i].b],ltk[r[i].a],false);
            }
        }
    }
}
void Topsort(){
    for(int i=1;i<=ltk[0];i++){
        if(!du[i]){
            que[++tail]=i;
            vis[i]=true;
            candy[i]=1;       //candy[i]记录i节点每个孩子要多少糖
        }
    }
    while(h<tail){
        int x=que[++h];
        for(int i=head2[x];i;i=e2[i].next){
            if(!--du[e2[i].to]){
                que[++tail]=e2[i].to;
                vis[e2[i].to]=true;
            }
            if(!e2[i].same){
                candy[e2[i].to]=max(candy[e2[i].to],candy[x]+1);
            }
            else{
                candy[e2[i].to]=max(candy[e2[i].to],candy[x]);
            }//dp,如果有一条边不允许相等就要+1 
        }
    }
    for(int i=1;i<=ltk[0];i++){
        if(!vis[i]){
            printf("-1\n");
            exit(0);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&r[i].x,&r[i].a,&r[i].b);
        if(r[i].x==1){
            add(r[i].a,r[i].b,true);
            add(r[i].b,r[i].a,true);
        }
        else if(r[i].x==3){
            add(r[i].b,r[i].a,true);
        }
        else if(r[i].x==5){
            add(r[i].a,r[i].b,true);
        }
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
    Rebuild();Topsort();
    for(int i=1;i<=ltk[0];i++){
        ans+=(candy[i]*size[i]);//记录答案,记得开long long
    }
    printf("%lld\n",ans);
    return 0;
}