二次元音游人

这是一股照亮混沌的令和时代互联网的一道光,给在电子的海洋里冲浪的阿宅们带来笑容

2025CCPC郑州部分题解

個、個、個、この世で個っ端みじんに壊れた個
个、个、个、在这世上碎成一“个个”渣的“个”

比赛链接

J. Subrectangle Count

给定一个长度为 nn ≥ 2)的整数序列 a₁, a₂, ..., aₙ,和一个长度为 mm ≥ 2)的整数序列 b₁, b₂, ..., bₘ。我们可以构造一个 n × m 的矩阵 c,其中矩阵元素 cᵢ,ⱼ = aᵢ ⊕ bⱼ1 ≤ i ≤ n, 1 ≤ j ≤ m),这里的 表示按位异或运算

请计算有多少对 (i, j)1 ≤ i < n, 1 ≤ j < m)满足:矩阵 c 中由 (i, j) 为左上角的 2×2 子矩形:
image

的形式为:
image

这等价于同时满足以下三个条件:

  1. cᵢ,ⱼ₊₁ = cᵢ,ⱼ + 1
  2. cᵢ₊₁,ⱼ = cᵢ,ⱼ + 2
  3. cᵢ₊₁,ⱼ₊₁ = cᵢ,ⱼ + 3

输入
第一行包含一个整数 T1 ≤ T ≤ 10⁵),表示测试用例的数量。

对于每个测试用例:

  • 第一行包含两个用空格分隔的整数 n, m2 ≤ n, m ≤ 2 × 10⁵),表示矩阵 c 的行数和列数。
  • 第二行包含 n 个用空格分隔的整数,表示序列 a₁, a₂, …, aₙ0 ≤ aᵢ < 2³⁰)。
  • 第三行包含 m 个用空格分隔的整数,表示序列 b₁, b₂, …, bₘ0 ≤ bᵢ < 2³⁰)。

保证所有测试用例的 n 之和不超过 2 × 10⁵,所有测试用例的 m 之和不超过 2 × 10⁵

输出
对于每个测试用例,输出一行,包含一个整数,表示满足条件的 (i, j) 对数。


题解

\(x=c_{i,j}\),那么我们可以得到一些式子:

  • \(x \oplus x+1=c_{i,j} \oplus c_{i,j+1}=(a_i\oplus b_j) \oplus (a_{i}\oplus b_{j+1})=b_j\oplus b_{j+1}\)

  • \(x \oplus x+2=c_{i,j} \oplus c_{i+1,j}=(a_i\oplus b_j) \oplus (a_{i+1}\oplus b_{j})=a_i\oplus a_{i+1}\)

  • \(x+1 \oplus x+3=c_{i,j+1} \oplus c_{i+1,j+1}=(a_{i}\oplus b_{j+1}) \oplus (a_{i+1}\oplus b_{j+1})=a_i\oplus a_{i+1}\)

  • \(x+2 \oplus x+3=c_{i+1,j} \oplus c_{i+1,j+1}=(a_{i+1}\oplus b_j) \oplus (a_{i+1}\oplus b_{j+1})=b_j\oplus b_{j+1}\)

考虑 \(x\oplus (x+1)=(x+2)\oplus(x+3)=b_j\oplus b_{j+1}=D_b\)

\(D_b\) 的性质

  • \(x\) 是偶数的时候,\((x+1)\)第一位是 \(1\) ,所以 \(x\oplus (x+1)=1=D_b\),同理 \((x+2)\) 也是偶数,可得 \((x+2)\oplus (x+3)=1=D_b\)

  • \(x\) 是奇数的时候,假设 \(x=011_{(2)}\),那么 \(x+1=100_{(2)},x+2=101_{(2)},x+3=110_{(2)}\)\(x\oplus (x+1)=111_{(2)}\neq (x+2)\oplus (x+3)=011_{(2)}\),易得 \(x\) 不能是奇数。

因此 \(x\) 必须是偶数,并且 \(D_b=1\)

考虑完了关于 \(b\) 数组性质后,接下来考虑 \(a\) 数组性质。

\(D_a\) 表示 \(x\oplus (x+2)\)

\(D_a\) 的性质

既然 \(x=2k\),不妨设 \(D_a=x\oplus(x+2)=2k\oplus (2k+2)=2(k\oplus(k+1))\)\(k\oplus (k+1)\) 必须是形如 \(2^p-1\),所以 \(D_a=x\oplus (x+2)=2^{p+1}-2\),设 \(D_a=x\oplus (x+2)=2^v-2\),那么稍加推理可以得到 \(x\) 的形状是 \(x\equiv 2^{v-1}-2\pmod{2^v}\).

