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
1 2
3 4
1 4
3 1
Sample Output
HINT
X3=1, X1=X4=2, X2=3
这样答案为3。容易发现没有更大的方案。
分析:
看到这两条限制
- 给出a,b (1<=a,b<=n),要求满足Xa + 1 = Xb
- 给出c,d (1<=c,d<=n),要求满足Xc <= Xd
反应出差分约束的模型
Xa+1=Xb:
X
a
+
1
=
X
b
:
Xa−(1)−>Xb,Xb−(−1)−>Xa
X
a
−
(
1
)
−
>
X
b
,
X
b
−
(
−
1
)
−
>
X
a
Xc<=Xd:
X
c
<=
X
d
:
Xd−(0)−>Xc
X
d
−
(
0
)
−
>
X
c
写完了朴素的差分约束,我发现了一个问题:
我们之所以把差分约束问题转化成最短路,是为了使每个数的取值在符合条件下尽量小
但是题目要求:在满足所有限制的条件下,求集合{Xi}大小的最大值(也就是说要使出现的数值尽量多)
这个不是一遍spfa可以解决的
怎么破?
我们先用tarjan缩点,得到一个DAG
显然DAG的点之间边都是单向的,一定是第二种限制条件的边
那么我们就能保证存在一组解使
SCC1,SCC2
S
C
C
1
,
S
C
C
2
的点权值不重复,最终的答案就是每个强连通分量的答案之和
那么现在我们只需要计算强连通量内的答案啦
因为边权的绝对值都是1,且权值非负
所以不同数值的数量就是:环内最大距离+1
因为是环内最大距离,所以我们要求的是最大路,建图也要变一变
Xa+1=Xb:
X
a
+
1
=
X
b
:
Xa−(1)−>Xb,Xb−(−1)−>Xa
X
a
−
(
1
)
−
>
X
b
,
X
b
−
(
−
1
)
−
>
X
a
Xc<=Xd:
X
c
<=
X
d
:
Xc−(0)−>Xd
X
c
−
(
0
)
−
>
X
d
由于数据范围较小,用floyed计算最长路即可
tip
注意可能会有重边,所以 dis[x][y] d i s [ x ] [ y ] 要取max
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
const int INF=0x33333333;
const int N=605;
int n,m1,m2,st[N],tot=0,dis[N][N],ans;
int dfn[N],low[N],clo=0,cnt=0,belong[N],s[N],top=0;
bool vis[N];
struct node{
int y,nxt;
};
node way[200010];
void add(int u,int w) {
tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
}
void tarjan(int now) {
dfn[now]=low[now]=++clo;
s[++top]=now;
vis[now]=1;
for (int i=st[now];i;i=way[i].nxt)
if (!dfn[way[i].y]) {
tarjan(way[i].y);
low[now]=min(low[now],low[way[i].y]);
}
else if (vis[way[i].y])
low[now]=min(low[now],dfn[way[i].y]);
if (dfn[now]==low[now]) {
cnt++;
int x=-1;
while (x!=now) {
x=s[top--];
belong[x]=cnt;
vis[x]=0;
}
}
}
int main()
{
scanf("%d%d%d",&n,&m1,&m2);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i!=j) dis[i][j]=-INF;
else dis[i][j]=0;
for (int i=1;i<=m1;i++) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
dis[x][y]=max(dis[x][y],1);
dis[y][x]=max(dis[y][x],-1);
}
for (int i=1;i<=m2;i++) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
dis[x][y]=max(dis[x][y],0);
}
for (int i=1;i<=n;i++)
if (!dfn[i])
top=0,tarjan(i); //tarjan缩点
for (int o=1;o<=cnt;o++) {
for (int k=1;k<=n;k++) if (belong[k]==o)
for (int i=1;i<=n;i++) if (belong[i]==o&&dis[i][k]!=-INF)
for (int j=1;j<=n;j++) if (belong[j]==o&&dis[k][j]!=-INF)
dis[i][j]=max(dis[i][j],dis[i][k]+dis[k][j]);
int now=0;
for (int i=1;i<=n;i++) if (belong[i]==o)
for (int j=1;j<=n;j++) if (belong[j]==o)
now=max(now,abs(dis[i][j]));
ans+=now+1;
}
for (int i=1;i<=n;i++)
if (dis[i][i]!=0) //存在环
{
printf("NIE\n");
return 0;
}
printf("%d\n",ans);
return 0;
}