AtCoder agc024_e - Sequence Growing Hard
tzc:“AtC風格的計數DP。”我就想問了,AtC的題怎麼可能不是AtC風格的呢?
洛谷題目頁面傳送門 & AtC題目頁面傳送門
給定\(n,m,p\),求有多少個值域為\([1,m]\cap\mathbb Z\)的序列\(n+1\)元組\((a_0,a_1,\cdots,a_n)\),滿足:
- \(\forall i\in[0,n],|a_i|=i\);
- \(\forall i\in[0,n)\),\(a_i\)是\(a_{i+1}\)的子序列;
- \(\forall i\in[0,n),a_i<a_{i+1}\)。
答案對\(p\)取模。
\(n,m\in[1,300],p\in\left[2,10^9\right]\)
。
顯然,原問題等價於,一個序列\(a\)一開始為空,\(n\)次每次往裡任意位置塞一個\([1,m]\)內的數,使得字典序大於原來的序列,求\(n\)次組成的序列組的個數(注意這裡不能說操作的數量,因為在不同位置插入相同數可能得到的序列一樣)。
考慮在位置\(x\)插入\(y\)合法的充要條件。若\(x=|a|+1\)顯然合法。剩下的顯然要滿足\([y]+a_{x,|a|}>a_{x,|a|}\)。若\(y\neq a_x\),那麼字典序相對大小決定在它們身上,所以\(y>a_x\)。否則的話,,,注意到若把\(y\)插入到往右數第一個滿足\(y\neq a_{x'}\)的位置\(x'\)
分析完之後還是不會做。這是一個動態的插入問題,每次的\(a\)都會變。不妨將時間定格在最後一刻,所有數都各就各位了,再來考慮每個位置\(i\)上的值\(v_i\)與被插入的時間(時間戳)\(dfn_i\)。
稍微轉化一下,此時一個\((v,dfn)\)序列合法當且僅當,\(\forall i\in[1,n],v_i>v_{\min\limits_{j>i,dfn_j<dfn_i}\{j\}}\)(為了方便,設\(v_{n+1}=dfn_{n+1}=0\)
考慮DP。設\(dp_{i,j}\)表示\((v,dfn)\)序列的大小為\(i\),值域為\([j,m]\)的方案數。邊界\(dp_{0,j}=1\),目標\(dp_{n,1}\)。列舉\(dfn_x=1\)的\(x\)和\(v_x\)兩個東西,就可以轉移了,注意到若左右各找一個方案,那麼左右兩半內部的\(dfn\)相對大小固定,而跨越左右兩半的\(dfn\)相對大小無所謂,將它們的\(dfn\)值域共同擴充套件,還要乘上一個組合數。
狀態轉移方程:
\[dp_{i,j}=\sum_{k=1}^i\sum_{o=j}^mdp_{k-1,o+1}dp_{i-k,j}\binom{i-1}{k-1} \]
不過這樣時間複雜度是\(\mathrm\!\left(n^4\right)\)的(視\(n,m\)同階),然鵝很好優化,移一下\(\sum\)得到
\[dp_{i,j}=\sum_{k=1}^idp_{i-k,j}\binom{i-1}{k-1}\sum_{o=j}^mdp_{k-1,o+1} \]
則實時維護\(\forall i\in[0,n],dp_i\)的字首和,後面那個\(\sum\)呼叫一下字首和複雜度就少一個\(n\)了。
程式碼:
#include<bits/stdc++.h>
using namespace std;
const int N=300,M=300;
int n,m,mod;
int comb[N+1][N+1];
int dp[N+1][M+2];
int Sum[N+1][M+2];
int main(){
cin>>n>>m>>mod;
comb[0][0]=1;
for(int i=1;i<=n;i++)for(int j=0;j<=i;j++)comb[i][j]=((j?comb[i-1][j-1]:0)+(j<i?comb[i-1][j]:0))%mod;//預處理組合數
for(int i=1;i<=m+1;i++)Sum[0][i]=(Sum[0][i-1]+(dp[0][i]=1))%mod;//邊界
for(int i=1;i<=n;i++){//轉移
for(int j=1;j<=m;j++){
for(int k=1;k<=i;k++)(dp[i][j]+=1ll*dp[i-k][j]*comb[i-1][k-1]%mod*(Sum[k-1][m+1]-Sum[k-1][j])%mod)%=mod;//轉移方程
Sum[i][j]=(Sum[i][j-1]+dp[i][j])%mod;//維護字首和
// printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
}
Sum[i][m+1]=Sum[i][m];//維護字首和
}
cout<<(dp[n][1]+mod)%mod;//目標
return 0;
}