最小路径覆盖与最小链覆盖 Dilworth定理:最小链覆盖等于最长反链(详细证明与经典例题)
作者:互联网
一、最小路径覆盖
定义
最小路径覆盖就是指在有向无环图中,用最少的、不相交的简单路径覆盖图中的所有点。
解法
①将原图中的每个点拆点,(将点u拆成u与u+n);
②将原图中的每条边 <u,v> 在新图中建立对应的边 <u,v+n>;
③将点(1 ~ n)作为二分图的左部,将点(n+1 ~ 2*n)作为二分图的右部,进行二分图的最大匹配;
④所求的最少路径数等于总点数n 减去 最大匹配数。
//二分图匹配可以用匈牙利算法或者网络流即可。
二、最小链覆盖
定义
最小链覆盖和最小路径覆盖的区别是,最小链覆盖允许路径相交。
最小路径覆盖就是指在有向无环图中,用最少的、可以相交的简单路径覆盖图中的所有点。
解法
根据**Floyd传递闭包**的思想将图的连通性传递:
for(int k = 1;k <= n;k++)
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
mp[i][j] |= mp[i][k] & mp[k][j];
传递完之后,若邻接矩阵mp[u][v] == 1
说明可以从u点到达v点。
做完这一步之后,我们就将问题转化成了最小路径覆盖,n-最大匹配数就是所求答案。
三、链与反链
定义
链:一条链是一些点的集合,点集中任意两个点u和v,满足u能到达v或是v能到达u,则称该点集为一条链。
反链:一条反链是一些点的集合,点集中任意两个点u和v,满足u不能到达v并且v不能到达u,则称该点集为一条反链。
四、Dilworth定理
1、Dilworth定理
简单来说就是一个图的最小链覆盖等于最长反链长度。(百度百科根本看不懂)
2、证明
该证明比较复杂,详细证明可以看这篇文章。
五、经典例题
1、洛谷P1020 [NOIP1999 普及组] 导弹拦截
题目链接:P1020 [NOIP1999 普及组] 导弹拦截
思路
最多能拦截导弹数目就是求最长非递增子序列长度;而需要多少拦截系统就是求最少有多少段非递增子序列,而根据Dilworth定理他就等于最长上升子序列长度。
为什么呢,我们可以将序列中后者小于等于前者看作前者能到达后者,数学表达为对于序列
a
a
a,若
a
i
≥
a
j
(
i
<
j
)
a_i≥a_j(i < j)
ai≥aj(i<j),则i点可以到达j点,那么根据链的定义一个非递增子序列就可以看作一条链,而根据反链的定义上升子序列就是一条反链,根据Dilworth定理,最小链覆盖就等于最长反链长度,那么求最少有多少段非递增子序列就等于求最长上升子序列长度。
ac代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,a[N],f[N],g[N];
int main()
{
int res = 0;
while(scanf("%d", &a[n])!= EOF)
n++;
for(int i = 0; i < n; i ++ )
{
f[i] = 1;
for(int j = 0; j < i; j ++ )
if(a[j] >= a[i])
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
printf("%d\n", res); //求最长非递增子序列长度
int cnt = 0;
for(int i = 0; i < n; i ++ )
{
int k = 0;
while(k < cnt && g[k] < a[i])
k++;
g[k] = a[i];
if(k >= cnt)
cnt++;
}
printf("%d\n", cnt); //求最长上升子序列长度
}
2、Codeforce 1296 E
题目链接: Codeforce 1296 E
题目大意
给你一个字符串s,你可以用数字给s中的每个字符染色,规定只有当相邻两个字符颜色不同的时候才可以交换这两个字符。求最少需要多少种颜色使得字符串s可以经过多次交换后呈字典序从小到大的顺序排列,并输出染色结果。
思路
我们可以发现,当s的一个子序列是非递减子序列,那么他们已经按从小到大的顺序排列了,因此这个子序列的颜色可以相同(他们之间都不需要交换,已经有序了),而两个非递减子序列之间一定需要交换,颜色需要不同,因此我们就要找出可以将字符串s分成多少个最长非递减子序列,根据Dilworth定理,就是求最长反链长度,即最长下降子序列长度,就是所需颜色数,而构造每个字符的颜色就是当前以它结尾的字符串最长下降子序列的长度。
ac代码
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
const int N = 2e5+10;
typedef long long LL;
int n,maxn,cnt[200],ans[N]; //cnt[i]存的是以字母i结尾的最长下降子序列长度,ans存的是染色方案
string s;
int main()
{
cin >> n >> s;
for(int i = 0;i < s.size();i++) //循环每个字符
{
int t = 0;
for(int j = s[i]+1;j <= 'z';j++) //循环比当前字母大的所有字母,因为当前字母只能加到比他大的字母后面,这样才是下降子序列
t = max(t,cnt[j]); //找出最长的一个
cnt[s[i]] = t+1; //每次都加给最长的下降子序列,则以当前字符结尾的长度是t加上1
ans[i] = t+1; //染色方案也就是以当前字母为结尾的最长下降子序列长度
maxn = max(maxn,t+1); //存一个最大值
}
cout << maxn << endl;
for(int i = 0;i < n;i++)
cout << ans[i] << " ";
return 0;
}
标签:反链,覆盖,int,最小链,序列,例题,最长 来源: https://blog.csdn.net/qq_45735851/article/details/122143331