2788: [Poi2012]Festival
Time Limit: 30 Sec Memory Limit: 64 MB
Description
有n個正整數X1,X2,…,Xn,再給出m1+m2個限制條件,限制分為兩類:
1. 給出a,b (1<=a,b<=n),要求滿足Xa + 1 = Xb
2. 給出c,d (1<=c,d<=n),要求滿足Xc <= Xd
在滿足所有限制的條件下,求集合{Xi}大小的最大值。
Input
第一行三個正整數n, m1, m2 (2<=n<=600, 1<=m1+m2<=100,000)。
接下來m1行每行兩個正整數a,b (1<=a,b<=n),表示第一類限制。
接下來m2行每行兩個正整數c,d (1<=c,d<=n),表示第二類限制。
Output
一個正整數,表示集合{Xi}大小的最大值。
如果無解輸出NIE。
Sample Input
4 2 2
1 2
3 4
1 4
3 1
Sample Output
3
HINT
|X3=1, X1=X4=2, X2=3
這樣答案為3。容易發現沒有更大的方案。
思路:
跑多個式子的可行解考慮差分限制。
把Xa+1=Xb轉化為Xa<=Xb-1,Xb<=Xa+1,建a到b邊權為1的邊,b到a權值為-1的邊。我們可以把它當做雙向邊(1類邊)。
把Xc<=Xd轉化為 Xc-Xd<=0,建一條d到c邊權為0的有向邊。我們可以把它當做單向邊(2類邊)。
建圖之後圖中可能會有強聯通分量。
那麼連接配接強聯通分量的邊不可能是1類邊(不然就聯通起來了)是以隻可能是A<=B(2類邊)。
那麼隻要保證A點所在強聯通分量中的最大值小于B點所在強聯通分量中的最小值,就可以使不同權值最多。(沒有重複)
那麼我們隻需要對每個強聯通求出答案再累加起來就好了。
如果強聯通分量中有2類邊,那麼它們一定是在一個環中的(不然就不是強連通了),由于它們是互相<=的(循環小于等于),那麼隻有當它們都是同一個權值是才有可能。
由于圖中隻有-1,0,1三種邊,是以每個強聯通分量中,權值種類最多就是最長路的絕對值的最大值+1。(最長路的兩端一定是這個強連通分量裡面的最大及最小值,因為如果不是的話,每兩個點之間又是可以互相到達的,那麼這就一定不是一條最長路。)
是以上面說的環的最長路跑出來是0。也是滿足條件的,不會影響我們所求的答案(最長路上的邊一定是1類邊)。
因為點數n<=600那麼floyd是可以的。(因為要限制隻在一個強連通分量裡面跑,是以選用枚舉方式的floyd應該是很好寫的)
怎麼判無解呢?就是當出現正權環的時候。我們要求的是最長路,如果出現了正權環,那麼我們就可以一直跑這個環,我們的ans就可以無限變大,而這肯定是不合法的(和求最短路判負環一樣)
如何判斷正權環呢?
隻需要初始化把dis[i][i]=0,跑完floyd後看是否還是0即可。
#include <cmath>
#include <stack>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1000
#define M 400010
#define INF 0x3f3f3f3f
using namespace std;
int n, m1, m2, idc, idx, top, cnt;
int head[N], dfn[N], low[N], ins[N], vis[N], place[N];
int dis[N][N];
stack <int> state;
struct Edge{
int from, to, next;
}ed[M];
void init(){
memset(head, , sizeof(head));
idc = ;
}
void adde(int u, int v){
ed[++idc].from = u;
ed[idc].to = v;
ed[idc].next = head[u];
head[u] = idc;
}
void tarjan(int u){
dfn[u] = low[u] = ++idx;
vis[u] = ins[u] = ;
state.push(u);
for(int i=head[u]; i; i=ed[i].next){
int v = ed[i].to;
if( !vis[v] ){
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if( ins[v] ){
low[u] = min(low[u], dfn[v]);
}
}
if(dfn[u] == low[u]){
cnt++; int t=-;
while(t != u){
t = state.top();
place[t] = cnt;//記錄強連通分量
ins[t] = ;
state.pop();
}
}
}
int main(){
init();
scanf("%d%d%d", &n, &m1, &m2);
for(int i=; i<=n; i++)
for(int j=; j<=n; j++)
dis[i][j] = -INF;
for(int i=; i<=n; i++) dis[i][i] = ;
for(int i=; i<=m1; i++){
int a, b;
scanf("%d%d", &a, &b);
adde(a, b); adde(b, a);
dis[a][b] = max(dis[a][b], );
dis[b][a] = max(dis[b][a], -);
//a+1=b -> a+1<=b&&a+1>=b -> a<=b-1&&b<=a+1 -> ab.w=1&&ba.w=-1
//有沖突情況時,不覆寫,取max讓dis[i][i]不為0
//eg:a=b+1&&b=a+1 -> dis[a][b]=dis[b][a]=1 -> dis[a][a]=dis[b][b]=2(更新後)
}
for(int i=; i<=m2; i++){
int a, b;
scanf("%d%d", &a, &b);
adde(a, b);
dis[a][b] = max(dis[a][b], );//b>=a -> a<=b+0
}
for(int i=; i<=n; i++){
if( !dfn[i] ) tarjan(i);
}
int ans = ;
for(int cc=; cc<=cnt; cc++){//枚舉每個強連通分量
int ret = ;
for(int k=; k<=n; k++){//FLOYD
if(place[k] != cc) continue;
for(int i=; i<=n; i++){
if(place[i] != cc || dis[i][k] == -INF) continue;
for(int j=; j<=n; j++){//枚舉任意兩個點
if(place[j] != cc || dis[k][j] == -INF) continue;
dis[i][j] = max(dis[i][j], dis[i][k] + dis[k][j]);//更新最長路
}
}
}
for(int i=; i<=n; i++){
if(place[i] != cc) continue;
for(int j=; j<=n; j++){
if(place[j] != cc) continue;
ret = max(ret, abs(dis[i][j]));//記錄最長路的max
}
}
ans += ret + ;//完成一個強連通分量的計算
}
for(int i=; i<=n; i++)
if(dis[i][i] != ){
puts("NIE");
return ;
}//有正權環,無解
printf("%d\n", ans);
return ;
}