image

算法步骤:

  1. 统计所有满足 \(b_j \oplus b_{j+1} = 1\) 的下标 \(j\)
  2. 遍历所有可能的 \(v \in [2, 30]\)。对于每一个 \(v\),找出所有满足 \(a_i \oplus a_{i+1} = 2^v - 2\) 的下标 \(i\)
  3. 利用公式 \(x \equiv 2^{v-1}-2 \pmod{2^v}\),由于 \(x = a_i \oplus b_j\),该条件等价于:

\[b_j \pmod{2^v} = (a_i \pmod{2^v}) \oplus (2^{v-1}-2) \]

  1. 使用排序配合二分查找来快速统计满足条件的 \((i, j)\) 数量。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
const int N=2e5+5;
int a[N],b[N];
inline void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=m;i++){
        cin>>b[i];
    }
    vector<int> posj;
    for(int j=1;j<m;j++){
        if((b[j]^b[j+1])==1){
            posj.push_back(j);
        }
    }
    vector<int> aa[32];
    for(int i=1;i<n;i++){
        int Da=a[i]^a[i+1];
        int tar=Da+2;
        if(tar>=4&&(tar&(tar-1))==0){//tar是2的次方
            int v=0;
            while(tar>1){
                tar>>=1;
                v++;
            }
            aa[v].push_back(i);
        }
    }
    int ans=0;
    for(int v=2;v<=30;v++){
        if(aa[v].empty()||posj.empty()) continue;

        int mod=(1<<v);
        vector<int> bmods;
        for(int j:posj){
            bmods.push_back(b[j]%mod);
        }
        sort(bmods.begin(),bmods.end());
        int xmask=(1<<(v-1))-2;
        for(int i:aa[v]){
            int tarbmod=(a[i]%mod)^xmask;//套用公式计算b的应该的值
            auto range=equal_range(bmods.begin(),bmods.end(),tarbmod);//在升序数组中统计bmods中和tarbmod值相等的区间,左开右闭
            ans+=distance(range.first,range.second);//区间范围长度
        }

    }
    cout<<ans<<'\n';
    return;
}
signed main(void){
    cin.tie(NULL)->sync_with_stdio(false);
    int T=1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

多理解

I. Dumb Problem II

题目大意:

  1. 前缀最大值集合 \(g(p)\):对于一个长度为 \(n\) 的排列 \(p\),如果某个元素 \(p_i\) 比它之前的所有元素都大,那么 \(p_i\) 就是一个前缀最大值。\(g(p)\) 是该排列中所有这类数值组成的集合。
  2. 随机过程:独立且均匀地随机选择 \(k\) 个排列 \(p_1, p_2, \dots, p_k\)
  3. 目标:求这 \(k\) 个排列所产生的 \(k\) 个集合 \(\{g(p_1), g(p_2), \dots, g(p_k)\}\) 中,不同集合的期望个数。
  4. 数据范围:\(n, k \le 5000\),答案对 \(998244353\) 取模。

题解

假设得到的前缀最大值集合是 \(S\)。我们从大到小插入数字 \(x=n,n-1,\dots 2,1\),有两种情况:

  • \(x \in S\),因为是从大到小插入,所以当前的 \(x\) 必须得排在所有已经插入的数字的最前面,只有 \(1\) 种情况。

  • \(x \notin S\),插入 \(x\) 时,前面已经插入了 \(n-x\) 个数,当前 \(x\) 不能是最大值,所以不能插在最前面,后面都可以插,共有 \(n-x\) 种可能。

因此,产生集合 \(S\) 的排列个数为 \(f(S) = \prod_{x \notin S} (n-x)\)

单个排列拥有集合 \(S\) 的概率为 \(P(S) = \frac{f(S)}{n!}\)

然后随机选择了 \(k\) 个集合,要求不同集合的期望数。也就是对于所有集合 \(S\),在抽到的 \(k\) 个排列中至少出现一次的概率之和。

\[E = \sum_{S} P(S \text{ 在 } k \text{ 个排列中至少出现一次}) \]

\[E = \sum_{S} (1-(1-P(S))^k) \]

利用公式 \((1-x)^k = \sum_{i=0}^k \binom{k}{i} (-1)^i x^i\),上式化为:

\[E = \sum_{S} (1- \sum_{i=0}^{k} \binom{k}{i} (-1)^i P(S)^i) \]

发现当 \(i=0\) 时,\(\binom{k}{i} (-1)^i P(S)^i=1\),所以原式继续化简:

\[E = \sum_{S} (\sum_{i=1}^{k} \binom{k}{i} (-1)^{i-1} P(S)^i) \]

交换求和符号:

\[E = \sum_{i=1}^{k} \binom{k}{i} (-1)^{i-1} \sum_{S} P(S)^i \]

现在想办法求出 \(g_i=\sum_{S} P(S)^i\)

\[g_i=\sum_{S} P(S)^i=\sum_{S} (\frac{f(S)}{n!})^i \]

\[g_i=\frac{1}{(n!)^i}\sum_{S} f(S)^i \]

\[g_i=\frac{1}{(n!)^i}\sum_{S} \prod_{x\notin S}(n-x)^i \]

可以把这个对每个集合单独考虑变成对每个元素单独考虑。对于元素 \(x\),如果 \(x \in S\),那么贡献 \(1\),如果 \(x \notin S\),那么贡献 \((n-x)^i\)

\[g_i=\frac{1}{(n!)^i} \prod_{x=1}^{n} (1+(n-x)^i)=\frac{1}{(n!)^i} \prod_{j=0}^{n-1} (1+j^i) \]

所以我们计算最终结果:

\[E=\sum_{i=1}^{k} \binom{k}{i}(-1)^{i-1}g_i=\sum_{i=1}^{k} \binom{k}{i}(-1)^{i-1}\frac{1}{(n!)^i} \prod_{j=0}^{n-1} (1+j^i) \]

由于 \(n, k \le 5000\),我们可以用 \(O(nk)\) 的复杂度计算所有的 \(g_i\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
const int N=5005;
int fact[N];
const int mod=998244353;
inline int qpow(int a,int xx){
    int res=1;
    while(xx){
        if(xx&1){
            res=(res*a)%mod;
        }
        a=a*a%mod;
        xx>>=1;
    }
    return res;
}
inline int inv(int x){
    return qpow(x,mod-2)%mod;
}
inline int C(int n,int m){
    if(m<0||m>n) return 0;
    return ((fact[n]*inv(fact[m])%mod)*inv(fact[n-m])%mod)%mod;
}
signed main(void){
    cin.tie(NULL)->sync_with_stdio(false);
    cin>>n>>k;
    fact[0]=1;
    for(int i=1;i<=max(n,k);i++){
        fact[i]=fact[i-1]*i%mod;
    }
    vector<int> g(k+1);
    vector<int> cur(k+1,1);
    for(int i=1;i<=k;i++){
        int tmp=1;
        for(int j=0;j<n;j++){
            tmp=tmp*(1+qpow(j,i))%mod;
        }
        g[i]=tmp*qpow(inv(fact[n]),i)%mod;
    }
    int ans=0;
    for(int i=1;i<=k;i++){
        int tmp=(C(k,i)*g[i])%mod;
        if(i&1){
            ans=(ans+tmp)%mod;
        }
        else{
            ans=(ans-tmp+mod)%mod;
        }
    }
    cout<<ans<<'\n';
    return 0;
}

补充

首先,考虑一个更通用的多项式展开。假设我们有一组数 \(B_1, B_2, \dots, B_n\),观察以下乘积的展开过程:

\[(1 + B_1)(1 + B_2)\dots(1 + B_n) = \prod_{x=1}^n (1 + B_x) \]

当我们把这个乘积完全展开时,得到的每一项都是从这 \(n\) 个括号中各选一个元素(要么选 \(1\),要么选 \(B_x\))相乘的结果。

  • 如果对于某个元素 \(x\),我们选择了 \(B_x\),则认为 \(x \notin S\)
  • 如果选择了 \(1\),则认为 \(x \in S\)

因此,展开后的每一项都对应一个唯一的集合 \(S \subseteq \{1, \dots, n\}\),其表达式为 \(\prod_{x \notin S} B_x\)。整个展开式的和就是:

\[\prod_{x=1}^n (1 + B_x) = \sum_{S \subseteq \{1, \dots, n\}} \prod_{x \notin S} B_x \]

G.Plus Xor

题意

给定 \(a,b,c\) ,判定能否通过将 \(a\) 加上 \(b\) 或者异或上 \(b\) 的操作变成 \(c\)

题解

由于 \(b\) 的范围很小(\(b \le 1000\)),而 \(a\)\(c\) 的范围很大(\(10^{18}\)),我们需要找到一种能够有效缩减状态空间的方法。

如果我们可以通过一系列操作到达某个值 \(a'\),只要满足以下两个条件,就一定能到达 \(c\)

  • \(a' \equiv c \pmod b\)
  • \(a' \le c\)

直接在 \(10^{18}\) 的空间搜索是不可能的。我们需要观察 \(a \leftarrow a \oplus b\) 这个操作对 \(a\) 的影响:

  • 位运算的影响范围:令 \(b\) 在二进制下有 \(L\) 位(即 \(2^{L-1} \le b < 2^L\))。\(a \oplus b\) 只会改变 \(a\) 的低 \(L\) 位。
  • 数值的变化量:\(a\) 异或 \(b\) 后,新值与旧值的差额 \(\Delta = (a \oplus b) - a\)

这个差额 \(\Delta\) 仅仅取决于 \(a\) 的低 \(L\) 位(即 \(a \pmod{2^L}\))。因此,为了追踪 \(a\) 的变化以及它对模 \(b\) 余数的影响,我们只需要记录两个信息:

  • \(x = a \pmod{2^L}\):决定了下一次异或操作会带来多大的数值增量。
  • \(y = a \pmod b\):记录当前的余数。

我们将 \((x, y)\) 视为图中的一个节点。状态总数为 \(2^L \times b\)。由于 \(b \le 1000\)\(L \approx 10\),总状态数约为 \(1024 \times 1000 \approx 10^6\),在可接受范围内。

转移方程:

  1. 加法操作 (\(a+b\)):
  • 新状态:\(((x + b) \pmod{2^L}, y)\)
  • \(a\) 的变化:\(a=(a + b)\)
  1. 异或操作 (\(a \oplus b\)):
  • 新状态:\(((x \oplus b) \pmod{2^L}, (y + (x \oplus b) - x) \pmod b)\)
  • \(a\) 的变化:\(a=(a \oplus b)\)

我们要找的是到达余数状态 \((c \pmod{2^L}, c \pmod b)\) 时,\(a\)最小值

算法

直接使用 \(\text{Dijkstra}\) 算法从起点 \((a \mod{2^L},a \mod{b})\) 开始跑单源最短路。最后找终点 \((c \mod{2^L}, c \mod b)\) 的值是否可行。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int a, b, c;
map<pair<int, int>, int> mp;
struct node{
    int A,B,C;
    friend bool operator<(node A,node B){
        return A.C>B.C;
    }
};
inline void solve()
{
  cin >> a >> b >> c;
  mp.clear();
  // vis.clear();
  int L = 0;
  for (int i = 15; i >= 0; i--)
  {
    if (b >> i & 1)
    {
      L = i;
      break;
    }
  }
  L++;
  int Mod = (1LL << L);
  node st = {a % Mod, a % b,a};
  pair<int,int> st1={a % Mod, a % b};
  priority_queue<node> q;
  
  q.push(st);
  
  mp[st1] = a;
  while (q.size())
  {
      auto xx = q.top();
      
      q.pop();
      
      
      pair<int,int> xxx={xx.A,xx.B};
    int T1=mp[xxx]+b,T2=mp[xxx]^b;
    int x = xx.A, y = xx.B;
    pair<int, int> tmp1 = {(x + b) % Mod, y};
    pair<int, int> tmp2 = {(x ^ b) % Mod, ((y + (x ^ b) - x) + b) % b};
    node tmp11 = {(x + b) % Mod, y,T1};
    node tmp22 = {(x ^ b) % Mod, ((y + (x ^ b) - x) + b) % b,T2};
    
    if(!mp[tmp1]||mp[tmp1]>T1){
      mp[tmp1] = T1;
      q.push(tmp11);
    }
    if(!mp[tmp2]||mp[tmp2]>T2){
      mp[tmp2] = T2;
      q.push(tmp22);
    }
  } if (mp[{c % Mod, c % b}] && mp[{c % Mod, c % b}] <= c && ((c - mp[{c % Mod, c % b}]) % b == 0))
  {
    cout << "YES\n";
  }
  else
  {
    cout << "NO\n";
  }

  return;
}
signed main(void)
{
  cin.tie(NULL)->sync_with_stdio(false);
  int T = 1;
  cin >> T;
  while (T--)
  {
    solve();
  }
  return 0;
}
posted @ 2026-04-21 01:52  超绝最可爱天使酱  阅读(43)  评论(0)    收藏  举报