两个做法
1.二分图式
我觉得应该评黑 真的
这个思路转换挺神奇的
这里不会多讲 题解理论过程粉兔说得很详细 代码Cyhlnj比较好看 思路zxyoi较清晰
我只讲一下代码中比较容易看不懂的地方
先floyd传递闭包一下得到每个点能到的点集
第一问 Dilworth定理 最长反链=最小链覆盖
第二问 最长反链=最大独立集=n-最小覆盖点集=最大匹配
这个可以看luogu粉兔+ 二分图的最小顶点覆盖 最大独立集 最大团
面对疾风吧 没学过这个可能要半h+
第三问 去掉当前点i与其能到达与能被到达的点判断一下剩下的图的最长反链是否等于原图最长反链-1
code:
#include<bits/stdc++.h>
using namespace std;
int match[110],to[110]; //前驱 后继
bool s[110],t[110]; //出 入
bool mp[110][110]; //全图
bool e[110][110]; //剩余图
int ban[110]; //删的点
int vis[110]; //该轮增广到达过的点
int n,m,ind,ans; //n m 轮数 答案
bool find(int u){
if(ban[u]) return 0;
for(int i=1;i<=n;i++)
if(e[u][i]&&ban[i]==0&&vis[i]!=ind){
vis[i]=ind;
if(match[i]==0||find(match[i])){
match[i]=u;
to[u]=i;
return true;
}
}
return false;
}
void dfs(int u){ //模拟找最小覆盖点集过程
if(s[u]) return;
s[u]=true;
for(int i=1;i<=n;i++)
if(e[u][i]&&t[i]==0){
t[i]=true;
dfs(match[i]);
}
}
int main(){
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
mp[x][y]=true; //单向边
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
mp[j][k]|=mp[j][i]&mp[i][k]; //
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
e[i][j]=mp[i][j]; ans=n;
for(ind=1;ind<=n;ind++)
ans-=find(ind);
printf("%d\n",ans);
for(int i=1;i<=n;i++)
if(to[i]==0) dfs(i);
for(int i=1;i<=n;i++)
printf("%d",s[i]&&!t[i]);//这里已经取反 原来求最小点集覆盖时是!s[i]||t[i] 即左端标记过的点与右端未标记的点
printf("\n");
int can=0,sum=0;
for(int i=1;i<=n;i++){
memset(e,0,sizeof(e));
memset(to,0,sizeof(to));
memset(ban,0,sizeof(ban));
memset(match,0,sizeof(match));
int res=0;
for(int j=1;j<=n;j++){
if(mp[j][i]||mp[i][j]||i==j) ban[j]=1; //可达与可被达统统删掉
else res++;
}
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
if(ban[j]==0&&ban[k]==0)
e[j][k]=mp[j][k]; //删掉后的图
for(int j=1;j<=n;j++)
if(ban[j]==0){
ind++;
res-=find(j);
}
printf("%d",res==ans-1); //res+1=ans
}
}
2.网络流
奇妙的方法增加了!
拆个点 X o u t X_{out} Xout连 Y i n Y_{in} Yin S连 X o u t X_{out} Xout Y i n Y_{in} Yin连T 流量全为1 ans=n-maxflow!
代码康康这个
其实跟最小割没关系⑧ 为什么说最小割
这玩意中间的边流量为1 最小割也是神奇
虽然对于这个题因为m>=n可以
但实际上是一个最小链覆盖的过程:
看着这个图 你发现了什么
没错 你什么也发现不了
我们研究流量究竟会对图产生什么影响
可以看出 我们 X o u t X_{out} Xout与 Y i n Y_{in} Yin的边流1个单位(就满了) 相当于原图X流水到Y 即标记Y为不可用
而每个 X o u t X_{out} Xout只有一个流是限制它的最小链覆盖 只能流一个儿子即一条链
很容易发现 我们这样做会导致除了每个链头没有流 其他点都有流
当我们做到最大流时 完成了链头最少 即最小链覆盖 那么ans=n-maxflow
注意这里是最长反链=最小链覆盖 这里求的是链覆盖的链头而非最长反链方案
第三问还是那样 现在就求一个第二问
奇妙up!
还有一种第二问的写法
这是某csp500+ 省队预定syh靠直觉想出来的奇妙做法
我们先处理第三问 再做第二问
现在我们知道了哪些点可用作祭坛了
我们暴力找到第一个可用点 然后删掉它能达到和能被达到的点 加入方案
然后再找下一个可用点 重复操作
直到没有(
这就是方案!
下面是我的证明:
假设最长反链点数为3 第一个选到的可用点为a 那么必有一组答案a,b,c
在删掉a与其能达到和能被达到的点后 若下一个选的可用点不是b 假设为d
那么d必在b或c的覆盖区域内(a,b,c覆盖区域可重叠)
这里注意覆盖是指能达到和能被达到的点
-
若d在b内
若b->d 因为c流不到b 那么 d->b 出现环 不可能
若d->b 那我们选d相当于选b 对答案无影响
对于样例,就是预设方案为a=1,b=4时,用4能被达的点3替换4对答案无影响 -
若d在c内
-
若c->d,d->b 则c->b 与abc方案矛盾
-
若d->c,b->d 则b->c 与abc方案矛盾
-
若c->d,b->d或d->c,d->b
因为d是可取点 那么有两种情况
一.ade组成一个方案 a,d已经确定 问题转化为e与另一个可用点f的问题 即f在e内 用f替换e 与d在b内类似
二.含d的方案不包括a 因为d不可能完全覆盖b 则必有e覆盖b剩余部分
-
故上述解法正确
说真的这个过程很像第三问 只是需要预知可用点
code: 来自@suyiheng
//前面与2一样 省略
for(int i=1; i<=n; i++)k[i]=check(i);
for(int i=1; i<=n; i++) {
b[i]=k[i];
for(int j=1; j<i; j++) {
if(f[i][j]&&b[j]) {
b[i]=0;
break;
}
if(f[j][i]&&b[j]) {
b[i]=0;
break;
}
}
}
for(int i=1; i<=n; i++) {
if(b[i])printf("1");
else printf("0");
}
printf("\n");
for(int i=1; i<=n; i++) {
if(k[i])printf("1");
else printf("0");
}
printf("\n");
这个也可以替换二分图的第二问啦
这样做法就亲民多了~