主要是最长公共子序列和最长上升子序列。

目录

一.最长公共子序列

1.基本操作

2.求最长公共子序列个数

【HAOI2010 Day2】最长公共子序列

3.求最长公共子序列(字典序版)

最长公共子序列(字典序最小版)

最短包含串

4.最长公共子序列转最长上升序列

最长公共子序列(变态版)

二.最长上升子序列

1.基本操作

O(n^2)做法

O(nlog(n))做法

2. 求方案数

位置不同

本质不同

3.字典序最长上升子序列

4.求最长上升子序列中的数

建图法(但是TLE)

差分法(智慧的)

5.思维题

通用的0


一.最长公共子序列

1.基本操作

问题:给定序列a、b,求a、b的最长公共子序列的长度。

直接dp[i][j]表示直到a的第i项,b的第j项的最长公共子序列。

dp[i][j]=max(dp[i][j-1],dp[i-1][j])(a[i]!=b[j])

        dp[i][j]=dp[i-1][j-1]+1(a[i]==b[j])

         2.求最长公共子序列个数   

                【HAOI2010 Day2】最长公共子序列     

问题描述

字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列“i0,i1,…,ik-1”,使得对所有的j=0,1,…,k-1,有xij = yj。例如,X=“ABCBDAB”,Y=“BCBD”是X的一个子序列。
对给定的两个字符序列,求出他们最长的公共子序列长度,以及最长公共子序列个数。

        设dp[i][j]表示直到a的第i项,b的第j项的最长公共子序列,f[i][j]表示直到a的第i项,b的第j项的最长公共子序列的个数。

dp[i][j]=max(dp[i][j-1],dp[i-1][j])(a[i]!=b[j])

        dp[i][j]=dp[i-1][j-1]+1(a[i]==b[j])

        f[i][j]=f[i-1][j-1](a[i]==b[j])

        f[i][j]+=f[i-1][j](dp[i][j]==dp[i-1][j])

        f[i][j]+=f[i][j-1](dp[i][j]==dp[i][j-1])

        f[i][j]-=f[i-1][j-1](a[i]!=b[j]&&dp[i-1][j-1]==dp[i][j])

                        

        f的设法很好想,关键是如何去重?

感性理解一下,如果有(a[i]!=b[j]&&dp[i-1][j-1]==dp[i][j]),那么f[i][j]将加上两次f[i-1][j-1],便需要减去。

3.求最长公共子序列(字典序版)

最长公共子序列(字典序最小版)

问题描述

求字符串a和b的最长公共子序列(lcs)。输出字典序最小的方案。

先求出最长公共子序列的长度,然后凑出字典序最小或最大。因为小写字母总共26个,所以对于ans数组的第i位去枚举放谁(乘上一个26的常数)。

于是初始化f[i][j]表示自i+1位起到结尾,字母(char)('a'-j+1)第一次出现位置的下标(是一个贪心)。每次枚举到ans的第i位,判断字母j:

dp[nexta][nextb]>=ans-i?( nexta=f[nowa][j],nextb=f[nowb][j])

是就转移括号内的,否则j++

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=5e3+5,M=30;
int n,m;
char a[N],b[N],c[N];
int ta[N][M],tb[N][M],dp[N][N];
void init()
{for(int i=1;i<=26;i++){ta[n+1][i]=N,tb[m+1][i]=N;for(int j=n;j>=1;j--){ta[j][i]=ta[j+1][i];if(a[j]==(char)'a'+i-1)ta[j][i]=j;}for(int j=m;j>=1;j--){tb[j][i]=tb[j+1][i];if(b[j]=='a'+i-1)tb[j][i]=j;}}
}
int main()
{scanf("%d%d",&n,&m);scanf("%s%s",a+1,b+1);for(int i=n;i>=1;i--){for(int j=m;j>=1;j--){dp[i][j]=max(dp[i+1][j],dp[i][j+1]);if(a[i]==b[j])dp[i][j]=dp[i+1][j+1]+1;      }}init();int ans=dp[1][1],ax=0,bx=0;for(int i=1;i<=ans;i++){for(int j=1;j<=26;j++){int nax=ta[ax+1][j],nbx=tb[bx+1][j];if(nax==N||nbx==N)continue;if(dp[nax][nbx]>=ans-i+1){ax=nax,bx=nbx;c[i]=(char)('a'+j-1);break;}}}for(int i=1;i<=ans;i++){printf("%c",c[i]);}return 0;
}

