1. 程式人生 > >【BZOJ2330】【SCOI2011】糖果——差分約束系統+tarjan

【BZOJ2330】【SCOI2011】糖果——差分約束系統+tarjan

題目連結

差分約束

這是一道經典的差分約束問題
我們假設最後第i個小朋友分得的糖果數為ai,aiN
那麼對於約束條件:i分得的糖果少於j的,有ai<aj,由於ai是整數,可以變形為aiaj+(1)
同樣,對於aiaj也可以看成aiaj+0
這些條件是不是很像最短路的最終狀態中有djdi+ei,j
於是我們可以將問題轉換成最短路/最長路求解
如果存在一個符合要求的最短路(最長路)的解,那麼每個點的最短路就是分配的糖果數。而如果因為出現負環沒有最短路(出現正環沒有最長路),那麼原問題無解
如何保障所有的ai>0呢?我們新加入一個超級節點s,使得as=

0,並且新加入n個限制as<ai,i[1,n],這樣既不影響答案,又能保證性質

最短路?最長路?

不難發現,以上兩個限制也可以寫成:

ajai+1
ajai+0
這樣就符合了最長路的限制djdi+ei,j
那麼最短路和最長路,使用哪一種都可以,沒有區別嗎?
其實不然,因為最短路可以最大化ai,而最長路可以最小化
為什麼呢?因為最短路使得每一個點的權值都是由一條邊更新來,而且這個點的權值無法再大,否則就會不符合該邊限制。最長路則正好相反,使得所有點權值無法再小。
這題我們要最小化ai,故考慮最長路

SPFA做法的缺陷

因為需要判環,考慮通過限制SPFA中節點入隊次數來判
每個節點最多入隊n次,若環長為n,環上每個節點都會入隊O(n)次,總體複雜度O

(N2)理論上是會T的(雖然大家都是這麼寫的也沒人T,鬱悶)
因此我們需要考慮更高效的判環方法

tarjan求SCC+拓撲圖DP

考慮什麼樣的環是正環
顯然邊權只有兩種,正的和0
如果一個環上有一條邊是正的,那麼整個環就是正環
如果沒有,那麼整個環的值應當相同
將環的結論拓展到SCC仍然適用
於是我們可以求出所有SCC,若SCC記憶體在某正邊,直接輸出-1
若不存在,可將SCC縮為一個點共同處理
縮點之後為一個拓撲圖
我們可以每次找入度為0的點到其他點更新最長路,因為這個點已經不能被其他點更新了
至此我們解決了這個問題
那麼這個思想可不可以應用到其他查分約束系統呢
答案是未必
只有邊權全為非負的最長路和邊權全為非正的最短路可以,而一般的差分約束並不保證這一點

程式碼

#include<stack> 
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100006
#define long long long
struct edge
{
    int v;
    int next;
    int w;
    edge(int v,int n,int w):v(v),next(n),w(w){}
    edge(){}
}e[maxn*3],e2[maxn*3];
int n,m;
int newedge,dfsclock,newcolor;
int ind[maxn],ind2[maxn];
bool flag=true;
void addedge(edge *e,int *ind,int u,int v,int a)
{
    //if(e-e2==0)printf("edge:%d %d %d\n",u,v,a);
    e[++newedge]=edge(v,ind[u],a);
    ind[u]=newedge;
}
long d[maxn];
int dfn[maxn],low[maxn];
int deg[maxn];
int color[maxn];
int s[maxn];
stack<int> stk;
queue<int> que;
void dfs(int u)
{
    dfn[u]=low[u]=++dfsclock;
    stk.push(u);
    for(int i=ind[u];i;i=e[i].next)
    {
        int v=e[i].v;
        if(!dfn[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else
            if(!color[v])
                low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        ++newcolor;
        s[newcolor]=1;
        while(stk.top()!=u)
        {
            color[stk.top()]=newcolor;
            s[newcolor]++;
            que.push(stk.top());
            stk.pop();
        }
        color[u]=newcolor;
        que.push(u);
        stk.pop();
        while(!que.empty())
        {
            int t=que.front();
            for(int i=ind[t];i;i=e[i].next)
                if(color[e[i].v]==newcolor&&e[i].w==1)
                    flag=false;
            que.pop();
        }
    }
    return;
}           
long ans;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        addedge(e,ind,0,i,1);
    for(int i=1;i<=m;i++)
    {
        int x,a,b;
        scanf("%d%d%d",&x,&a,&b);
        switch(x)
        {
            case 1:
                addedge(e,ind,a,b,0);
                addedge(e,ind,b,a,0);
                break;
            case 2:
                addedge(e,ind,a,b,1);
                break;
            case 3:
                addedge(e,ind,b,a,0);
                break;
            case 4:
                addedge(e,ind,b,a,1);
                break;
            case 5:
                addedge(e,ind,a,b,0);
                break;
            default:puts("QAQ");
        }
    }
    dfs(0);
    if(!flag)
    {
        puts("-1");
        return 0;
    }
    newedge=0;
    for(int u=0;u<=n;u++)
        for(int i=ind[u];i;i=e[i].next)
        {
            if(color[u]==color[e[i].v])
                continue;
            addedge(e2,ind2,color[u],color[e[i].v],e[i].w);
            deg[color[e[i].v]]++;
        }
    que.push(color[0]);
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        for(int i=ind2[u];i;i=e2[i].next)
        {
            int v=e2[i].v;
            d[v]=max(d[u]+e2[i].w,d[v]);
            deg[v]--;
            if(!deg[v])
                que.push(v);
        }
    }
    for(int i=1;i<=newcolor;i++)
        ans+=(long)d[i]*s[i];
    printf("%lld",ans);
    return 0;
}