Atcoder agc022F

一个比较清真的计数DP,因为一个细节想了挺久。
我们发现可以把跳的过程看做一棵树, B B B的父亲为 A A A,我们需要不断把儿子合并到父亲,每次把 B B B合并到 A A A后坐标会变成 2 B − A 2B-A 2BA。那么考虑每个点对最终答案的贡献系数,是 2 深 度 ∗ ( − 1 ) 儿 子 数 目 + 从 父 亲 到 根 被 合 并 时 是 第 几 个 的 和 − 深 度 2^{深度}*(-1)^{儿子数目+从父亲到根被合并时是第几个的和-深度} 2(1)+。因为进制非常大,所以两个方案最终落到的位置不同当且仅当两个点的贡献系数不同。
考虑每一层的点数,每一层中贡献系数是 ( − 1 ) 奇 (-1)^奇 (1)的位置数目,那么就可以用组合数算出最终的方案数。但是那个 ( − 1 ) k (-1)^k (1)k贡献非常奇怪,我们先忽略掉深度,同时考虑儿子数目和从父亲到根被合并时是第几个的和不好做,不过我们发现其实可以不用管儿子数目的奇偶。如果我们已经知道了每一层的从父亲到根被合并时是第几个的和为奇数的点数和每层的点数,我们可以还原出每一层的实际贡献(假设第 i i i层从父亲到根被合并时是第几个的和为奇数的点数是 k k k,第 i + 1 i+1 i+1层奇与偶的差为 d ≥ 0 d\geq 0 d0,那么需要满足 k ≥ d k \geq d kd,并且第 i i i层真实贡献为奇数的点数目是 k − d k-d kd)。
于是我们随便DP一下就好了,我的实现是 O ( n 5 ) \mathcal O(n^5) O(n5)的,不过可以优化到 O ( n 4 ) \mathcal O(n^4) O(n4)甚至 O ( n 3 ) \mathcal O(n^3) O(n3)

#include <bits/stdc++.h>
#define MOD 1000000007

using namespace std;

typedef long long ll;

ll pow_mod(ll x,int k) {
  ll ans=1;
  while (k) {
  	if (k&1) ans=ans*x%MOD;
  	x=x*x%MOD;
  	k>>=1;
  }
  return ans;
}

inline void add(int &x,ll y) {
  x=(x+y)%MOD;
}

ll facd[55],facv[55];

void pre(int n) {
  facd[0]=1;
  for(int i=1;i<=n;i++) facd[i]=facd[i-1]*i%MOD;
  facv[n]=pow_mod(facd[n],MOD-2);
  for(int i=n-1;i>=0;i--) facv[i]=facv[i+1]*(i+1)%MOD;
}

int f[55][55][55];

int dp(int n) {
  f[1][1][0]=1;
  for(int i=1;i<n;i++)
  for(int j=0;j<=i;j++)
  for(int k=0;j+k<=i;k++)
    if (f[i][j][k]) {
    	for(int t1=0;i+t1<=n;t1++)
    	for(int t2=0;i+t1+t2<=n;t2++)
    	  if (t1||t2) {
    	  	if (t1>t2) {
    	  		if (t1-t2>j) continue;
    	  		add(f[i+t1+t2][t1][t2],f[i][j][k]*facv[j-(t1-t2)]%MOD*facv[k+(t1-t2)]);
			  }
			else {
				if (t2-t1>k) continue;
				add(f[i+t1+t2][t1][t2],f[i][j][k]*facv[j+(t2-t1)]%MOD*facv[k-(t2-t1)]);
			}
		  }
	}
  int ans=0;
  for(int i=0;i<=n;i++)
    for(int j=0;i+j<=n;j++)
      if (f[n][i][j]) add(ans,f[n][i][j]*facv[i]%MOD*facv[j]);
  return ans*facd[n]%MOD;
}

int main() {
  int n;
  scanf("%d",&n);
  pre(n);
  printf("%d\n",dp(n));
  return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值