总结:对于求字典序最大或最小问题,dp[i][j]表示自a的i位,b的j位到结尾的最长公共子序列长度大小,方便ans数组的求解(从后往前)。

                最短包含串

问题描述

给定字符串a和b,求最短的字符串s,使得a与b均为s的子序列。

仅求长度太简单了,所以你要找到所有满足条件的s中字典序最小的。

同理,只是变了一下。

求出答案anslen=lena+lenb-dp[1][1]。dp设法同上一道题,倒着存。

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=5e3+5,M=30;
int n,m;
int dp[N][N];
char a[N],b[N],c[2*N];
int main()
{scanf("%d%d",&n,&m);scanf("%s%s",a+1,b+1);for(int i=n;i>=1;i--){for(int j=m;j>=1;j--){dp[i][j]=max(dp[i][j+1],dp[i+1][j]);if(a[i]==b[j])dp[i][j]=dp[i+1][j+1]+1;}}int ax=1,bx=1,len=n+m-dp[1][1];for(int i=1;i<=len;i++){  if(ax>n){for(;bx<=m;bx++)c[i++]=b[bx];break;}if(bx>m){for(;ax<=n;ax++)c[i++]=a[ax];break;}if(a[ax]==b[bx]){c[i]=a[ax];ax++,bx++;}else if(a[ax]<b[bx]){if((n-ax)+(m-bx+1)-dp[ax+1][bx]<=len-i)c[i]=a[ax++];else c[i]=b[bx++];}else{if((n-ax+1)+(m-bx)-dp[ax][bx+1]<=len-i)c[i]=b[bx++];else c[i]=a[ax++];}}for(int i=1;i<=len;i++){printf("%c",c[i]);}return 0;
}

4.最长公共子序列转最长上升序列

最长公共子序列(变态版)

问题描述

求数列a与b数列的最长公共子序列长度。

1<=lena,lenb<=3*10^5

0<=ai,bi<=2^31

数列a的值互不相同,数列b的值也互不相同。 

因为值不同,所以我们对于每个数i(记得离散化)找到它在a、b中的下标ida,idb。

显而易见,我们要求满足ida_i<ida_j,idb_i<idb_j的i,j最大个数。于是按照ida(或idb)为关键字排序,再求最长上升子序列即可。

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=3e5+5;
int n,m,ans;
int a[N],b[N],t[2*N],z[2*N],dp[N];
struct node{int a,b;
}f[N];
bool cmp(int x,int y)
{return x<y;
}
void init()
{int m1=0;sort(t+1,t+1+n+m,cmp);m1=unique(t+1,t+1+n+m)-t-1;for(int i=1;i<=n;i++){a[i]=upper_bound(t+1,t+1+m1,a[i])-t-1;f[a[i]].a=i;}for(int i=1;i<=m;i++){b[i]=upper_bound(t+1,t+1+m1,b[i])-t-1;f[b[i]].b=i;z[f[b[i]].a]=i;}
}
int main()
{scanf("%d%d",&n,&m);if(n>m)swap(n,m);for(int i=1;i<=n;i++){scanf("%d",&a[i]);t[i]=a[i];}for(int i=1;i<=m;i++){scanf("%d",&b[i]);t[n+i]=b[i];}init();for(int i=1;i<=n+m&&z[i]!=0;i++){int tp=lower_bound(dp+1,dp+1+ans,z[i])-dp;if(dp[tp]>=z[i])dp[tp]=z[i];else dp[++ans]=z[i];}printf("%d",ans);return 0;
}

二.最长上升子序列

        1.基本操作

        问题:给定序列a,求a的最长上升子序列的长度。

O(n^2)做法

设dp[i]表示到第i位的最长上升子序列长度。

代码:

for(int i=1;i<=n;i++)for(int j=1;j<i;j++)if(a[i]>a[j])f[i]=f[j]+1;

O(nlog(n))做法

设dp[i]表示到目前位置,凑成长度为i的最长上升子序列的末尾最小值(贪心)。

代码:

for(int i=1;i<=n;i++)
{int tp=lower_bound(dp+1,dp+1+ans,a[i]);if(dp[ans]>=a[i])dp[tp]=a[i];else dp[++ans]=a[i];
}

2. 求方案数

