[題解] P5888 傳球遊戲
阿新 • • 發佈:2021-08-12
一道非常好的 dp 和理解滾動陣列的題
理解題意(做題即翻譯)
有 n 個人擺成環形在玩傳球遊戲,每個人可以傳給任意編號的人,但是某個 zz 覺得太簡單了,於是加了 k 條限制:其格式為 u 不可傳球到 v 這個人,問 m 次傳球后回到 1 的方案數是多少。
資料範圍很重要
n <= 1e9 , k <= 5e4。
睜眼一看,我靠n怎麼這麼大,這要O(n)???!!!
狀態選擇
明顯地,最好選擇傳了 i 次球為狀態,考慮求在第 j 個人的手裡的方案數是 \(f[ i ][ j ]\),結果便是 \(f[ m ][ 1 ]\)。
然而這會爆空間,因此本題的亮點之一滾動陣列就派上用場了,我們在狀態轉移的時候再談。
狀態轉移
我們發現 n 很大,但是 k 卻不是很大,回到題意,也就是說受限制的人很少,設受限制的人(稱為特殊人)為 k 個(包括1號),則對於其餘 n - k 個人都是一樣的,所以我們把 0 號看成一般人,1-k 號看成特殊人進行狀態轉移。
轉移方法:
思路有了,方法也就差不多出來了。
$f[ i ][ j ] = sigma(f[ i - 1 ][ j ](j != 0)) + (n - k) * (f[ 0 ][ 0 ]) - $不能傳給 j 的方案數
接著考慮進一步優化:
對於sigma我們可以在上一層狀態求一個 sum 來儲存上一層的狀態有多少特殊人傳給 j 人的方案,然後加上 0 號人的,減去非法方案更新就完了。
至於滾動陣列,我們可以吧f陣列變成 \(f[ 2 ][ maxn ]\),\(f[ 0 ]\)表示上一層狀態,\(f[ 1 ]\)表示這一層狀態
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cstdlib> #define re register using namespace std; const int P = 998244353; const int maxn=1e6+5,maxm=5e4+5; int n,m,k; int a[maxm],b[maxm],read[maxn],cnt=0,head[maxn],s[maxn]; long long f[2][maxn]; int num = 0; struct node{ int to,nxt; }e[maxm]; inline void link(int u,int v){ e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt; } int main(){ scanf("%d%d%d",&n,&m,&k); for(re int i=1;i<=k;i++){ scanf("%d%d",a+i,b+i); if(a[i]==b[i])continue;//需要判斷 read[++num]=a[i]; read[++num]=b[i]; } read[++num]=1;//1也算,他不可以隨便傳球 sort(read+1,read+num+1); int Cnt=0; for(re int i=1;i<=num;i++){ if(i==1 || read[i]!=read[i-1])s[++Cnt]=read[i]; } for(re int i=1;i<=k;i++){//離散化 a[i]=lower_bound(s+1,s+1+Cnt,a[i])-s; b[i]=lower_bound(s+1,s+1+Cnt,b[i])-s; if(a[i]==b[i])continue; link(b[i],a[i]); } f[0][1] = 1; k = Cnt;//特殊人的個數 long long sum = 1; for(re int i=1;i<=m;i++){//狀態 for(re int j=0;j<=k;j++) f[1][j]=(sum+1LL*(n-k)*f[0][0]%P)%P; sum=0; for(re int j=0;j<=k;j++){//決策 for(re int k=head[j];k;k=e[k].nxt){//遍歷不能傳給他的人(反向建圖的好處),減去非法方案,避免出現負數!!! int v=e[k].to; f[1][j]=(f[1][j]-f[0][v]+P)%P; } f[1][j]=(f[1][j]-f[0][j]+P)%P;//自己不能傳給自己 if(j)sum=(sum+f[1][j])%P; } for(re int j=0;j<=k;j++){//滾動陣列,為下一層狀態做準備 f[0][j]=f[1][j]; f[1][j]=0; } } cout<<f[0][1]; return 0; }
PS:對於離散化操作,我們可以把所有特殊人存到一個有序的輔助空間 s 裡,將其下標作為建圖的下標即可