1. 程式人生 > >P5241 序列(滾動數組+前綴和優化dp)

P5241 序列(滾動數組+前綴和優化dp)

觀察 總結 復雜度 get namespace 正數 const tar 下標

P5241 序列

挺神仙的一題

看看除了dp好像沒什麽其他辦法了

想著怎麽構個具體的圖出來,然鵝不太現實。

於是我們想辦法用幾個參數來表示dp數組

加了幾條邊肯定要的吧,於是加個參數$i$表示已加了$i$條邊

這顯然是不夠的。於是我們又想:強連通分量.....連通塊.......

於是加個$j$表示還有$j$個強連通分量

於是dp數組為$f[i][j]$

這是我們發現一個問題,狀態$f[i][j]$不一定是合法的。

那dp不就GG了嗎

再次撕烤,我們發現每次加上的邊無非就3種情況:

1.把2個強連通分量(或鏈)連成一條鏈

2.在某個強連通分量中瞎連(沒啥用)

3.在1條鏈上的某點向回連,形成一個環,縮成一個新強連通分量(可以減少任意個強連通分量

我們設$k-1$條邊(dp數組下標$k$為正數較好處理)投入到第3種情況

要生成剩下$j$個強連通的情況,我們最少投入$n-j$條邊用於第1種情況

所以$n-j+(k-1)<=i$

我們又發現,要生成剩下$j$個強連通的情況,我們最多共投入的邊數$i$是有限制的

最多情況就是1個塊有$n-j+1$個點,剩下$j-1$個塊只有1個點,藍後大塊每個點連$n-1$條邊,小塊互相之間弱連通

那麽最大邊數為$(n-j+1)*(n-1)+(j-2+j-3+j-4+...+1)=(n-j+1)*(n-1)+(j-1)*(j-2)/2$

所以$i<=(n-j+1)*(n-1)+(j-1)*(j-2)/2$

總結一下,即設$f[i][j][k]$表示到第$i$條邊,有$j$個強連通分量,$k-1$條邊向回連的方案數

限制條件:

$n-j+(k-1)<=i$

$i<=(n-j+1)*(n-1)+(j-1)*(j-2)/2$

轉移:

$f[i][j][k]+=f[i-1][j][k]$(第2種情況)

$f[i][j][k]+=\sum_{h=j+1}^{n}f[i-1][h][k-1]$

顯然是可以滾動數組+前綴和優化的辣

然鵝復雜度還是太高,主要因為k很麻煩

仔細觀察k,發現

$n-j+(k-1)<=i$

$k<=i+j-n+1$

發現$i>=2n$時k總是合法的

於是我們就可以愉快地縮成2維辣

#include<iostream>
#include<cstdio>
#include<cstring>
#define rint register int
using namespace std;
inline int Min(int a,int b){return a<b?a:b;}
const int mod=1e9+7;
inline int Md(int x){return x<mod?x:x-mod;}
#define N 405
int n,f[2][N][N],sf[2][N][N],g[2][N],sg[N][N],lim[N],ans[N*N];
int main(){
    scanf("%d",&n); int tn=Min(n*(n-1),n<<1),w=0;
    for(rint j=1;j<=n;++j) lim[j]=(n-j+1)*(n-1)+(j-1)*(j-2)/2;
    f[1][n][1]=ans[1]=1;
    for(rint j=1;j<=n;++j) sf[1][n][1]=1;
    for(rint i=2;i<=tn;++i,w^=1){
        for(rint j=1;j<=n;++j)
            for(rint k=1;k<=n;++k)
                f[w][j][k]=0;
        for(rint j=1;j<=n;++j) if(lim[j]>=i)
            for(rint k=1;k<=n;++k) if(i-(k-1)>=n-j)
                f[w][j][k]=Md(f[w^1][j][k]+sf[w^1][j+1][k-1]);
        for(rint j=n;j;--j)
            for(rint k=1;k<=n;++k){
                sf[w][j][k]=Md(sf[w][j+1][k]+f[w][j][k]);
                ans[i]=Md(ans[i]+f[w][j][k]);
            }
    }w=1;
    for(rint j=1;j<=n;++j)
        for(rint k=1;k<=n;++k)
            g[0][j]=Md(g[0][j]+f[0][j][k]);
    for(rint j=n;j;--j) sg[0][j]=Md(sg[0][j+1]+g[0][j]);//降維
    for(rint i=tn+1;i<=n*(n-1);++i,w^=1){
        for(rint j=1;j<=n;++j) g[w][j]=0;
        for(rint j=1;j<=n;++j) if(lim[j]>=i)
            g[w][j]=Md(g[w^1][j]+sg[w^1][j+1]);
        for(rint j=n;j;--j){
            sg[w][j]=Md(sg[w][j+1]+g[w][j]);
            ans[i]=Md(ans[i]+g[w][j]);
        }
    }
    for(rint i=1;i<=n*(n-1);++i) printf("%d ",ans[i]);
    return 0;
}

P5241 序列(滾動數組+前綴和優化dp)