Logo wfirstzhang 的博客

博客

QOJ-12606(#741. C. 四方配额)Tutorial

avatar wfirstzhang 2026-08-27 16:21:08

QOJ-12606 题解

题意

给定四个整数 $A,B,C,D$,求有多少个不同的四元组 $(a,b,c,d)$ 满足:

$$ 0\le a\le A,\quad 0\le b\le B,\quad 0\le c\le C,\quad 0\le d\le D $$

且

$$ a+c>b+d,\qquad a+d\ge b+c $$

答案对 $10^9+7$ 取模。


核心转化

令

$$ u=a-b,\qquad v=c-d $$

则两个条件等价于:

$$ u+v>0,\qquad u\ge v $$

如果 $u\le 0$,由 $v\le u$ 可得 $u+v\le 0$,矛盾,所以必须有:

$$ u>0 $$

于是对于每个固定的 $u>0$,$v$ 需要满足:

$ - u < v \le u $

因此答案为:

$$ \sum_{u=1}^{A} f(u)\cdot H(u) $$

其中:

  • $f(u)$ 表示满足 $a-b=u$ 的 $(a,b)$ 对数;
  • $H(u)$ 表示满足 $-u < c-d \le u$ 的 $(c,d)$ 对数。

计算 $f(u)$

对于 $u\ge 1$:

$$ f(u)=\#\{(a,b):0\le a\le A,\ 0\le b\le B,\ a-b=u\} $$

可得:

$$ f(u)=\min(A-u,B)+1 $$

它是一个分段函数:

  • 若 $A>B$:
    • 当 $1\le u\le A-B$ 时,$f(u)=B+1$,为常数;
    • 当 $A-B < u \le A$ 时,$f(u)=A-u+1$,为一次函数。
  • 若 $A\le B$:
    • 当 $1 \le u \le A$ 时,$f(u)=A-u+1$,为一次函数。

计算 $H(u)$

设总对数:

$$ N=(C+1)(D+1) $$

用容斥计算 $H(u)$:

$$ H(u)=N-Q(C,D,u+1)-Q(D,C,u) $$

其中

$$ Q(M,N,k)=\#\{(x,y):0\le x\le M,\ 0\le y\le N,\ x-y\ge k\} $$

这里:

  • $Q(C,D,u+1)$ 排除 $c-d>u$ 的情况;
  • $Q(D,C,u)$ 排除 $c-d\le -u$ 的情况。

计算 $Q(M,N,k)$

当 $k>M$ 时,$Q=0$。

否则令:

$$ L=M-k+1 $$

有:

$$ Q(M,N,k)=\sum_{y=0}^{\min(N,L)}(L-y) $$

化简为:

$$ Q(M,N,k)=(\min(N,L)+1)L-\frac{\min(N,L)(\min(N,L)+1)}2 $$

这是一个关于 $k$ 的分段函数:

  • 当 $L\ge N$,即 $k\le M-N+1$ 时,$\min(N,L)=N$,$Q$ 是 $k$ 的一次函数;
  • 当 $LM-N+1$ 时,$\min(N,L)=L$,$Q$ 是 $k$ 的二次函数。

由于本题中 $k$ 是 $u+1$ 或 $u$,所以 $Q$ 也是关于 $u$ 的低次多项式。


多项式求和

$f(u)$ 是零次或一次多项式,$H(u)$ 是零次、一次或二次多项式,因此:

$$ f(u)\cdot H(u) $$

在每一段上是次数不超过 $3$ 的多项式。

把所有分段点(包括 $f$ 的断点、两个 $Q$ 函数的线性/二次分界点、归零点,以及 $1$ 和 $A+1$)排序,相邻两个断点之间是一段。对每一段直接使用幂和公式:

$$ \sum_{i=1}^{n}1,\quad \sum_{i=1}^{n}i,\quad \sum_{i=1}^{n}i^2,\quad \sum_{i=1}^{n}i^3 $$

即可 $O(1)$ 求出这一段的和。

所有计算均在模 $10^9+7$ 下进行。


复杂度

每组数据只需要常数个分段,因此:

$$ O(1)\ \text{per test case} $$

总复杂度为 $O(T)$,可以轻松通过 $T\le 1000,\ A,B,C,D\le 10^{18}$。


参考代码

#include <bits/stdc++.h>
using namespace std;

using int64 = long long;
using i128 = __int128_t;

const int64 MOD = 1000000007LL;
const int64 INV2 = 500000004LL;
const int64 INV6 = 166666668LL;

int64 md_norm(int64 x) {
    x %= MOD;
    if (x < 0) x += MOD;
    return x;
}
int64 md_add(int64 a, int64 b) { return md_norm(a + b); }
int64 md_sub(int64 a, int64 b) { return md_norm(a - b); }
int64 md_mul(int64 a, int64 b) { return (int64)((i128)a * b % MOD); }

struct Poly {
    int64 c[4];
    Poly(int64 a = 0, int64 b = 0, int64 c2 = 0, int64 c3 = 0) {
        c[0] = md_norm(a);
        c[1] = md_norm(b);
        c[2] = md_norm(c2);
        c[3] = md_norm(c3);
    }
};

Poly poly_add(const Poly &a, const Poly &b) {
    Poly r;
    for (int i = 0; i < 4; ++i) r.c[i] = md_add(a.c[i], b.c[i]);
    return r;
}

Poly poly_sub(const Poly &a, const Poly &b) {
    Poly r;
    for (int i = 0; i < 4; ++i) r.c[i] = md_sub(a.c[i], b.c[i]);
    return r;
}

Poly poly_mul(const Poly &a, const Poly &b) {
    Poly r;
    for (int i = 0; i < 4; ++i) {
        if (!a.c[i]) continue;
        for (int j = 0; j < 4; ++j) {
            if (!b.c[j]) continue;
            if (i + j < 4) r.c[i + j] = md_add(r.c[i + j], md_mul(a.c[i], b.c[j]));
        }
    }
    return r;
}

int64 sum1(int64 n) {
    if (n <= 0) return 0;
    return md_mul(md_mul(md_norm(n), md_norm(n + 1)), INV2);
}
int64 sum2(int64 n) {
    if (n <= 0) return 0;
    return md_mul(md_mul(md_mul(md_norm(n), md_norm(n + 1)), md_norm(2 * n + 1)), INV6);
}
int64 sum3(int64 n) {
    if (n <= 0) return 0;
    int64 s = sum1(n);
    return md_mul(s, s);
}

int64 poly_sum(const Poly &p, int64 n) {
    if (n <= 0) return 0;
    int64 res = 0;
    res = md_add(res, md_mul(p.c[0], md_norm(n)));
    res = md_add(res, md_mul(p.c[1], sum1(n)));
    res = md_add(res, md_mul(p.c[2], sum2(n)));
    res = md_add(res, md_mul(p.c[3], sum3(n)));
    return res;
}

int64 range_sum(const Poly &p, int64 L, int64 R) {
    if (L > R) return 0;
    return md_sub(poly_sum(p, R), poly_sum(p, L - 1));
}

// Q(M,N,k) = # {(x,y): 0<=x<=M, 0<=y<=N, x-y >= k}, k >= 1.
// Returns it as a polynomial in u, where k = u + off.
Poly get_Q_poly(int64 M, int64 N, int64 off, int64 u) {
    int64 k = u + off;
    if (k <= 0 || k > M) return Poly(0);

    if (M >= N && k <= M - N + 1) {
        // linear part: (N+1)*(M-k+1) - N*(N+1)/2
        int64 c1 = md_norm(-(N + 1));
        int64 c0 = md_sub(md_mul(md_norm(N + 1), md_norm(M - off + 1)),
                          md_mul(md_mul(md_norm(N), md_norm(N + 1)), INV2));
        return Poly(c0, c1);
    } else {
        // quadratic part: (M-k+1)*(M-k+2)/2, let s=M-off+1, then it is (s-u)(s-u+1)/2
        int64 s = M - off + 1;
        int64 c2 = INV2;
        int64 c1 = md_mul(md_norm(-(2 * s + 1)), INV2);
        int64 c0 = md_mul(md_mul(md_norm(s), md_norm(s + 1)), INV2);
        return Poly(c0, c1, c2);
    }
}

// f(u) = # {(a,b): 0<=a<=A, 0<=b<=B, a-b=u}, u>=1.
Poly get_f_poly(int64 A, int64 B, int64 u) {
    if (A > B && u <= A - B) return Poly(B + 1);
    return Poly(A + 1, -1); // A - u + 1
}

int64 solve_one(int64 A, int64 B, int64 C, int64 D) {
    if (A <= 0) return 0;

    vector<int64> pts;
    pts.push_back(1);
    pts.push_back(A + 1);

    // Breakpoint of f(u).
    if (A > B && A - B + 1 <= A) pts.push_back(A - B + 1);

    auto add_Q_breakpoints = [&](int64 M, int64 N, int64 off) {
        if (M <= 0) return;
        int64 zero_start = M - off + 1; // first u with Q = 0
        if (zero_start >= 1 && zero_start <= A + 1) pts.push_back(zero_start);
        if (M >= N) {
            int64 b = M - N + 2 - off; // first u of the quadratic part
            if (b >= 1 && b <= A + 1) pts.push_back(b);
        }
    };

    // Breakpoints of H(u) = total - Q(C,D,u+1) - Q(D,C,u).
    add_Q_breakpoints(C, D, 1);
    add_Q_breakpoints(D, C, 0);

    sort(pts.begin(), pts.end());
    pts.erase(unique(pts.begin(), pts.end()), pts.end());

    int64 ans = 0;
    for (size_t i = 0; i + 1 < pts.size(); ++i) {
        int64 L = pts[i];
        int64 R = pts[i + 1] - 1;
        if (L < 1) L = 1;
        if (R > A) R = A;
        if (L > R) continue;

        Poly f = get_f_poly(A, B, L);
        int64 total = (int64)((i128)(C + 1) * (D + 1) % MOD);
        Poly H = poly_sub(poly_sub(Poly(total), get_Q_poly(C, D, 1, L)),
                          get_Q_poly(D, C, 0, L));

        ans = md_add(ans, range_sum(poly_mul(f, H), L, R));
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;
    cin >> T;
    while (T--) {
        int64 A, B, C, D;
        cin >> A >> B >> C >> D;
        cout << solve_one(A, B, C, D) << '\n';
    }
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

wfirstzhang Avatar
密码是111111
活跃度 近 53 周
查看全年