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;
}
posted @ 2021-02-16 16:47  1124828077ccj  阅读(61)  评论(0编辑  收藏  举报