题目描述
在遥远的东方,有一个神秘的民族,自称Y族。他们世代居住在水面上,奉龙王为神。每逢重大庆典, Y族都会在水面上举办盛大的祭祀活动。我们可以把Y族居住地水系看成一个由岔口和河道组成的网络。每条河道连接着两个岔口,并且水在河道内按照一个固定的方向流动。显然,水系中不会有环流,由于人数众多的原因,Y族的祭祀活动会在多个岔口上同时举行。出于对龙王的尊重,这些祭祀地点的选择必须非常慎重。准确地说,Y族人认为,如果水流可以从一个祭祀点流到另外一个祭祀点,那么祭祀就会失去它神圣的意义。族长希望在保持祭祀神圣性的基础上,选择尽可能多的祭祀的地点。
输入输出格式
输入格式:
第一行包含两个用空格隔开的整数N、M,分别表示岔口和河道的数目,岔口从1到N编号。
接下来M行,每行包含两个用空格隔开的整数u、v,描述一条连接岔口u和岔口v的河道,水流方向为自u向v。
输出格式:
第一行包含一个整数K,表示最多能选取的祭祀点的个数。 接下来一行输出一种可行的选取方案。对于每个岔口依次输出一个整数,如果在该岔口设置了祭祀点,那么输出一个1,否则输出一个0。应确保你输出的1 的个数最多,且中间没有空格。 接下来一行输出,在选择最多祭祀点的前提下,每个岔口是否能够设置祭祀点。对于每个岔口依次输出一个整数,如果在该岔口能够设置祭祀点,那么输出一个 1,否则输出一个0。 注意:多余的空格和换行可能会导致你的答案被判断为错误答案。
输入输出样例
输入样例#1:
4 4
1 2
3 4
3 2
4 2
输出样例#1:
2
1010
1011
说明
N ≤ 100 M ≤ 1 000
在样例给出的水系中,不存在一种方法能够选择三个或者三个以上的祭祀点。包含两个祭祀点的测试点的方案有两种:
选择岔口1与岔口3(如样例输出第二行),选择岔口1与岔口4。
水流可以从任意岔口流至岔口2。如果在岔口2建立祭祀点,那么任意其他岔口都不能建立祭祀点但是在最优的一种祭祀点的选取方案中我们可以建立两个祭祀点,所以岔口2不能建立祭祀点。对于其他岔口至少存在一个最优方案选择该岔口为祭祀点,所以输出为1011。
题解
这似乎算是一道结论题?
首先这道题就是让我们求一条最长的反链
反链就是一个点的集合,在这个集合中任意两点不互相到达
那么有一个定理
最长反链 = 最小链覆盖
然后最小链覆盖就是最小路径覆盖
那么我们这题就先传递闭包一波然后当成最小路径覆盖做就好了
最小路径覆盖
就是把每个点拆成两个点跑最大流
因为每次增广就相当于把两条路径给合并起来
所以最大流的增广次数就是路径的合并次数
答案就是点数-最大流
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 210 ;
const int INF = 1e9 ;
using namespace std ;
inline int read() {
char c = getchar() ; int x = 0 , w = 1 ;
while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; }
return x*w ;
}
bool f[M][M] , sit[M] ;
int n , m , num = 1 , hea[M] ;
int S , T , Ans , d[M] ;
struct E {
int Nxt , to , dis ;
} edge[M * M << 1] ;
inline void add_edge(int from , int to , int dis) {
edge[++num].Nxt = hea[from] ; edge[num].to = to ;
edge[num].dis = dis ; hea[from] = num ;
}
inline void Insert(int u , int v , int w) {
add_edge(u , v , w) ;
add_edge(v , u , 0) ;
}
inline bool Bfs() {
queue < int > q ; q.push(S) ;
memset(d , 0 , sizeof(d)) ; d[S] = 1 ;
while(!q.empty()) {
int u = q.front() ; q.pop() ;
for(int i = hea[u] ; i ; i = edge[i].Nxt) {
int v = edge[i].to ;
if(!d[v] && edge[i].dis) {
d[v] = d[u] + 1 ;
q.push(v) ;
}
}
}
return d[T] ;
}
int Dfs(int u , int dis) {
if(u == T || !dis) return dis ;
int sum = 0 ;
for(int i = hea[u] ; i ; i = edge[i].Nxt) {
int v = edge[i].to ;
if(d[v] == d[u] + 1 && edge[i].dis) {
int diss = Dfs(v , min(dis , edge[i].dis)) ;
if(diss > 0) {
edge[i].dis -= diss ; edge[i ^ 1].dis += diss ;
sum += diss ; dis -= diss ;
if(!dis) break ;
}
}
}
if(!sum) d[u] = -1 ;
return sum ;
}
inline int Dinic() {
Ans = 0 ;
while(Bfs())
Ans += Dfs(S , INF) ;
return Ans ;
}
inline void Rebuild(int u) {
for(int i = 1 ; i <= n ; i ++) {
if(i == u) continue ;
Insert(S , i , 1) ;
Insert(n + i , T , 1) ;
}
for(int i = 1 ; i <= n ; i ++) {
if(i == u) continue ;
for(int j = 1 ; j <= n ; j ++){
if(!f[i][j] || j == u || i == j) continue ;
Insert(i , n + j , 1) ;
}
}
}
int main() {
n = read() ; m = read() ; S = 0 , T = n * 2 + 5 ;
for(int i = 1 ; i <= n ; i ++) f[i][i] = true ;
for(int i = 1 , u , v ; i <= m ; i ++) {
u = read() , v = read() ;
f[u][v] = true ;
}
for(int k = 1 ; k <= n ; k ++)
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= n ; j ++)
f[i][j] |= (f[i][k] & f[k][j]) ;
for(int i = 1 ; i <= n ; i ++) {
Insert(S , i , 1) ;
Insert(n + i , T , 1) ;
}
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= n ; j ++)
if(f[i][j] && i != j)
Insert(i , n + j , 1) ;
int tmp = n - Dinic() ;
printf("%d
",tmp) ;
return 0 ;
}