1.单调栈
相关:
给定序列a[],最少用多少个上升子序列列可以覆盖它?
答案等于a[]的最上不上升子序列的长度
给定序列a[],最少修改多少个位置可以令其变成上升序列
解法:令a_[i] = a[i] - i,对 a_[i] 求最长上升子序列,可以得到最多有多少个位置保持不变
a[ ]1 5 3 2 7
a_[ ]0 3 0 -2 2
对于相邻两个合法的点x,y;设其中间有k个空隙
y-x-1=k
a[y]-a[x]>k
a[y]-a[x]>=k+1
即a[y]-a[x]>=y-x
整理a[y]-y>=a[x]-x
2.LIS
O(n^2)朴素
#include <iostream> #include <cstdio> #include <algorithm> #include <cstdlib> #include <cstring> #include <cmath> using namespace std; const int maxn = 103,INF=0x7f7f7f7f; int a[maxn],f[maxn]; int n,ans=-INF; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); f[i]=1; } for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(a[j]<a[i]) f[i]=max(f[i],f[j]+1); for(int i=1;i<=n;i++) ans=max(ans,f[i]); printf("%d\n",ans); return 0; }
O(nlogn) 二分插入(贪心)
#include<cstdio> #include<algorithm> using namespace std; const int MAXN=200001; int a[MAXN],f[MAXN]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); f[1]=a[1]; int len=1; for(int i=2;i<=n;i++) { if(a[i]>f[len]) f[++len]=a[i]; else{ int j=lower_bound(f+1,f+len+1,a[i])-f; f[j]=a[i]; } } printf("%d\n",len); return 0; }
树状数组维护最大值
#include <iostream> #include <cstdio> #include <algorithm> #include <cstdlib> #include <cstring> #include <cmath> using namespace std; const int maxn =103,INF=0x7f7f7f7f; struct Node{ int val,num; }z[maxn]; int T[maxn]; int n; bool cmp(Node a,Node b) { return a.val==b.val?a.num<b.num:a.val<b.val; } void modify(int x,int y)//把val[x]替换为val[x]和y中较大的数 { for(;x<=n;x+=x&(-x)) T[x]=max(T[x],y); } int query(int x)//返回val[1]~val[x]中的最大值 { int res=-INF; for(;x;x-=x&(-x)) res=max(res,T[x]); return res; } int main() { int ans=0; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&z[i].val); z[i].num=i;//记住val[i]的编号,有点类似于离散化的处理,但没有去重 } sort(z+1,z+n+1,cmp);//以权值为第一关键字从小到大排序 for(int i=1;i<=n;i++)//按权值从小到大枚举 { int maxx=query(z[i].num);//查询编号小于等于num[i]的LIS最大长度 modify(z[i].num,++maxx);//把长度+1,再去更新前面的LIS长度 ans=max(ans,maxx);//更新答案 } printf("%d\n",ans); return 0; }
例题:导弹拦截
超快写法
求一个不上升序列
求一个上升序列长度
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #define R register 5 using namespace std; 6 const int N=100010; 7 int a[N],d1[N],d2[N],n; 8 inline bool read(int &x) { 9 char c=getchar(); 10 if(c==EOF)return false; 11 while(c>'9'||c<'0')c=getchar(); 12 while(c>='0'&&c<='9') { 13 x=(x<<1)+(x<<3)+(c^48); 14 c=getchar(); 15 } 16 return true; 17 } 18 int main() { 19 while(read(a[++n]));n--; 20 R int len1=1,len2=1; 21 d1[1]=d2[1]=a[1]; 22 for(R int i=2; i<=n; i++) { 23 if(d1[len1]>=a[i])d1[++len1]=a[i]; 24 else *upper_bound(d1+1,d1+1+len1,a[i],greater<int>())=a[i]; 25 if(d2[len2]<a[i])d2[++len2]=a[i]; 26 else *lower_bound(d2+1,d2+1+len2,a[i])=a[i]; 27 } 28 printf("%d\n%d",len1,len2); 29 return 0; 30 }
拓展
1. 序列变成一个环(即a[n]和a[1] 相邻)在环上找一个最大的子段?
处理理环上问题时, 一个经典的思路路是“破环成链”
将环复制一份,即令a[i+n] = a[i],那么环上的一段子区间,对应了序列上长度不超过n的一段区间
那么问题转化为找到一个长度不大于 n 的区间,使得 sum[r]-sum[l - 1] 最 大
可行性DP(bool,f[i][j])-->最优化DP(int,f[i])
有 n 个物品,若 干个询问,每个询问给定 i;问把 物品 i 去掉做背包的结果
维护 f[i] 表示前 i 个物品的背包数组,g[i] 表示后 i 个物品的背包数组,删掉 一个物品,将 f[i - 1] 和 g[i + 1] 合并即可
每次给定区间 [l, r],问把区间 [l, r] 中的物品拿出来做背包的结果
分治, solve(l, r) 表示处理 [l, r] 的 子区间的函数
取中点 mid,预处理 f[i ~ mid] 的背包数组和 g[mid+1 ~ i] 的背包数组,回答经过 mid 的区间的询问
递归处理 (l, mid - 1) 和 (mid + 1, r)
trick
一些问题中,物品大小非常大,开不不下背包数组;另一方面,价值比较小
设 f[i] 表示要获得 i 这么多的价值, 至少要多大的背包
其实是另一个维度入手的背包
这种技巧在其它一些DP题里也会用到