1. 程式人生 > 其它 >AGC005D - ~K Perm Counting(組合數學,揹包,dp)

AGC005D - ~K Perm Counting(組合數學,揹包,dp)

AGC005D - ~K Perm Counting

Solution

經典數排列個數題,寫了個大麻煩容斥。

直接容斥,考慮求出 f i f_i fi表示有 i i i個位置 ∣ p i − i ∣ = k |p_i-i|=k pii=k的方案數。一個位置 i i i滿足 ∣ p i − i ∣ = k |p_i-i|=k pii=k,要麼 p i = i + k p_i=i+k pi=i+k,要麼 p i = i − k p_i=i-k pi=ik,當前面的位置選擇了 p i = i + k p_i=i+k pi=i+k時,後面的 p i + 2 k p_{i+2k}

pi+2k就不能選擇 i + k i+k i+k了,前面可能對後面產生影響。

因此我們發現對於下標 m o d    k mod\;k modk不同的位置之間一定沒有影響,於是我們對 m o d    k mod\;k modk餘數分類,拉出 n m o d    k n\mod k nmodk條長為 ⌊ n k ⌋ + 1 \lfloor\frac{n}{k}\rfloor+1 kn+1的鏈和 k − n m o d    k k-n\mod k knmodk條長為 ⌊ n k ⌋ \lfloor\frac{n}{k}\rfloor kn的鏈,此時 ∣ p i − i ∣ = k |p_i-i|=k

pii=k等價於選擇了該點在鏈上的相鄰點,把這些鏈的 f f f值用揹包合併起來就是我們要求的 f f f

於是我們要對每一種 j j j,算出一條鏈上選取 j j j個的方案數。令 g i , j , k g_{i,j,k} gi,j,k表示前 i i i點,選取 j j j個相鄰點, i − 1 , i , i + 1 {i-1,i,i+1} i1,i,i+1的選取狀態為 k k k的方案數。

最後的答案要算上沒選的點的任意選取的排列個數,即:
A n s = ∑ i = 0 n ( − 1 ) i f i ( n − i ) ! Ans=\sum_{i=0}^n(-1)^if_i(n-i)!

Ans=i=0n(1)ifi(ni)!

時間複雜度 O ( n 2 ) O(n^2) O(n2),據說也有 O ( n l g n ) O(nlgn) O(nlgn)的更麻煩做法。

Code

#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <string.h>
//#include <unordered_set>
//#include <unordered_map>
//#include <bits/stdc++.h>

#define MP(A,B) make_pair(A,B)
#define PB(A) push_back(A)
#define SIZE(A) ((int)A.size())
#define LEN(A) ((int)A.length())
#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define fi first
#define se second

using namespace std;

template<typename T>inline bool upmin(T &x,T y) { return y<x?x=y,1:0; }
template<typename T>inline bool upmax(T &x,T y) { return x<y?x=y,1:0; }

typedef long long ll;
typedef unsigned long long ull;
typedef long double lod;
typedef pair<int,int> PR;
typedef vector<int> VI;

const lod eps=1e-11;
const lod pi=acos(-1);
const int oo=1<<30;
const ll loo=1ll<<62;
const int mods=924844033;
const int MAXN=2005;
const int INF=0x3f3f3f3f;//1061109567
/*--------------------------------------------------------------------*/
inline int read()
{
	int f=1,x=0; char c=getchar();
	while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
	while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+(c^48); c=getchar(); }
	return x*f;
}
int num=0,h[MAXN],g[MAXN][MAXN][8],f[MAXN],fac[MAXN],tmp[MAXN];
int upd(int x,int y) { return x+y>=mods?x+y-mods:x+y; }
void Merge(int q)
{
	for (int j=0;j<=q;j++) 
	{
		h[j]=0;
		for (int t=0;t<4;t++) h[j]=upd(h[j],g[q][j][t]);
	}
	num+=q;
	for (int j=0;j<=num;j++) tmp[j]=0;
	for (int j=num;j>=0;j--)
		for (int k=0;k<=q;k++) 
			if (j>=k) tmp[j]=upd(tmp[j],1ll*f[j-k]*h[k]%mods);
	for (int j=0;j<=num;j++) f[j]=tmp[j];
}
signed main()
{
	int n=read(),p=read(),q=n/p+1;
	fac[0]=1;
	for (int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mods;
	
	g[1][0][0]=g[1][1][4]=1;
	for (int i=1;i<q;i++)
		for (int j=0;j<=i;j++)
		{
			for (int k=0;k<8;k++) g[i+1][j][k>>1]=upd(g[i+1][j][k>>1],g[i][j][k]);
			for (int k=0;k<8;k++)
			{
				if (!((k>>1)&1)) g[i+1][j+1][(k>>1)|1]=upd(g[i+1][j+1][(k>>1)|1],g[i][j][k]);
				if (!((k>>1)&4)) g[i+1][j+1][(k>>1)|4]=upd(g[i+1][j+1][(k>>1)|4],g[i][j][k]);
			}
		}
	f[0]=1;
	int r=n%p; 
	for (int i=1;i<=r;i++) Merge(q);
	for (int i=1;i<=p-r;i++) Merge(q-1);

	int ans=0;
	for (int i=0;i<=n;i++) 
		if (i&1) ans=upd(ans,mods-1ll*f[i]*fac[n-i]%mods);
		else ans=upd(ans,1ll*f[i]*fac[n-i]%mods);
	printf("%d\n",ans);
	return 0;
}