[BZOJ4513][SDOI2016]储能表(数位DP/分治乱搞)

发现大家都用数位DP
这里讲一种利用了分治思想的乱搞方法。
先假设现在要求 n1i=0m1j=0max((i xor j)k,0) 。设 n>m
先求得 n1 在二进制意义下的位数,减 1 后记为x。分以下两种情况考虑:
(1) m2x 。这时候将问题分为两个部分:
1、 [0,2x) 内的数与 [0,m) 内的数互相异或。
2、 [2x,n) 内的数与 [0,m) 内的数互相异或。
对于第一个部分,由于 a xor b=c a xor c=b ,并且 m2x ,所以 [0,2x) 内的数异或上任意一个 [0,m) 内的数后,得到的结果仍然是 [0,2x)
因此第一部分的答案为 m2xk1i=0i 2xk1i=0i 可以用公式计算。
第二部分则递归处理( [2x,n) 内的数与 [0,m) 内的数互相异或相当于 [0,n2x) 内的数与 [0,m) 内的数互相异或再加 2x ),但要分两种情况:
1、 k2x ,则容易得到,递归到 n2x1i=0m1j=0max((i xor j)(k2x),0)
2、 k<2x ,第二部分所有的 i xor j 都不会小于 k
此时递归到n2x1i=0m1j=0max(i xor j,0),并且加上 m(2xk)(n2x)
(2) m>2x 。此时分为四个部分(互相异或):
1、 [0,2x),[0,2x)
2、 [2x,n),[0,2x)
3、 [0,2x),[2x,m)
4、 [2x,n),[2x,m)
容易推出,第一部分的结果为 2x2xk1i=0 ,第四部分的结果由于最高位被异或掉就变成了 0 ,所以最后一部分实际上就是[0,n2x),[0,m2x)(递归处理)。而中间的两部分,可以通过 k 2x的大小关系来分情况计算。和 m2x 的情况里计算第二部分的方法基本相似。
感觉数位DP好像更好做一些。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int PYZ;
typedef long long ll;
int sum_x(ll n) {
    if (n == -1) return 0;
    if (n & 1) return 1ll * (n % PYZ) * ((n + 1 >> 1) % PYZ) % PYZ;
    else return 1ll * ((n >> 1) % PYZ) * ((n + 1) % PYZ) % PYZ;
}
int sum(ll l, ll r) {
    if (l > r) return 0;
    return (sum_x(r) - sum_x(l - 1) + PYZ) % PYZ;
}
int solve(ll n, ll m, ll K) {
    if (n < m) swap(n, m);
    if (m == 1) return sum(0, n - 1 - K);
    ll x = n - 1; int tot = 0, res; while (x) tot++, x >>= 1;
    ll mid = 1ll << tot - 1;
    if (m <= mid) {
        res = 1ll * (m % PYZ) * sum(0, mid - 1 - K) % PYZ;
        if (K >= mid) (res += solve(n - mid, m, K - mid)) %= PYZ;
        else {
            (res += solve(n - mid, m, 0)) %= PYZ;
            (res += 1ll * ((mid - K) % PYZ) * ((n - mid) % PYZ) % PYZ
                * (m % PYZ) % PYZ) %= PYZ;
        }
        return res;
    }
    res = 1ll * (mid % PYZ) * sum(0, mid - 1 - K) % PYZ;
    (res += solve(n - mid, m - mid, K)) %= PYZ; x = (n - mid + m - mid) % PYZ;
    if (K <= mid) {
        (res += 1ll * x * sum(0, mid - 1) % PYZ) %= PYZ;
        (res += 1ll * x * (mid % PYZ) % PYZ *
            ((mid - K) % PYZ) % PYZ) %= PYZ;
    }
    else (res += 1ll * x * sum(0, (mid << 1) - 1 - K) % PYZ) %= PYZ;
    return res;
}
void work() {
    ll n, m, K; scanf("%lld%lld%lld%d", &n, &m, &K, &PYZ);
    printf("%d\n", solve(n, m, K));
}
int main() {
    int T; cin >> T; while (T--) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值