位置不同

问题描述

给定a序列,求出最长上升子序列的长度,以及位置最长上升子序列的数量。

1<=lena<=10^5,0<=ai<=lena,答案mod10^9+7

设dp[i]表示到当位置能凑成长度为i的最长上升子序列的末尾最小值。

建立vector<int>g存下所有进入过dp数组的值k与当时的方案数sum,并存成前缀和的形式,方便后面转移。

直接上样例:4 3 5 7 1 6 2

dp(从下往上存):

1
3 2 6
4 5 7

g(从下往上存):

k sum k sum k sum
1 3
3 2 2 3 6 4
4 1 5 2 7 2

每次对于新的数i,判断是否需要ans++:

如果要:

dp[++ans]=i

在在g[ans-1]中寻找第一个不以成为i的上一个数下标t(t以后的都比i小),得到方案数ans-1层的总方案数减去t前的总方案数(g是满足单调性的)

如果不要:

dp[tp]=i

在在g[tp-1]中寻找第一个不以成为i的上一个数下标t(t以后的都比i小),得到方案数tp-1层的总方案数减去t前的总方案数(g是满足单调性的),注意存的是前缀和。

注意,tp或ans等于一时sum直接加1。

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=1e5+5,mod=1e9+7;
int n,ans;
int a[N],dp[N];
struct node{long long k,sum;
};
vector<node> g[N];
int find(int x,int l,int r,int y)
{while(l<r){int mid=l+r+1>>1;if(g[x][mid].k>=y)l=mid;else r=mid-1;}return l;
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){int tp=lower_bound(dp+1,dp+1+ans,a[i])-dp;if(dp[tp]>=a[i]){dp[tp]=a[i];long long t1=g[tp][g[tp].size()-1].sum;if(tp==1){g[tp].push_back((node){a[i],t1+1});}else{int size=g[tp-1].size()-1;int t=find(tp-1,0,size,a[i]);long long t2=g[tp-1][size].sum-g[tp-1][t].sum;if(g[tp-1][t].k<a[i])t2=g[tp-1][size].sum;g[tp].push_back((node){a[i],(t1+t2+mod)%mod});}}else{dp[++ans]=a[i];if(ans==1){g[ans].push_back((node){a[i],1});}else{int size=g[ans-1].size()-1;int t=find(ans-1,0,size,a[i]);long long t1=g[ans-1][size].sum-g[ans-1][t].sum;if(g[ans-1][t].k<a[i])t1=g[ans-1][size].sum;g[ans].push_back((node){a[i],(t1+mod)%mod});}}}printf("%lld %lld",ans,g[ans][g[ans].size()-1].sum%mod);return 0;
}
/*
10
5 3 8 6 3 6 9 1 5 2
*/

本质不同

方法和位置不同差不多,只是当发现dp[tp]等于当前的i时直接覆盖(因为当前重新算的覆盖了上次算的)。

问题描述

给定a序列,求出最长上升子序列的长度,以及本质最长上升子序列的数量。

(1 2 1 2 3,1 2 3和1 2 3本质相同)

1<=lena<=10^5,0<=ai<=lena,答案mod10^9+7

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=1e5+5,mod=1e9+7;
int n,ans;
int a[N],dp[N];
struct node{int k;long long sum;
};
vector<node> g[N];
int find(int x,int z)
{int l=0,r=g[x].size()-1;while(l<r){int mid=l+r+1>>1;if(g[x][mid].k>=z)l=mid;else r=mid-1;}return l;
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){int tp=lower_bound(dp+1,dp+1+ans,a[i])-dp; if(dp[tp]>=a[i]){if(tp==1){if(dp[tp]!=a[i]){g[tp].push_back((node){a[i],g[tp][g[tp].size()-1].sum+1});}dp[tp]=a[i];continue;}  int size=g[tp-1].size()-1;int size1=g[tp].size()-1;int tp1=find(tp-1,a[i]);long long tp2=g[tp-1][size].sum-g[tp-1][tp1].sum;if(g[tp-1][tp1].k<a[i]){tp2=g[tp-1][size].sum;}if(dp[tp]==a[i]){g[tp][size1].sum=tp2;if(size1>=1){g[tp][size1].sum=(g[tp][size1].sum+g[tp][size1-1].sum+mod)%mod;}    }else{g[tp].push_back((node){a[i],(tp2+g[tp][g[tp].size()-1].sum+mod)%mod});}dp[tp]=a[i];}else{dp[++ans]=a[i];if(ans==1){g[ans].push_back((node){a[i],1});}else{int size=g[ans-1].size()-1;int tp=find(ans-1,a[i]);long long tp1=g[ans-1][size].sum-g[ans-1][tp].sum;if(g[ans-1][tp].k<a[i])tp1=g[ans-1][size].sum;g[ans].push_back((node){a[i],(tp1+mod)%mod});}}}printf("%d %d",ans,g[ans][g[ans].size()-1].sum);return 0;
}

