[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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章