1.上升子序列对数。
给定一个长度n的序列a,求出其中长度为m的有序子序列对数,mod上123456789。(1<= n<=10000 ,1<=m<=100)
因为在处理第i个数的时候,得到了之前i之前长度为1-m的长度,因此很容易想到用动态规划来处理该问题。
思想来源:子序列有长度要求,并且是递增的,因此每往序列后添加一个字就会使得结果增加关于添加字的数量,就是一个dp
dp[i][j]:表示到长度为i的序列时,以a[i]结尾,有长度为j的子序列对数。
因此状态转移就应该:dp[i][j]的结果等于长度k为1-i-1中所有结果为j-1且a[k]<a[i]的和。
状态转移方程: 最终结果:
结果发现超时,因此该转移需要优化,即处理对于条件a[k]<a[i]的求和,由于求和,我们可以考虑线段树或树状数组。
线段树或树状数组:区间单调上升序列的求和 https://blog.csdn.net/qq_38890926/article/details/81381872
代码: HDU - 4991 HDU - 5542
#define maxn 10005
#define maxm 105
typedef long long ll;
int n,m;
ll a[maxn],b[maxn];
ll tree[105][maxn];
int lowbit(int i)
{
return i&(-i);
}
ll sum(int len,int x)
{
ll s=0;
while(x)
{
s=(s+tree[len][x])%mod;
x=x-lowbit(x);
}
return s;
}
void add(int len,int i,ll v)
{
while(i<=n)
{
tree[len][i]=(tree[len][i]+v)%mod; //添加向上添加
i=i+lowbit(i);
}
}
ll dp[maxn][maxm]; //dp数组,可以不用要,线段树中能够直接获取结果,方便理解 写法见后面
int main()
{
while(scanf("%d %d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
b[i]=a[i];
}
sort(b+1,b+1+n); //获取排序后的位置
memset(dp,0,sizeof(dp));
memset(tree,0,sizeof(tree));
for(int i=1;i<=n;i++)
{
dp[i][1]=1;
int index=lower_bound(b+1,b+1+n,a[i])-b;//单调递增,low_bound可得a[i]最前一个位置,即在树状数组中位置
add(1,index,1); //每次得到一个新的dp值,就要加入到树状数组中修改值,让后面求和
for(int j=2;j<=m;j++)
{
dp[i][j]=sum(j-1,index-1);
add(j,index,dp[i][j]);
}
}
ll ans=0;
for(int i=1;i<=n;i++)
ans=(ans+dp[i][m])%mod;
printf("%lld\n",ans);
}
return 0;
}
/* //不用dp数组方法
for(int i=1;i<=n;i++)
{
int index=lower_bound(b+1,b+1+n,a[i])-b;
add(1,index,1);
for(int j=2; j<=m; j++)
add(j,index,sum(j-1,index-1));
}
printf("%lld\n",sum(m,lower_bound(b+1,b+1+n,b[n])-b));
*/
2.最长分段子序列
将一个只包含数字的字符串分割成为两部分,使得两部分中各自不降子序列的长度和最大,求该最大值。
分成两个部分,各自求最长不降子序列,暴力方法即是从头到尾标记分割位置,每个划分处求取两段的最大值求和,再和总的最大值比较取较大的一个。
第一段最长不降子序列其实每次都在求取从头到当前分割处的序列长度最大值,故我们可以考虑从最左到最右做一次最长不降子序列,用dp记录到达每个位置的最长值,这样可以优化分割后的第一段,使得只做一次求取。
接下来考虑每个分割处的右端第二段,每次都从分割点处到末尾求一次最长不降子序列,故我们可以想到类似的从末尾开始,向前做一次最长不升子序列,反向dp记录到每个分割点的长度,这样就使得第二段只做一次求取。
最后寻找分割点两端dp值求和的最大值即可。