3.字典序最长上升子序列

问题描述

给定序列a,求出a的最长上升子序列的长度,并输出其中字典序最大的一个。

跟最长公共子序列的字典序问题求法差不多。

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=5e3+5;
int n,ans;
int a[N],b[N];
int dp[N],t[N][N];
void init()
{for(int i=1;i<=n;i++){t[n+1][i]=N;for(int j=n;j>=1;j--){t[j][i]=t[j+1][i];if(a[j]==i)t[j][i]=j;}}
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=n;i>=1;i--){dp[i]=1;for(int j=n;j>=i+1;j--){if(a[i]<a[j]){dp[i]=max(dp[i],dp[j]+1);}}ans=max(ans,dp[i]);}init();int tp=0;for(int i=1;i<=ans;i++){for(int j=n;j>=1;j--){int nxt=t[tp+1][j];if(nxt==N)continue;if(dp[nxt]>=ans-i+1){tp=nxt,b[i]=j;break;}}}printf("%d\n",ans);for(int i=1;i<=ans;i++){printf("%d ",b[i]);}return 0;
}

4.求最长上升子序列中的数

问题描述

最长上升子序列的方案可能有很多,但有的数在所有方案中都有出现,有的数只出现在部分方案中,有的数不在任意一种方案中。

你需要按上述规则将原数列中的数进行分类:

输出2表示它存在于所有最长上升子序列中;

输出1表示它存在与一些最长上升子序列中;

输出0表示它不存在与任意最长上升子序列中。

建图法(但是TLE)

关于用建图方式解决最长上升子序列的长度、方案数、字典序具体方案等问题都可以完成。

直接上样例:5 8 9 2 3 1 6 4 7

建图:

可以看到只有结尾为7时才满足,于是找到所有结尾满足的点后,倒着 DFS回去

怎么找?当下标i的数进入dp数组后,存f[i]为点i在dp数组的下标。找到ans后遍历f即可。

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=1e5+5;
int n,cnt,ans;
int a[N],head[N];
int f[N],dp[N],num[N],f1[N],f2[N];
struct node{int nxt,v;
}edge[2*N];
vector<int> g[N];
void add(int u,int v)
{edge[++cnt].nxt=head[u];edge[cnt].v=v;head[u]=cnt;
}
void dfs(int x)
{f1[x]=1;for(int i=head[x];i;i=edge[i].nxt){int v=edge[i].v;if(f1[v]==0)dfs(v);}
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){int tp=lower_bound(dp+1,dp+1+ans,a[i])-dp;if(dp[tp]>=a[i]){dp[tp]=a[i];f[i]=tp;g[tp].push_back(i);if(tp>1){for(int j=g[tp-1].size()-1;j>=0;j--){if(a[g[tp-1][j]]>=a[i])break;add(i,g[tp-1][j]);}}}else{dp[++ans]=a[i];f[i]=ans;g[ans].push_back(i);if(ans>1){for(int j=g[ans-1].size()-1;j>=0;j--){if(a[g[ans-1][j]]>=a[i])break;add(i,g[ans-1][j]);}}}}for(int i=1;i<=n;i++){if(f[i]==ans)dfs(i);}for(int i=1;i<=n;i++){if(f1[i])f2[f[i]]++;}for(int i=1;i<=n;i++){if(f1[i]&&f2[f[i]]==1)printf("2 ");else if(f1[i])printf("1 ");else printf("0 ");}return 0;
}

差分法(智慧的)

考虑数i的影响范围,找到g[tp]中时间小于i的时间的,且数值也小于i的区间。

可想想当枚举到i时,g[tp]中的数一定是比i先进入,且g是满足单调性的,所以只需要二分g[tp]中第一个大于它的数的下标t,然后t到g[tp]结尾的数都满足(存到s[i].l与s[i].r中)

