1. 程式人生 > >AGC005 D ~K Perm Counting

AGC005 D ~K Perm Counting

題意

問N有多少個排列滿足,對於i∈[1,n],有aii̸=K|a_i-i|\not=K 答案對924844033取模

題解

如果我們算出至少i個位置不滿足這個條件的方案數(令之為f(i)),那我們就可以通過容斥來實現這道題 那麼f(i)怎麼求呢 我們把位置看成點,位置上的數也看成點,把那些不滿足條件的關係看做邊,那麼就成了一個二分圖 如圖是5 2 如圖是5 2 如果我們把他拉直會怎樣 在這裡插入圖片描述 其實就成了幾條鏈 其實也就是說,只有那些%k相同的彼此間會有影響 我們可以通過一個dp,分開計算每條鏈匹配i條邊的方案數,然後再加起來 不過更巧妙的辦法是把這些看做一條長鏈,只是那些原來的鏈的終止節點不能被匹配,然後答案直接讀取末尾的值 也即是說,定義d

(i,j,0/1)d(i,j,0/1)表示前i個,有j個不滿足條件,當前節點是否選擇 d(i+1,j,0)=d(i,j,0)+d(i,j,1)d(i+1,j,0)=d(i,j,0)+d(i,j,1) d(i+1,j+1,1)=d(i,j,0)d(i+1,j+1,1)=d(i,j,0)(注意此處轉移時判斷是否是兩條鏈的交界處(即是否真的可以連邊)) 那麼,我們要求的f(i)就是(d(2n,i,0)+d(2n,i,1))(ni)!(d(2*n,i,0)+d(2*n,i,1))*(n-i)! 然後容斥

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mod=924844033;
const int N=4005;
int n,k;
bool vis[N][2];
bool ed[N];
int tot;
int d[N][N][2];
int f[N];
int fact[N];
int main()
{
    //freopen("captain.in","r",stdin);
//freopen("captain.out","w",stdout); scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) for(int j=0;j<=1;j++) if(!vis[i][j]){ int len=0; for(int x=i,y=j;x<=n;x+=k,y^=1) vis[x][y]=1,len++; tot+=len; ed[tot]=1; } ed[0]=1; d[0][0][0]=1; for(int i=0;i<2*n;i++) for(int j=0;j<=n;j++){ d[i+1][j][0]=(d[i][j][0]+d[i][j][1])%mod; if(!ed[i]) d[i+1][j+1][1]=d[i][j][0]; } for(int i=0;i<=n;i++) f[i]=(d[2*n][i][0]+d[2*n][i][1])%mod; fact[0]=1; for(int i=1;i<=n;i++) fact[i]=1ll*fact[i-1]*i%mod; int ans=0; for(int i=0,j=1;i<=n;i++,j=-j) ans=((1ll*ans+1ll*j*fact[n-i]*f[i]%mod)%mod+mod)%mod; printf("%d\n",ans); }