【AGC009 E】Eternal Average(DP)

题面

🔗

黑板上有 n n n 0 0 0 m m m 1 1 1,我们每次选择 k k k 个数字将其擦除,然后把它们的平均数写上去,这样一直操作直到只剩下一个数字,问剩下的这个数字有多少种不同的情况。

答案对 1 0 9 + 7 10^9+7 109+7 取模。

1 ≤ n , m ≤ 2000 , 2 ≤ k ≤ 2000 , ( k − 1 ) ∣ ( n + m − 1 ) 1\leq n,m\leq2000,2\leq k\leq2000,(k-1)|(n+m-1) 1n,m2000,2k2000,(k1)(n+m1)

题解

最终剩下的数字很可能是个分数,但是我们可以把它表示成一个 k k k 进制小数。

如果我们将 k k k 个 1 进行合成,可以浪费掉 k − 1 k-1 k1 个 1,将 k k k 个 0 进行合成,可以浪费掉 k − 1 k-1 k1 个 0 。并且,如果将两个小数进行合成,也一定会造成浪费( 2 k 2k 2k 个 0 或 1 组成了原本 k k k 个就可以表示的数)。

所以我们可以求所有 n ′ , m ′ n',m' n,m 个 0 和 1( n ′ = n − a ( k − 1 ) , m ′ = m − b ( k − 1 ) n'=n-a(k-1),m'=m-b(k-1) n=na(k1),m=mb(k1)不浪费组成的小数个数。不浪费就是指,黑板上有且仅有一个小数(在 ( 0 , 1 ) (0,1) (0,1) 内),每次合并将 k − 1 k-1 k1 个 0 或 1 与该小数合并,使得小数位右移一位( k k k 进制位),同时填充 1 k \frac{1}{k} k1 位为 0 ∼ k − 1 0\sim k-1 0k1 之间的数。

为了方便转移,我们令 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示总共用去 i i i 个 0 和 1,其中有 j j j 1 1 1不浪费结果数,转移如下:
d p [ k ] [ 0 ∼ k − 1 ] = 1 d p [ i ] [ j ] = ∑ d = 0 k − 1 d p [ i − k + 1 ] [ j − d ] dp[k][0\sim k-1]=1\\ dp[i][j]=\sum_{d=0}^{k-1}dp[i-k+1][j-d] dp[k][0k1]=1dp[i][j]=d=0k1dp[ik+1][jd]

于是很容易用前缀和优化做到 O ( n 2 ) O(n^2) O(n2)

CODE

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<random>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
#define MAXN 2005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB long double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
//#define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 1000000007;
int n,m,s,o,k;
int dp[MAXN<<1][MAXN];
int sm[MAXN];
int main() {
//	freopen("eternalaverage.in","r",stdin);
//	freopen("eternalaverage.out","w",stdout);
	m = read(); n = read(); k = read();
	if(m == 0 || n == 0) return AIput(1,'\n'),0;
	for(int i = 1;i < k;i ++) dp[k][i] = 1;
	for(int i = k+k-1;i <= n+m;i ++) {
		for(int j = 1;j <= n;j ++) sm[j] = (sm[j-1] + dp[i-k+1][j]) % MOD;
		for(int j = 1;j <= n && j <= i;j ++) {
			dp[i][j] = (sm[j]+MOD-sm[max(0,j-k)]) % MOD;
		}
	}
	int ans = 0;
	for(int i = n;i > 0;i -= (k-1)) {
		for(int j = m;j >= 0;j -= (k-1)) {
			(ans += dp[i+j][i]) %= MOD;
		}
	}
	AIput(ans,'\n');
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值