dtoi2677「SDOI2016」储能表
题意:
有一个 $ n $ 行 $ m $ 列的表格,行从 $ 0 $ 到 $ n - 1 $ 编号,列从 $ 0 $ 到 $ m - 1 $ 编号。
每个格子都储存着能量。最初,第 $ i $ 行第 $ j $ 列的格子储存着 $ (i \mathbin{\text{xor}} j) $ 点能量。所以,整个表格储存的总能量是
$$ \sum\limits_{i = 0} ^ {n - 1} \sum\limits_{j = 0} ^ {m - 1} (i \mathbin{\text{xor}} j) $$
随着时间的推移,格子中的能量会渐渐减少。一个时间单位,每个格子中的能量都会减少 $ 1 $。显然,一个格子的能量减少到 $ 0 $ 之后就不会再减少了。
也就是说,$ k $ 个时间单位后,整个表格储存的总能量是
$$ \sum\limits_{i = 0} ^ {n - 1} \sum\limits_{j = 0} ^ {m - 1} \max((i \mathbin{\text{xor}} j) - k, 0) $$
给出一个表格,求 $ k $ 个时间单位后它储存的总能量。
由于总能量可能较大,输出时对 $ p $ 取模,多组数据。
$ T = 5000 $,$ n \leq 10 ^ {18} $,$ m \leq 10 ^ {18} $,$ k \leq 10 ^ {18} $,$ p \leq 10 ^ 9 $
题解:
先将问题稍加转化,只需要求有多少个 $(i,j)$ 满足异或值大于 $k$,以及这些位置的异或值之和即可。
然后发现数据范围非常大,考虑数位dp。$f[i][0/1][0/1][0/1]和g[i][0/1][0/1][0/1]$分别表示前 $i$ 位,行坐标小于还是等于 $n$,列坐标小于还是等于 $m$,当前的异或的值等于还是大于 $k$ 的总和以及方案数。分别枚举两个数字的下一位是 $0$ 还是 $1$ 即可转移。
#include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> using namespace std; int T,p,f[62][2][2][2],g[62][2][2][2]; long long n,m,K; int main() { scanf("%d",&T); while(T--) { scanf("%lld%lld%lld%d",&n,&m,&K,&p); memset(f,0,sizeof(f));memset(g,0,sizeof(g)); f[61][1][1][0]=0;g[61][1][1][0]=1; for (int i=61;i>=1;i--) for (int j=0;j<=1;j++) for (int k=0;k<=1;k++) for (int l=0;l<=1;l++) for (int a=0;a<=1;a++) for (int b=0;b<=1;b++) { int c,d,e; if ((1ll<<i-1)&n) { if (!a)c=0;else c=j; } else { if (j && a)continue;else c=j; } if ((1ll<<i-1)&m) { if (!b)d=0;else d=k; } else { if (k && b)continue;else d=k; } if ((1ll<<i-1)&K) { if (!l && !(a^b))continue;else e=l; } else { if (a^b)e=1;else e=l; } f[i-1][c][d][e]=((long long)f[i-1][c][d][e]+f[i][j][k][l]+g[i][j][k][l]*(a^b)*((1ll<<i-1)%p))%p; g[i-1][c][d][e]=(g[i-1][c][d][e]+g[i][j][k][l])%p; } printf("%lld\n",((f[0][0][0][1]-(long long)g[0][0][0][1]*(K%p)%p)%p+p)%p); } return 0; }