• Dilworth定理


    学习的是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 }
    View Code

    洛谷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 }
    View Code
  • 相关阅读:
    07.C#泛型的限制和可空类型的简单说明(三章3.5-四章4.1)
    列表的相关操作和方法/深浅拷贝
    字符串的格式化format和字符串相关函数
    for循环和关键字
    双项循环经典题
    python流程控制
    python运算符
    容器类型的强制转换和字典强转
    python强制类型转换和自动类型转换
    Javascript 执行环境及作用域
  • 原文地址:https://www.cnblogs.com/clno1/p/11656987.html
Copyright © 2020-2023  润新知