1. 程式人生 > >[校內NOIP2018模擬20181020] wph AKIOI穩了

[校內NOIP2018模擬20181020] wph AKIOI穩了

++ 遞推 復雜 utc output typename algo 坐標 code

題目描述

這天S鮶正走在去機房的路上,無意間掉進了不知是哪位老人丟下的洞裏,來到了隙間世界。

S鮶發現,隙間是一個p維空間,其中的點可以用一個p維坐標 (\(z_1\),\(z_2\),……\(z_p\)) 表示,且所有坐標的範圍為\([1,n]\) 之間的整數,S鮶她的出發位置在\((1,1,……,1)\)

隙間太大了,所以在任意兩個點之間移動都要開坦克。S鮶一共有 \(m\) 架坦克,第 \(i\) 架坦克有兩個值 \(a_i,b_i\) ,若這架坦克可以在兩個點之間移動,則這兩個點有 \(p-1\) 維坐標相同,且剩下一維坐標分別為 \(a_i,b_i\)

但因為隙間太大了,S鮶算不出了出口在哪裏,只算得該點的坐標的第 \(i\)

\(z_i\) 可能是 \(c_i\) 或者 \(1\)。於是S鮶想要知道有多少種剛好移動 \(q\) 次的方案,可以到達一個可能是出口的位置。

答案對 \(998244353\) 取膜。

輸入格式

第一行4 個整數 \(n,m,p,q\)

第二行 \(p\) 個數 \(c_i\)

接下來 \(m\) 行每行兩個數 \(a_i,b_i\)

輸出格式

一個數表示答案

樣例數據

input1

2 1 2 2
2 2
1 2

output1

4

input2

2 1 3 3
2 1 1
1 2

output2

7

樣例解釋

樣例1

4 個位置都有可能是別墅的位置,到達(1, 1) 的方案有2 種,到達(2, 1) 的方案有0 種,到達(1, 2) 的方案有0 種,到達(2, 2) 的方案有2種,總方案數為4。

樣例2

(1, 1, 1) 和(2, 1, 1) 可能是別墅的位置,到達(1, 1, 1) 的方案有0 種,到達(2, 1, 1) 的方案有7 種,總方案數為7。

數據規模與約定

前10%的數據,滿足\(q\leq 10 , p=2 , q\leq 3\)

另外20%的數據,滿足\(n\leq 20 , p\leq 3\)

另外20%的數據,滿足\(m=n-1\) 所有\(z_i=1\), 第\(i\)\(a_i=i,b_i=i+1,p\leq 100\)

對於100%的數據,滿足\(n\leq 50;m,q\leq 100;p\leq 10^{6}\)

時間限制:\(1\text{s}\)

空間限制:\(512\text{MB}\)

Solution

首先可以發現,每一維都是獨立的,所以先求出到任何一維的每一個點的方案數。這個直接dp就好了,也沒有什麽懸念。我們把求出來的命名為\(dp[i][j]\)表示到了\(i\)點走了\(j\)步的方案。

下面類似於背包。設\(f[i][j]\)表示前\(i\)維,走\(j\)步的方案數。

\[f[i][j]=\sum \limits_{k=0}^{j} f[i-1][j-k]\times dp[1\ or\ c[i]][k]\]

但是這個真的對嗎?

我們發現,先第一維走一步,然後第二維走一步,然後第一維再走一步與西安第一維走兩步,然後第二維走一步是不同的方案。所以式子可能還要改一下。

\[f[i][j]=\sum \limits_{k=0}^{j} f[i-1][j-k]\times dp[1\ or\ c[i]][k] \times C_j^k\]

為什麽還要再乘上一個\(C_j^k\)呢?我們可以抽象一下,一共有\(j\)步給你走,然後這一維可以從中選\(k\)步,所以就是套個組合數。

但是直接轉移是\(O(pq^2)\)的,肯定會超時。既然是遞推,那麽一個顯然的思路是用矩陣優化。但是這個轉移式還跟\(i\)有關呢!更確切的說,是跟\(c[i]\)有關。發現\(c[i]<=n\),而\(n\)只有\(50\),所以以坐標的值分開來轉移。所以我們成功優化成了\(O(nq^3\log p)\)。可是好像還是過不了!!!

發現
\[ \begin{align*} f[i][j]&=\sum \limits_{k=0}^{j} f[i-1][j-k]\times dp[1\ or\ c[i]][k] \times C_j^k\ &=\sum \limits_{k=0}^{j} f[i-1][j-k]\times dp[1\ or\ c[i]][k] \times \frac{j!}{k!(j-k)!}\ &=(\sum \limits_{k=0}^{j} \frac{f[i-1][j-k]\times dp[1\ or\ c[i]][k]}{k!(j-k)!} )\times j! \end{align*} \]
細心地同學可能已經發現,這個新式子其實就相當於是再用可重復的全排列對其進行轉移!

