【BZOJ2330】【SCOI2011】糖果——差分約束系統+tarjan
阿新 • • 發佈:2019-01-23
題目連結
差分約束
這是一道經典的差分約束問題
我們假設最後第i個小朋友分得的糖果數為
那麼對於約束條件:i分得的糖果少於j的,有
同樣,對於
這些條件是不是很像最短路的最終狀態中有
於是我們可以將問題轉換成最短路/最長路求解
如果存在一個符合要求的最短路(最長路)的解,那麼每個點的最短路就是分配的糖果數。而如果因為出現負環沒有最短路(出現正環沒有最長路),那麼原問題無解
如何保障所有的 0
最短路?最長路?
不難發現,以上兩個限制也可以寫成:
這樣就符合了最長路的限制
那麼最短路和最長路,使用哪一種都可以,沒有區別嗎?
其實不然,因為最短路可以最大化
為什麼呢?因為最短路使得每一個點的權值都是由一條邊更新來,而且這個點的權值無法再大,否則就會不符合該邊限制。最長路則正好相反,使得所有點權值無法再小。
這題我們要最小化
SPFA做法的缺陷
因為需要判環,考慮通過限制SPFA中節點入隊次數來判
每個節點最多入隊n次,若環長為n,環上每個節點都會入隊O(n)次,總體複雜度 (N2)
因此我們需要考慮更高效的判環方法
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;
}