Generational Triplets

Generational Triplets

数位dp,位运算转换
cf2100

题目大意

给定整数 \(n\),求满足以下条件的整数三元组 \((a,b,c)\) 的数量:

  1. \(1 \le a < b < c \le n\)
  2. \(a,b,c\) 成等差数列,即 \(b-a = c-b\)
  3. \(a \oplus b \oplus c = 0\),其中 \(\oplus\) 表示按位异或

答案对 \(10^9+7\) 取模。

问题转化

由 \(a \oplus b \oplus c = 0\) 可得 \(b = a \oplus c\)。
又因为 \(a,b,c\) 成等差数列,所以 \(b = \frac{a+c}{2}\)。
于是得到:

\[a \oplus c = \frac{a+c}{2} \]

利用恒等式 \(a+c = (a \oplus c) + 2(a \& c)\),代入上式:

\[a \oplus c = \frac{(a \oplus c) + 2(a \& c)}{2} \]

化简得:

\[2(a \oplus c) = (a \oplus c) + 2(a \& c) \implies a \oplus c = 2(a \& c) \]

因此,原问题等价于:统计满足 \(1 \le a < c \le n\) 且 \(a \oplus c = 2(a \& c)\) 的整数对 \((a,c)\) 的数量。
每一个这样的 \((a,c)\) 唯一对应一个合法的三元组 \((a,b,c)\),其中 \(b = a \oplus c\)。

逐位分析

设 \(a\) 和 \(c\) 的第 \(i\) 位分别为 \(a_i, c_i\)。
由 \(a \oplus c = 2(a \& c)\) 可知,第 \(i\) 位的异或结果等于第 \(i-1\) 位的与结果:

\[(a \oplus c)_i = (a \& c)_{i-1} \]

也就是说,当前位的异或值由低一位的与值决定。
反过来,从高位向低位枚举时,当前位的与值必须等于高一位的异或值。
这样我们就把高低位之间的依赖关系反转了过来,使得每一位的决策只依赖于更高位的信息,满足无后效性,可以使用数位 DP。

数位 DP 设计

从高位到低位(第 \(60\) 位到第 \(0\) 位)枚举 \(a\) 和 \(c\) 的每一位。
因为 \(n \le 10^{18} < 2^{60}\),所以从第 \(60\) 位开始足够。

状态定义

\[dp[pos][tight][tightA][need] \]

  • \(pos\):当前处理的二进制位(从 \(60\) 递减到 \(0\))。
  • \(tight\):\(c\) 是否受到 \(n\) 的上限约束。
    若为 \(1\),说明 \(c\) 的前缀与 \(n\) 相同,当前位 \(c\) 不能超过 \(n\) 的当前位;
    若为 \(0\),说明 \(c\) 已经小于 \(n\),当前位 \(c\) 可以任意取 \(0\) 或 \(1\)。
  • \(tightA\):\(a\) 是否已经严格小于 \(c\)。
    若为 \(1\),说明 \(a\) 和 \(c\) 的前缀完全相同,当前位 \(a\) 不能大于 \(c\);
    若为 \(0\),说明之前已经出现 \(a < c\),后续位 \(a\) 可以任意取。
  • \(need\):当前位 \(a \& c\) 必须等于的值,由高一位的 \(a \oplus c\) 传递下来。

状态转移

对于当前位 \(pos\),令 \(up = tight ? ((n >> pos) \& 1) : 1\)。
枚举 \(c\) 从 \(0\) 到 \(up\),再枚举 \(a\) 从 \(0\) 到 \(t\),其中 \(t = tightA ? c : 1\)。
必须满足:

\[(a \& c) == need \]

然后计算:

  • \(newneed = a \oplus c\)(传递给下一位作为新的 \(need\))
  • \(ntight = tight \&\& (c == up)\)
  • \(nA = tightA \&\& (a == c)\)

递归调用:

\[res += dfs(pos - 1, ntight, nA, newneed) \]

累加后对 \(10^9+7\) 取模。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;

ll dp[66][2][2][2];
ll n;

ll dfs(int pos, int tight, int tightA, int need) {//pos位,tight n对c的限制,tightA c对a的限制,need 上一位XOR值
    if(pos < 0) return (!tightA && need == 0) ? 1 : 0;
    if(dp[pos][tight][tightA][need] != -1) return dp[pos][tight][tightA][need];

    int up = tight ? ((n >> pos) & 1) : 1;
    ll res = 0;
    for(int c = 0; c <= up; c ++) {
        int t = tightA ? c : 1;
        for(int a = 0; a <= t; a ++) {
            if((a & c) != need) continue;
            int newneed = a ^ c;
            int ntight = (tight && c == up);
            int nA = (tightA && a == c);

            res += dfs(pos - 1, ntight, nA, newneed);
            res %= MOD;
        }
    }
    dp[pos][tight][tightA][need] = res;
    return res;
}

void solve() {
    cin >> n;
    memset(dp, -1, sizeof(dp));
    cout << dfs(60, 1, 1, 0) << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-10-13 14:41  thenrify  阅读(7)  评论(0)    收藏  举报