学习的是https://blog.csdn.net/qq_34564984/article/details/52993626这篇博客的。
简单来说就两个定理,在严格偏序关系(DAG)中:
①最长反链=最小链覆盖 (即可相交的最小路径覆盖)
②最长链=最小反链覆盖
证明就略去了。
洛谷P3974 [TJOI2015]组合数学
题意:在n*m的网格图上,一条路从左上走到右下可以有多条路,一条路的代价是路上的格子最大值。问每个点都至少被经过一次的走的代价最小。
解法:仔细思考可以把题目抽象成:每条链就是从左上走到右下,链的代价是路上最大值,那么答案就是求从左上到右下的最小链覆盖。
根据Dilworth定理,最小链覆盖=最长反链。那么此题的最长反链就是从左下走到右上的路!!并且相邻两点不能同行同列!!那么我们怎么求这个最长反链呢?
直接从左下到右上做一次DP即可,dp[i][j]代表从左下到右上的最长链,那么dp[i][j]=max(dp[i+1][j-1]+a[i][j],max(dp[i+1][j],dp[i][j-1])) ; (注意这里只有从左下来才能加上a[i][j]的值,从左边和下边来不能加)。
DP完了之后dp[1][m]就是答案。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e3+10; 4 typedef long long LL; 5 int n,m,a[N][N]; 6 LL dp[N][N]; 7 8 int main() 9 { 10 int T; cin>>T; 11 while (T--) { 12 scanf("%d%d",&n,&m); 13 for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) scanf("%d",&a[i][j]); 14 for (int i=0;i<=n+1;i++) for (int j=0;j<=m+1;j++) dp[i][j]=0; 15 16 for (int i=n;i;i--) 17 for (int j=1;j<=m;j++) 18 dp[i][j]=max(dp[i+1][j-1]+a[i][j],max(dp[i+1][j],dp[i][j-1])); 19 printf("%lld ",dp[1][m]); 20 } 21 return 0; 22 }
洛谷P4934 礼物
题意:有n个礼物,每个礼物有一个魔力值ai,ai两两互不相同。如果两个礼物ai,aj的魔力值满足ai&aj>=min(ai,aj),那么这两个礼物不能放在一个箱子里。现在他请求你帮助他合理分配,用尽可能少的箱子装下所有礼物。换言之,使得每个礼物都被恰好装入一个箱子中,且同一个箱子中的礼物魔力不会相互抵消。如果有多种合法的方案,你只需要给出任意一种。
解法:我们观察ai&aj>=min(ai,aj),意思是ai和aj有包含关系,那么意思就是如果ai和aj有包含关系的是,它们就不能放到同一个箱子里。那么我们考虑如果ai€aj,那么ai向aj连边。那么题目变成在一个DAG上,任两个同一条边的两个礼物不能放到同一个箱子里,问最少要几个箱子装完这些礼物?
这不就是DAG上的最短反链!!! 最短反链并不好求,那么根据Dilworth定理 最短反链=最长链 。我们变成求DAG上最长链的长度,这个比较容易可以用拓扑排序求。
那么我们暴力n^2连边建图DAG,然后拓扑排序求最长链。这样会获得TLE,我们得优化。
我们分析主要得时间浪费在建图上,那么我们考虑进一步优化建图:这次我们将边的数量限制在O(nk)。考虑从每一个魔力值对应的集合,只连向多一个元素到达的集合,例如从0000连向1000, 0100, 0010, 0001四个位置。这样图的结构仍然是对的。但很多节点都是无用的了。那么只需要在原来的算法上做一些改动——对于有用点,该点权值为1;对于无用点,该点权值为0。
到此我们终于获得AC了。
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<vector> 5 using namespace std; 6 const int N=1000000+10; 7 const int M=(1<<20)+100; 8 const int INF=0x3f3f3f3f; 9 int n,k; 10 int a[N],v[M],indeg[M],d[M]; 11 int cnt=0,head[M],nxt[M*20],to[M*20]; 12 queue<int> q; 13 vector<int> ans[M]; 14 15 void add_edge(int x,int y) { 16 nxt[++cnt]=head[x]; to[cnt]=y; head[x]=cnt; 17 } 18 19 int main() 20 { 21 scanf("%d%d",&n,&k); 22 for (int i=1;i<=n;i++) scanf("%d",&a[i]); 23 for (int i=0;i<=(1<<k)-1;i++) 24 for (int j=0;j<k;j++) 25 if ( !(i&(1<<j)) ) { 26 add_edge(i,i|(1<<j)); 27 indeg[i|(1<<j)]++; 28 } 29 for (int i=1;i<=n;i++) v[a[i]]=1; 30 31 q.push(0); d[0]=v[0]; 32 int maxdep=-INF; 33 while (!q.empty()) { 34 int x=q.front(); q.pop(); 35 for (int i=head[x];i;i=nxt[i]) { 36 int y=to[i]; 37 d[y]=max(d[x]+v[y],d[y]); 38 maxdep=max(maxdep,d[y]); 39 if (--indeg[y]==0) q.push(y); 40 } 41 } 42 43 printf("1 %d ",maxdep); 44 for (int i=0;i<=(1<<k)-1;i++) 45 if (v[i]) ans[d[i]].push_back(i); 46 for (int i=1;i<=maxdep;i++) { 47 printf("%d ",ans[i].size()); 48 for (int j=0;j<ans[i].size();j++) printf("%d ",ans[i][j]); 49 printf(" "); 50 } 51 return 0; 52 }