zoukankan      html  css  js  c++  java
  • @atcoder


    @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
    ", 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
    ", 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。

  • 相关阅读:
    1066 Bash 游戏
    1070 Bash 游戏 V4
    codevs 2928 你缺什么
    分块、线段树练习
    Father Christmas flymouse
    codevs 2494 Vani和Cl2捉迷藏
    codevs 2144 砝码称重2
    国王游戏
    codevs 1664 清凉冷水
    2015ACM/ICPC亚洲区沈阳站 Pagodas
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11795339.html
Copyright © 2011-2022 走看看