不妨設
\[ \begin{align*} f'[i][j]&=\sum \limits_{k=0}^{j} \frac{f[i-1][j-k]\times dp[1\ or\ c[i]][k]}{k!(j-k)!}\ &=\sum \limits_{k=0}^{j} \frac{f'[i-1][j-k]\times (j-k)!\times dp[1\ or\ c[i]][k]}{k!(j-k)!}\ &=\sum \limits_{k=0}^{j} \frac{f'[i-1][j-k]\times dp[1\ or\ c[i]][k]}{k!}\\end{align*} \]
於是我們實現了一個奇跡,我們使得後面轉移裏面與\(f\)無關的部分,也與\(j\)無關!

於是我們畫一下它的轉移矩陣,發現這是一個循環矩陣!於是我們可以用\(O(q^2)\)實現矩陣乘法,從而這做到\(O(nq^2\log p)\)的復雜度。但是之前的式子就不可以,因為它的矩陣中的每一項還要跟\(j\)有關,不是循環矩陣!

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define lowbit(x) ((x)&(-(x)))
#define REP(i,a,n) for(register int i=(a);i<=(n);++i)
#define PER(i,a,n) for(register int i=(a);i>=(n);--i)
#define FEC(i,x) for(register int i=head[x];i;i=g[i].ne)
template<typename A>inline void read(A&a){a=0;char c=0;int f=1;while(c<'0'||c>'9')(c=getchar())=='-'?f=-1:0;while(c>='0'&&c<='9')a=(a<<3)+(a<<1)+c-'0',c=getchar();f==-1?a=-a:0;}
char buf[30];template<typename A>inline void write(A a){if(a<0)putchar('-'),a=-a;int top=0;if(!a)buf[top=1]='0';while(a)buf[++top]=a%10+'0',a/=10;while(top)putchar(buf[top--]);}
typedef long long ll;typedef unsigned long long ull;
template<typename A,typename B>inline bool SMAX(A&x,const B&y){return y>x?x=y,1:0;}
template<typename A,typename B>inline bool SMIN(A&x,const B&y){return y<x?x=y,1:0;}

const int N=50+7,M=100+7,P=1e6+7,MOD=998244353;
int n,m,p,q,x,y,now,pre,c[N],f[2][M],C[M][M],fac[M],inv[M],a[M],b[M],ans[M],u[M],v[M];
int dp[M][M],vis[M][M];

struct Edge{int to,ne;}g[M<<1];int head[N],tot;
inline void addedge(int x,int y){g[++tot].to=y;g[tot].ne=head[x];head[x]=tot;}

inline ll DP(int x,int y){
    if(y==0)return vis[x][y]=1,dp[x][y]=(x==1);if(vis[x][y])return dp[x][y];
    int &ans=dp[x][y];vis[x][y]=1;
    for(register int i=head[x];i;i=g[i].ne)
        ans+=DP(g[i].to,y-1),ans>=MOD?ans-=MOD:0;
    return ans;
}
inline void Make_DP_on_Graph(){dp[1][0]=1;for(register int j=1;j<=q;++j)for(register int i=1;i<=m;++i)(dp[u[i]][j]+=dp[v[i]][j-1])%=MOD,(dp[v[i]][j]+=dp[u[i]][j-1])%=MOD;}
inline void Make_fac(){
    fac[0]=1;for(register int i=1;i<=q;++i)fac[i]=((ll)fac[i-1]*i)%MOD;
    inv[1]=1;for(register int i=2;i<=q;++i)inv[i]=(ll)(MOD-MOD/i)*inv[MOD%i]%MOD;
    inv[0]=1;for(register int i=1;i<=q;++i)inv[i]=(ll)inv[i-1]*inv[i]%MOD;
} 

inline void Mul(int *p){
    memset(b,0,sizeof(b));
    for(register int i=0;i<=q;++i){
        int s=0;
        for(register int j=0;j<=i;++j)s+=(ll)a[j]*p[i-j]%MOD,s>=MOD?s-=MOD:0;
        b[i]=s;
    }
    memcpy(p,b,sizeof(b));
}

int main(){
//  freopen("camp.in","r",stdin);freopen("camp.out","w",stdout);
    read(n),read(m),read(p),read(q);
    for(register int i=1;i<=p;++i)read(x),++c[x];
    for(register int i=1;i<=m;++i)read(u[i]),read(v[i]);//錯誤筆記: 一開始以為重邊只能算一個方案,沒想到算不同的方案,導致考場丟了30分 
    Make_DP_on_Graph();Make_fac();ans[0]=1;
    for(register int i=1;i<=n;++i)if(c[i]){
        memset(a,0,sizeof(a));
        for(register int j=0;j<=q;++j)a[j]=ll(dp[1][j]+(i==1?0:dp[i][j]))*inv[j]%MOD;
        for(register int j=c[i];j;j>>=1,Mul(a))if(j&1)Mul(ans);
    }write((ll)ans[q]*fac[q]%MOD),putchar('\n');
    fclose(stdin);fclose(stdout);return 0;
}

[校內NOIP2018模擬20181020] wph AKIOI穩了