深夜更一波啊。明天任务,分别A一道 二分,线性dp 和 搜索题
题目链接:https://www.luogu.com.cn/problem/P1020
大意:给出一组数据,求出这种数据的最长不升子序列 以及 至少多少个这样的不升子序列个数。
思路:前半个题最长不升子序列正常线性dp,后面求这样的不升子序列的个数,其实就是最长升子序列的个数,因为最长上升子序列包含 最长不升子序列的所有的逆序对。
这个题数据加强过了,n^2 dp只能过一半的数据。最优解法需要 二分dp。二分是我一个薄弱的地方(貌似我没什么强势的地方=.=)
那么这个题就需要我认真思考一下了。我发现了这个优化写法有两种。情况都挺复杂,但是大致的思想是一样的,就是我们的dp数组表示的不是当前i位置时所记录的符合的最大数量,而是直接记录的合法数,然后遇到不合法数进行替换,最后得到最长上升子序列的长度(但不一定是最长上升子序列)但是数量是和最长上升子序列的数量是一致的
50%code:d
#include<bits/stdc++.h> using namespace std; const int nn = 1e5+10; int f[nn]; int dp[nn]; int a[nn]; //我们考虑 f[i]表示 到第i个数的时候,最长不升子序列的数量 int main() { int n=0; while(~scanf("%d",&a[++n])); --n; // for(int i=1;i<=n;i++) cin>>a[i]; int maxt = 0; for(int i=1;i<=n;i++) { f[i] = 1; for(int j=1;j<i;j++) if(a[i]<=a[j]) f[i] = max(f[i],f[j]+1); maxt=max(maxt,f[i]); } cout<<maxt<<endl; maxt = 0; for(int i=1;i<=n;i++) { dp[i]=1; for(int j=1;j<i;j++) if(a[i]>a[j]) dp[i] = max(dp[i],dp[j]+1); maxt=max(maxt,dp[i]); } cout<<maxt<<endl; }
1.二分code:
我们每次都找一个合法的数移动到我们dp数组的最后,然后我们去找不合法的数,找到不合法的数之后,我们去前面合法的数去找一个合法的位置,然后覆盖掉。反复如此,因为我们放数的顺序是一致的,所以我们最后得出的答案数量自然也是正确的。有可能我们之后不合法的数放到了前面一个合法的位置,但最后这个数并不是我们所想要的合法数,而只是一个替代另一个序列的数。但是数量是一致的。
#include<bits/stdc++.h> using namespace std; const int nn = 1e5+10; int f[nn]; int dp[nn]; int a[nn]; int main() { int n=0; while(~scanf("%d",&a[++n])); --n; // for(int i=1;i<=n;i++) cin>>a[i]; int maxt = 0; int cnt =1; f[1] = a[1]; for(int i=2;i<=n;i++) { if(f[cnt]>=a[i]) f[++cnt] = a[i];//将符合条件的移到最后 else for(int j=1;;j++)//找到不满足的,就找前面一个位置放进去。 if(f[j]<a[i]){f[j]=a[i];break;} } cout<<cnt<<endl; cnt =1; dp[1] = a[1]; for(int i=2;i<=n;i++) { if(dp[cnt]<a[i]) dp[++cnt] = a[i]; else for(int j=1;;j++) if(dp[j]>=a[i]){dp[j]=a[i];break;} } cout<<cnt<<endl; }
2.二分code:
同上一样的原理,或者说这个是真正的二分。同样是记录数,我们这里记录的时候查找不合法数放置的位置是通过二分,利用已知合法数组的边界l和r,来控制二分的范围,然后如果遇到不合法数合法的位置,我们放进去,不同的是,这个放置的位置可能是合法的末尾,如果是合法的末尾,那么l+1的位置一定是maxt+1,也就是比maxt要大,这样我们就可以更新maxt。经过猜想和实验,我们将代码x>maxt替换成了x==maxt+1,同样AC。
#include<bits/stdc++.h> using namespace std; const int nn = 1e5+10; int f[nn]; int dp[nn]; int a[nn]; int main() { int n=0; while(~scanf("%d",&a[++n])); --n; // for(int i=1;i<=n;i++) cin>>a[i]; int maxt = 0; f[0] = 0x3f3f3f3f; for(int i=1;i<=n;i++) { int l = 0, r = maxt+1;//用来控制合法数组的l和r while(r-l>1) { int m = l+(r-l)/2; if(f[m]>=a[i]) l=m; else r=m; //从我们已知的合法数中去寻找,求得合法位置放置此数。 } //同样是覆盖的原理,同上一种二分。或者说这次我们找的是真正的二分,而上一个是暴力的放置数。 int x = l+1; if(x>maxt) maxt = x;//如果新得到的更大,也就是说它是一个新的合法数,我们更新maxt,然后放置到最后 f[x] = a[i]; } cout<<maxt<<endl; maxt = 0; dp[0] = 0; for(int i=1;i<=n;i++) { int l = 0, r = maxt+1; while(r-l>1) { int m = l+(r-l)/2; if(dp[m]<a[i]) l=m; else r=m; } int x = l+1; if(x>maxt) maxt = x; dp[x] = a[i]; } cout<<maxt<<endl; }