同上,找到每个满足f[i]==ans的i,然后往回标记g[ans-1][s[i].l]到g[ans-1][s[i].r]。再在遍历到ans-1位时继续往回搞。用差分数组在s[i].l加1,在s[r].r减1。

注意右边界为g[tp]的结尾,左边界要二分到第一个大于它的数。

继续上样例:5 8 9 2 3 1 4 6 7

dp(从下往上存):

1 4
2 3 6
5 8 9 7

s(从下往上存,vector第一位从0开始):

s[i].l s[i].r 具体的值
1 -1 -1
2 -1 -1
3 2 0 1、2
4 1 0 3
5 -1 -1
6 1 0 3
7 2 0 4、6
8 0 -1 5
9 0 -1 8

代码(代码里l和r搞反了,但不影响):

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=1e5+5;
int n,cnt,ans;
int a[N];
int f[N],dp[N],num[N],f1[N],f2[N];
struct node1{int l,r;
}s[N];
vector<int> g[N],d[N];
int find(int x,int z)
{int l=0,r=g[x].size()-1;while(l<r){int mid=l+r+1>>1;if(a[g[x][mid]]>=z)l=mid;else r=mid-1;}return l;
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){int tp=lower_bound(dp+1,dp+1+ans,a[i])-dp;if(dp[tp]>=a[i]){dp[tp]=a[i];f[i]=tp;g[tp].push_back(i);d[tp].push_back(0);if(tp>1){int tp1=find(tp-1,a[i]);s[i].l=g[tp-1].size()-1;s[i].r=tp1;if(a[g[tp-1][0]]<a[i]){s[i].r=-1;}if(a[g[tp-1][g[tp-1].size()-1]]>a[i]){s[i].l=-1,s[i].r=-1;}}}else{dp[++ans]=a[i];f[i]=ans;g[ans].push_back(i);d[ans].push_back(0);if(ans>1){int tp1=find(ans-1,a[i]);s[i].l=g[ans-1].size()-1;s[i].r=tp1;if(a[g[ans-1][0]]<a[i]){s[i].r=-1;}if(a[g[ans-1][g[ans-1].size()-1]]>a[i]){s[i].l=-1,s[i].r=-1;}}}}for(int i=1;i<=n;i++){if(f[i]==ans){f1[i]=1;if(s[i].l>=0&&ans>=2)d[ans-1][s[i].l]++;if(s[i].r>=0&&ans>=2)d[ans-1][s[i].r]--;}}for(int i=ans-1;i>=1;i--){int tp=0;for(int j=g[i].size()-1;j>=0;j--){tp+=d[i][j];if(tp>0){int tp1=g[i][j];f1[tp1]=1;if(s[tp1].l>=0&&i>=2)d[i-1][s[tp1].l]++;if(s[tp1].r>=0&&i>=2)d[i-1][s[tp1].r]--;}}}for(int i=1;i<=n;i++){if(f1[i])f2[f[i]]++;}for(int i=1;i<=n;i++){if(f1[i]&&f2[f[i]]==1)printf("2 ");else if(f1[i])printf("1 ");else printf("0 ");}return 0;
}
/*
9
5 8 9 2 3 1 5 4 6
*/

5.思维题

通用的0

问题描述

对数列a,求其最长上升(严格)子序列的长度。特别地,你可以将数列中的0替换为任意整数(可为负数),不同的0可以替换为不同的数。

因为0可以变成任何数,所以遇到0时,可以让它后面的所有数都有机会进入dp。

于是可以转换成将0后所有的数减1(贪心),0除外。

代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int N=1e5+5;
int n,ans,cnt;
int a[N],dp[N];
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){if(a[i]==0){cnt++;continue;}else a[i]-=cnt;int tp=lower_bound(dp+1,dp+1+ans,a[i])-dp;if(dp[ans]>=a[i])dp[tp]=a[i];else dp[++ans]=a[i];}printf("%d",ans+cnt);return 0;
}

