前言:昨天膜你赛t2中考到了dilworth定理,感觉有一点点奇妙,所以简单学习了一下
1.引入:首先回顾昨天膜你赛t2
题目大意是给定n个二元组(x,y),对于x1<=x2且y1<=y2的两个二元组可分为一堆,问最少分多少堆
思路:将所有二元组按x从大到小排序后,那么只要看y最小能划分成多少个不上升子序列即可
问题来了,怎么求出最小划分数呢?
——Dilworth定理闪亮登场
2.什么是dilworth定理?
狄尔沃斯定理(Dilworth's theorem)亦称偏序集分解定理,是关于偏序集的极大极小的定理,该定理断言:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目,由偏序集P按如下方式产生的图G称为偏序集的可比图:G的节点集由P的元素组成,而e为G中的边,仅当e的两端点在P中是可比较的,有限全序集的可比图为完全图(来自百度百科)
(可以简易理解为:最小划分数=最长反链长度)
证明:
定理 对偏序集<A,≤>,设A中最长链的长度是n,则将A中元素分成不相交的反链,反链个数至少是n。
证明施归纳于n。
当n=1时,A本身就是一条反链,定理结论成立。(这时≤是恒等关系)。
假设对于n=k,结论成立。考虑n=k+1的情况,当A中最长链的长度为k+1时,令M为A中极大元的集合,显然M是一条反链。而且A-M中最长链的长度为k。由归纳假设,可以把A-M分成至少k个不相交的反链,加上反链M,则A可分成至少k+1条反链。(来自百度百科)
所以,对于刚才那道题,只需求最长上升子序列即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,f[100005],a[100005],len,x[100005],xx,yy,cnt;
struct node{int x,y;}b[100005];
bool cmp(node a,node b){
if(a.x!=b.x)return a.x>b.x;
else return a.y>b.y;
}
bool vis[1005][1005];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&xx,&yy);
if(!vis[xx][yy]){
b[i].x=xx;b[i].y=yy;
vis[xx][yy]=1;
cnt++;
}
}n=cnt;
sort(b+1,b+1+n,cmp);
for(int i=1;i<=n;i++)a[i]=b[i].y;
for(int i=1;i<=n;i++){
if(len==0||a[i]>x[len]) x[++len]=a[i];
else{
int l=1,r=len;
while(l<r){
int mid=(l+r)>>1;
if(a[i]<=x[mid]) r=mid;
else l=mid+1;
}
x[l]=a[i];
}
}
printf("%lld\n",len);
return 0;
}
3.典型例题
(1) [USACO14DEC]Cow Jog G - 洛谷
题目大意:给n只奶牛,给定初始位置(各不相同且保证升序输入),每只奶牛速度,求最少安排多少跑道保证不会发生超车
分析:首先,由于初始位置各不相同且升序,若t时间后结束位置有逆序,说明过程中存在超车,故此题的实质是要求对于结束位置的最小上升子序列划分数,根据dilworth定理,可以转化为求最长不上升子序列
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[1000005],c[1000005],len,p,n,t,v;
signed main(){
scanf("%lld%lld",&n,&t);
for(int i=1;i<=n;i++)scanf("%lld%lld",&p,&v),a[i]=p+v*t;
for(int i=n;i>=1;i--){
if(len==0||a[i]>=c[len])c[++len]=a[i];
else{int t=upper_bound(c+1,c+len+1,a[i])-c;
c[t]=a[i];
}
}
printf("%lld\n",len);
return 0;
}
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[1005][1005],f[1005][1005],t,n,m;
signed main(){
scanf("%d",&t);
while(t--){
memset(f,0,sizeof(f));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
f[i][j]=max(max(f[i-1][j],f[i][j+1]),f[i-1][j+1]+a[i][j]);
}
}
printf("%lld\n",f[n][1]);
}
return 0;
}