题解 [AGC009E] Eternal Average
一开始想把贡献拆成被操作了 \(i\) 次的数贡献 \(\frac{val}{k^i}\)
然后就什么也不会了
正解首先需要一个神奇的转化
考虑每个操作方案都对应着一棵 \(k\) 叉树
其中有 \(n\) 个权值为 0 的叶子,有 \(m\) 个权值为 1 的叶子
其它每个非叶节点的权值是其儿子权值的平均值
这样根节点的权值就是最后剩下的那个数
考虑一棵 \(k\) 叉树合法的条件是什么
令 \(x_i\) 为每个 0 节点的深度,\(y_i\) 为每个 1 节点的深度
考虑若将所有 0 节点的权值改为 1,则根节点权值一定为 1
此时满足
\[\sum(\frac{1}{k})^{x_i}+\sum(\frac{1}{k})^{y_i}=1
\]
容易发现这是必要条件
考虑其充分性
因为最后和为 1,考虑 \(k\) 进制小数的进位
发现每个深度的节点都完成了凑齐 \(k\) 个节点形成一个上层节点的过程
那么确实是充分的
那么现在一个根节点的权值 \(z\) 合法的条件就变成了:
\(z\) 可以被表示成 \(\sum\limits_{i=1}^n(\frac{1}{k})^{x_i}\) 的形式且 \(1-z\) 可以被表示成 \(\sum\limits_{i=1}^m(\frac{1}{k})^{y_i}\) 的形式
\(z\) 和 \(1-z\) 好像反了但是没影响
- 与固定分数 \(\frac{1}{k}\) 的幂次相关的限制可以考虑 \(k\) 进制小数 DP?
注意小数 DP 需要记录最后一位是不是 0
尝试对 \(z\) 的 \(k\) 进制小数形式 \(0.z_1z_2\cdots z_l(z_l>0)\) 进行 DP
那么限制条件是什么呢?
如果不考虑进位有 \(\sum z_i=n\),加上进位就是
\[\sum z_i\leqslant n\and \sum z_i\equiv n\pmod {k-1}
\]
\(n\) 和 \(m\) 好像也反了但是问题不大
同理有
\[1+\sum(k-z_i-1)\leqslant m\and 1+\sum(k-z_i-1)\equiv m\pmod{k-1}
\]
对这个东西大力小数 DP 即可
复杂度 \(O(\frac{n+m-1}{k-1}nk)=O(n^2)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 4010
#define ll long long
//#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n, m, k;
const ll mod=1e9+7;
ll f[N][N][2], ans;
signed main()
{
n=read(); m=read(); k=read();
int lim=(n+m-1)/(k-1);
f[0][0][0]=1;
for (int i=0; i<=lim; ++i) {
for (int j=0; j<=min((k-1)*i, n); ++j) {
// if (f[i][j][0]) printf("f[%d][%d][%d]=%lld\n", i, j, 0, f[i][j][0]);
// if (f[i][j][1]) printf("f[%d][%d][%d]=%lld\n", i, j, 1, f[i][j][1]);
for (int t=0; t<k; ++t) {
if (j+t<=n && 1+(i+1)*(k-1)-j-t<=m) f[i+1][j+t][t!=0]=(f[i+1][j+t][t!=0]+f[i][j][0]+f[i][j][1])%mod;
}
}
}
for (int i=1; i<=lim; ++i)
for (int j=0; j<=min((k-1)*i, n); ++j)
if (j%(k-1)==n%(k-1) && ((1-j)%(k-1)+(k-1))%(k-1)==m%(k-1))
ans=(ans+f[i][j][1])%mod;
printf("%lld\n", ans);
return 0;
}