DP专项(一维DP)相关推荐

  1. 01背包一维dp数组的实现

    二维的01背包模板 #include <iostream> #include <cstdio> #include <cstring>using namespace ...

  2. 强壮的ZZW (一维dp 01背包)

    题目描述 ZZW神牛想要通过吃东西变得强壮!于是他买了N(1<=N<=4000)件食物,每件食物有自己的体积V(1<=V<=400),以及可以为ZZW增加的强壮度D(1< ...

  3. 0-1背包使用一维dp数组时为何v要从大到小枚举

    样例数据 5 8 3 5 1 2 2 4 5 2 1 3 如若不然,也就是让v按照从小到大的顺序枚举,就会出现 注意高亮的那一行,第一件物品的重量只有3,怎么会得到6呢? 代码如下 #include& ...

  4. hdu 4502 一维dp

    吉哥系列故事--临时工计划 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) To ...

  5. 树形dp ---- gym101667 A(贪心 + 树形dp + 两个dp方程组维护)

    题目链接 题目大意: 就是一棵5e35e35e3的树,可以选择一些点,放上基站,如果uuu上的基站价值为ddd,那么距离uuu小于等于ddd的点都会被覆盖,问使得整棵树被覆盖需要的最小价值. 解题思路 ...

  6. leetcode10 为什么p[j-1] == '*'的时候,不能用递推公式dp[i][j] = dp[i][j-1] || dp[i][j-2] || dp[i-1][j]

    因为可能会出现以下情况: "mississippi" "mis*is*p*." mississ mis*is* 符合 mississi mis*is* 符合 所 ...

  7. 【BZOJ】1076 [SCOI2008]奖励关 期望DP+状压DP

    [题意]n种宝物,k关游戏,每关游戏给出一种宝物,可捡可不捡.每种宝物有一个价值(有负数).每个宝物有前提宝物列表,必须在前面的关卡取得列表宝物才能捡起这个宝物,求期望收益.k<=100,n&l ...

  8. 牛客网dp专题 数位dp

    文章目录 数位dp 例题: NC116652 uva11038 How many 0's NC15035 送分了QAQ NC20669 诡异数字 NC20665 7的意志 NC17385 Beauti ...

  9. [SOCI2005]最大子矩阵(DP) + [JXOI2018]守卫(DP) + [CQOI2016]手机号码(数位DP)[各种DP专练]

    DP专练博客 DP专练 T1:最大子矩阵 题目 题解 代码实现 T2:守卫 题目 题解 代码实现 T3:手机号码 题目 题解 代码实现 T1:最大子矩阵 题目 这里有一个n*m的矩阵,请你选出其中k个 ...

最新文章

  1. 架构师之路 — 部署架构 — 高可用集群 — N+1 高可用模型
  2. order by、group by也会使用索引?使用这俩关键字的时候索引什么时间会失效
  3. 获得数据库中表字段的名字.txt
  4. IS环境下配置PHP5+MySql+PHPMyAdmin
  5. 玩具谜题(洛谷-P1563)
  6. java list 效率_Java中5种List的去重方法及它们的效率对比,你用对了吗?
  7. 在碾压中找到自己,提升自己的思考能力
  8. kubernetes 网络callico和flannel两种网络
  9. 基于51单片机的数字频率计
  10. 基于Python的Bangumi中动画片排行榜数据可视化分析
  11. Apache ShenYu源码阅读系列-注册中心实现原理之Http注册
  12. d3-axis坐标轴
  13. 想查看微信好友撤回的消息?Python帮你搞定
  14. 误发邮件怎么办?发错邮件如何撤回?/
  15. linux C-kermit 安装使用
  16. L1-030. 一帮一-PAT团体程序设计天梯赛GPLT
  17. 初级测试小宝典 测试流程,不能复现bug,开发不认为是bug级2020测试点的热点提问的回答
  18. 【纪中受难记】——Day2.感觉冤的慌
  19. 基于百度AI的人像识别开发的登录模块
  20. android实习日志_Android实习的个人总结

热门文章

  1. 关掉服务器影响svn,服务器重启后svn
  2. LVS负载均衡-基础知识梳理
  3. 招商证券股票最低卖出价格计算器 | averiany涂鸦馆
  4. 库克:乔布斯1998年说服我离开康柏 加入了一家频临破产的公司
  5. 远程桌面的复制粘贴失败解决方案
  6. python便捷数据怎么获取_Python数据分析入门——从数据获取到可视化
  7. php游戏礼包源码,php 游戏新手卡领号程序管理系统 v2.5
  8. vue渲染html代码
  9. 【SVO代码】(一)从头到尾
  10. python 直接退出程序_python程序退出方式