@atcoder - AGC035E@ Develop



@description@

給定初始集合為 1 ~ N 的全集,並給定一個 K。

每次對於當前集合 S,你可以選擇 S 中的一個元素 x,並將 x 從 S 中刪除。
假如 x - 2 在 1 ~ N 的范圍內且不在集合 S 中,在 S 中加入 x - 2。
假如 x + K 在 1 ~ N 的范圍內且不在集合 S 中,在 S 中加入 x + K。

求最后可以得到的不同集合數量 mod M。

Constraints
1≤K≤N≤150, 10^8≤M≤10^9

Input
輸入格式如下:
N K M

Output
輸出不同集合數量 mod M。

Sample Input 1
3 1 998244353
Sample Output 1
7

@solution@

考慮假如 x 與 x-2 最后都要被刪除,肯定應該先刪 x 再刪 x-2(先刪 x-2 的話,再刪 x 就又多出來一個 x-2)。

那么我們連邊 x -> x-2,x -> x+K,表示 x 比 x-2, x+K 先刪。
最后如果要求刪除的點形成一個環,肯定無解。否則我們按照拓撲序來刪必然是一個合法方案。
那么相當於對於這樣一個圖,有多少點集滿足點集內的點不形成環。

考慮與 K 無關的那些邊,會連成 1 <- 3 <- ... 與 2 <- 4 <- ... 兩條鏈,一條奇數,一條偶數。
接下來將 K 分奇偶討論,因為 K 是偶數時 x -> x+K 必然是在奇偶內部連邊,而 K 為奇數時可以跨奇偶連邊。

當 K 為偶數時,我們要避免 a -> a-2 -> ... a-K -> a 這樣的環,其實就是不能選擇超過 K/2 + 1 個連續點。
這個隨便怎么 dp 都可以。

當 K 為奇數時。注意到最小環必然恰好經過 2 條 K 邊(0,1 顯然,> 2 可以縮成 2 條)。
我們先將圖寫成類似以下形式(假設 K = 3):

其實就是對於每個奇數 x,將 x 與 x + K 放在同一層。
這樣有什么好處呢?注意到環的形式一定為 a -> a-2 -> ... b-K -> b -> b-2 -> ... a-K -> a(假設 a 為奇數),對應到圖上即從 a 開始往上走到某一點 b-K,再往右走到 b,再往上走到 a-K 的地方。
等價地說,假如這樣一條往上 + 往右 + 往上的路徑包含 > K + 1 個點,就會形成環。

注意到這樣一條路徑沒有往下的選擇,所以我們就可以從上到下 dp。
定義 dp(i, j, k) 表示前 i 層,往上 + 往右 + 往上的路徑包含 j 個點,右邊偶數的鏈對應往上的點連續選中了 k 個。
k 這一維是為了方便我們得到新的 j(可能在 i 這個點直接往右走)。
注意往上 + 往右 + 往上,兩個往上可以縮減成一個點,但必須要有往右的過程。

@accepted code@

#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 150;
int N, K, M;
int add(int a, int b) {return (a + b)%M;}
int mul(int a, int b) {return 1LL*a*b%M;}
int f[MAXN + 5][MAXN + 5];
void solve1() {
	K /= 2, f[0][0] = 1;
	for(int i=1;i<=N;i++) {
		for(int j=0;j<=K;j++)
			f[i][0] = add(f[i][0], f[i-1][j]);
		for(int j=0;j<K;j++)
			f[i][j+1] = add(f[i][j+1], f[i-1][j]);
	}
	int ans1 = 0, ans2 = 0;
	for(int i=0;i<=K;i++)
		ans1 = add(ans1, f[N/2][i]);
	for(int i=0;i<=K;i++)
		ans2 = add(ans2, f[(N+1)/2][i]);
	printf("%d\n", mul(ans1, ans2));
}
int g[2*MAXN + 5][MAXN + 5][MAXN + 5];
void solve2() {
	int p; g[0][0][0] = 1;
	for(int i=2;i-K<=N;i+=2) {
		for(int j=0;j<=N;j++)
			for(int k=0;k<=K+1;k++)
				g[i][0][0] = add(g[i][0][0], g[i-2][j][k]);
		if( i <= N ) {
			for(int j=0;j<=N;j++)
				for(int k=0;k<=K+1;k++)
					g[i][j+1][0] = add(g[i][j+1][0], g[i-2][j][k]);
		}
		if( i - K >= 1 ) {
			for(int j=0;j<=N;j++) {
				for(int k=1;k<=K;k++)
					g[i][0][k+1] = add(g[i][0][k+1], g[i-2][j][k]);
				g[i][0][0] = add(g[i][0][0], g[i-2][j][0]);
			}
		}
		if( i <= N && i - K >= 1 ) {
			for(int j=0;j<=N;j++)
				for(int k=0;max(k,j+1)<=K;k++)
					g[i][j+1][max(k+1,j+2)] = add(g[i][j+1][max(k+1,j+2)], g[i-2][j][k]);
		}
		p = i;
	}
	int ans = 0;
	for(int j=0;j<=N;j++)
		for(int k=0;k<=K+1;k++)
			ans = add(ans, g[p][j][k]);
	printf("%d\n", ans);
}
int main() {
	scanf("%d%d%d", &N, &K, &M);
	if( K % 2 == 0 ) solve1();
	else solve2();
}

@details@

感覺 AGC 好像很喜歡出這種狀態定義比較抽象,但是狀態轉移非常簡單的 dp 題。
比如 AGC039E,或者說 AGC037D 都是這種類型的 dp。

你以為你繞了半天寫出來的長代碼就是正解了?
拜托,正解根本不足 100 行.jpg。

但是做了這么多 AGC 的 dp 題還是不會 QAQ。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM