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值求和的最大值即可。