[SCOI2011]糖果(差分约束(爆long long爆T特判)+spfa的判负环的dfs优化(玄学??)+tarjan&缩点&topsort上的dp与判环 )

题意:传送门

题解:这道题一眼看上去就是差分约束板题,但是有两个大坑,先说下如何建图,对于

op==1 a==b a-b>=0 b-a>=0

op==2 a<b a<=b-1 a-b<=-1 b-a>=1

op==3 a>=b a-b>=0

op==4 a>b a-1>=b a-b>=1

op==5 a<=b a-b<=0 b-a>=0

还有每个孩子的糖果都是正数,那么所有的的a-0>=1

可以看出,我是全部转化成>=进行来做,然后就是跑最长路,但是这样就太弱了,发现第一发wa,后经分析,ans爆long long,之后有一个点老是T,看了许多题解,第一种是最关键的地方,就是在第二个和第四个操作时如果一旦有输入的两个点是相同的,那么岂不是有自环,并且环上有权值,那么岂不是直接输出不可以就行,此时就可以A了

附上代码:



#include<bits/stdc++.h>

using namespace std;

const int maxm=1e6+50;
const int maxn=1e5+50;

struct edge{
    int v,w,next;
};
edge edges[maxm<<2];
int head[maxn],tot;

void init()
{
    memset(head,-1,sizeof(head));
    tot=0;
}

void add_edges(int u,int v,int w)
{
    edges[tot].v=v;
    edges[tot].w=w;
    edges[tot].next=head[u];
    head[u]=tot++;
}

int n,k;
bool vis[maxn];
int cnt[maxn];
int dist[maxn];

bool spfa()
{
    queue<int>q;
    for(int i=0;i<=n;i++){
        q.push(i);
        vis[i]=false;dist[i]=0;cnt[i]=1;
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];~i;i=edges[i].next){
            int v=edges[i].v;
            if(dist[v]<dist[u]+edges[i].w){
                dist[v]=dist[u]+edges[i].w;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                    if(++cnt[v]>n+1){
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

int a[maxn];

int main()
{
    init();
    scanf("%d%d",&n,&k);
    int op,a,b;
    for(int i=0;i<k;i++){
        scanf("%d%d%d",&op,&a,&b);
        a--;b--;
        if(op==1){
            add_edges(a,b,0);
            add_edges(b,a,0);
        }else if(op==2){
            add_edges(a,b,1);
        }else if(op==3){
            add_edges(b,a,0);
        }else if(op==4){
            add_edges(b,a,1);
        }else if(op==5){
            add_edges(a,b,0);
        }
        if(op%2==0&&a==b){
            printf("-1\n");
            return 0;
        }
    }
    for(int i=0;i<n;i++){
        add_edges(n,i,1);
    }
    if(spfa()){
        long long ans=0;
        for(int i=0;i<n;i++){
            ans+=dist[i]-dist[n];
        }
        printf("%lld\n",ans);
    }else{
        printf("%d\n",-1);
    }
    return 0;
}

还可以再进行优化,终于看了传说中的dfs优化的spfa,可以了解下这个传送门,可以参考下这篇,虽然感觉没讲到重点传送门,这篇文章说了如果不存在最短路的情况下,这样跑是没问题,但是现在要用dfs判断是否存在负环,那么就得重新写一种判断方法了,但是改成dfs后,竟然还有一个点T,然后看到网上有人说是因为最后加特判每个数是正数那个点时需要从后往前加,因为出题人出了一个十万条的链???什么意思,玄学么,然后仔细想了想自己为什么第一发就能过,因为自己入队时就是从前往后入的,这题???搞的不是很懂,还是先学一发dfs优化的spfa跑跑。

附上代码:



#include<bits/stdc++.h>

using namespace std;

const int maxm=1e6+50;
const int maxn=1e5+50;

struct edge{
    int v,w,next;
};
edge edges[maxm<<2];
int head[maxn],tot;

void init()
{
    memset(head,-1,sizeof(head));
    tot=0;
}

void add_edges(int u,int v,int w)
{
    edges[tot].v=v;
    edges[tot].w=w;
    edges[tot].next=head[u];
    head[u]=tot++;
}

int n,k;
bool vis[maxn];
int cnt[maxn];
int dist[maxn];

bool spfa()
{
    queue<int>q;
    for(int i=0;i<=n;i++){
        q.push(i);
        vis[i]=false;dist[i]=0;cnt[i]=1;
    }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];~i;i=edges[i].next){
            int v=edges[i].v;
            if(dist[v]<dist[u]+edges[i].w){
                dist[v]=dist[u]+edges[i].w;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                    if(++cnt[v]>n+1){
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

int book[maxn];

bool dfs(int p)
{
    book[p]=true;
    for(int i=head[p];~i;i=edges[i].next){
        edge &e=edges[i];
        if(dist[e.v]<dist[p]+e.w){
            dist[e.v]=dist[p]+e.w;
            if(book[e.v]||!dfs(e.v)){
                return false;
            }
        }
    }
    book[p]=false;
    return true;
}

int a[maxn];

int main()
{
    init();
    scanf("%d%d",&n,&k);
    int op,a,b;
    for(int i=0;i<k;i++){
        scanf("%d%d%d",&op,&a,&b);
        a--;b--;
        if(op==1){
            add_edges(a,b,0);
            add_edges(b,a,0);
        }else if(op==2){
            add_edges(a,b,1);
        }else if(op==3){
            add_edges(b,a,0);
        }else if(op==4){
            add_edges(b,a,1);
        }else if(op==5){
            add_edges(a,b,0);
        }
        if(op%2==0&&a==b){
            printf("-1\n");
            return 0;
        }
    }
    for(int i=n-1;i>=0;i--){
        add_edges(n,i,1);
    }
    dist[n]=0;
    if(dfs(n)){
        long long ans=0;
        for(int i=0;i<n;i++){
            ans+=dist[i];
        }
        printf("%lld\n",ans);
    }else{
        printf("%d\n",-1);
    }
    return 0;
}

最后还有一种是使用tarjan+缩点+dp做出来的,说下题解,首先只加上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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值