做题笔记 02

1. P13834 【MX-X18-T6】「FAOI-R6」Voices of the Chord

https://www.luogu.com.cn/problem/P13834
题意:给定长度为 \(k\) 的正整数序列 \(b\),长度为 \(m\) 的集合序列 \(S\)\(S_i\) 内元素互不相同。在线维护长度为 \(n\) 的正整数序列 \(a\),支持操作:1. 给定 \(l,r,x\)\(\forall i\in[l,r],\forall j\in S_{b_i},a_j\leftarrow a_j+x\);2. 查询 \(\sum_{i\in[l,r]}a_i\)
\(n,m,k,q\leq 10^5,\sum |S_i|\leq 3\times 10^5\)

比较复杂的分块题。

Part 1. \(b\) 整块

\(b\) 分块,考虑整块部分,即 \(b\) 整块对 \(a\) 前缀的贡献,目标是可以 \(O(1)\) 查询。

求出 \(F_{i,j}\) 表示若一次 \(1\) 操作覆盖了 \(b\) 的第 \(i\) 个块(操作的 \(x=1\)),那么这次操作中 \(b\) 的第 \(i\) 个块对 \(a\) 前缀 \(j\) 的贡献系数。

求出 \(F\) 后整块的修改就可以绑定在 \(b\) 上了,具体的,维护 \(B_i\) 表示覆盖 \(b\) 上块 \(i\)\(1\) 操作 \(x\) 的和。

然后查询 \(a_{[l,r]}\) 的时候就可以枚举 \(b\) 中的块 \(i\),然后累加 \((F_{i,r}-F_{i,l-1})B_i\)

最后的问题是 \(F\) 怎么求。直接求的话复杂度容易爆炸,正确的做法是:枚举 \(b\)\(i\),求出块内对于每个 \(j\),集合 \(S_j\) 出现了几次,然后枚举 \(j\in[1,m]\)\(S_j\) 内的元素,乘这个集合的出现次数,贡献到 \(a\) 对应位置,最后前缀和一下即可。

Part 2. \(b\) 散块

\(T_i\) 表示 \(i\) 出现在了哪些 \(S\) 中,\(T_i=\{j|i\in S_j\}\)。容易得到 \(\sum |T|=\sum |S|\),是可以接受的。

此时的一个可行的做法是:将修改绑定到 \(S\) 上。那么现在的修改和查询形如:

  • \(\forall i\in[l,r], S_{b_i}\leftarrow S_{b_i}+x\)
  • \(\sum_{i\in[l,r]}\sum_{j\in T_i}S_j\)

\(T\) 按照下标顺序合并为一个数组,查询容易改为:

  • \(\sum_{i\in[l,r]} S_{a_j}\)

这个问题就比较可做了。具体的,首先按照 \(a\) 分块,然后还是考虑
\(b\) 的区间修改对 \(a\) 整块/散块的贡献。

此时修改操作的性质有:每次修改一段 \(b\) 下标连续段,长度小于块长。

Part 3. \(a\) 整块

求出 \(G_{i,j}\) 数组表示若进行一次 \(\forall i\in[1,j],S_{b_i}\leftarrow S_{b_i}+1\) 操作,\(a\) 的第 \(i\) 块的 \(\sum_{k\in Block(i)}S_{a_{k}}\),求的方法与 \(F\) 类似,同样注意不要使得复杂度退化。

然后每次修改时,可以直接枚举 \(a\) 的块然后贡献过去,具体的,修改 \(l,r,x\) 就可以枚举 \(i\),然后对 \(a\) 的第 \(i\) 块贡献 \(x(G_{i,r}-G_{i,l-1})\)

Part 4. \(a\) 散块

注意到此时是 \(b\) 散块对 \(a\) 散块贡献,直接记录即可。

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

const int B = 1000;

int n, K, m, q, b[100010], a[300010];
vector<int> S[100010], T[100010];
unsigned int F[400][100010], G[400][100010];
int Binb[100010], Ble[100010], Bri[100010];
int le[300010], ri[300010];
int Ainb[300010], Ale[300010], Ari[300010];
unsigned int SS[100010], BB[400], AA[400];
int Scnt[100010];

int main(){
  scanf("%d%d%d%d", &n, &K, &m, &q);
  for(int i = 1; i <= K; ++ i){
    scanf("%d", &b[i]);
    Binb[i] = (i - 1) / B + 1;
    if(Binb[i] != Binb[i-1]){
      Bri[Binb[i-1]] = i-1;
      Ble[Binb[i]] = i;
    }
  }
  Bri[Binb[K]] = K;
  for(int i = 1; i <= m; ++ i){
    int sz;
    scanf("%d", &sz);
    for(int j = 1; j <= sz; ++ j){
      int x;
      scanf("%d", &x);
      S[i].push_back(x);
      T[x].push_back(i);
    }
  }
  for(int i = 1; i <= Binb[K]; ++ i){
    for(int j = Ble[i]; j <= Bri[i]; ++ j){
      ++ Scnt[b[j]];
    }
    for(int j = 1; j <= m; ++ j){
      for(int k : S[j]){
        F[i][k] += Scnt[j];
      }
    }
    for(int j = 1; j <= n; ++ j){
      F[i][j] += F[i][j-1];
    }
    for(int j = Ble[i]; j <= Bri[i]; ++ j){
      -- Scnt[b[j]];
    }
  }
  for(int i = 1, la = 0; i <= n; ++ i){
    le[i] = la + 1;
    ri[i] = le[i] + T[i].size() - 1;
    la = ri[i];
    for(int j = le[i]; j <= ri[i]; ++ j){
      a[j] = T[i][j-le[i]];
    }
  }
  for(int i = 1; i <= ri[n]; ++ i){
    Ainb[i] = (i - 1) / B + 1;
    if(Ainb[i] != Ainb[i-1]){
      Ari[Ainb[i-1]] = i-1;
      Ale[Ainb[i]] = i;
    }
  }
  Ari[Ainb[ri[n]]] = ri[n];
  for(int i = 1; i <= Ainb[ri[n]]; ++ i){
    for(int j = Ale[i]; j <= Ari[i]; ++ j){
      ++ Scnt[a[j]];
    }
    for(int j = 1; j <= K; ++ j){
      G[i][j] += Scnt[b[j]] + G[i][j-1];
    }
    for(int j = Ale[i]; j <= Ari[i]; ++ j){
      -- Scnt[a[j]];
    }
  }
  unsigned int la = 0;
  while(q--){
    int op, l, r;
    unsigned int x;
    scanf("%d%d%d", &op, &l, &r);
    la &= 65535;
    l ^= la;
    r ^= la;
    if(op == 1){
      scanf("%u", &x);
      if(Binb[l] == Binb[r]){
        for(int i = l; i <= r; ++ i){
          SS[b[i]] += x;
        }
        for(int i = 1; i <= Ainb[ri[n]]; ++ i){
          AA[i] += x * (G[i][r] - G[i][l-1]);
        }
      } else {
        for(int i = l; i <= Bri[Binb[l]]; ++ i){
          SS[b[i]] += x;
        }
        for(int i = Ble[Binb[r]]; i <= r; ++ i){
          SS[b[i]] += x;
        }
        for(int i = 1; i <= Ainb[ri[n]]; ++ i){
          AA[i] += x * (G[i][Bri[Binb[l]]] - G[i][l-1]);
          AA[i] += x * (G[i][r] - G[i][Ble[Binb[r]]-1]);
        }
        for(int i = Binb[l] + 1; i < Binb[r]; ++ i){
          BB[i] += x;
        }
      }
    } else {
      la = 0;
      for(int i = 1; i <= Binb[K]; ++ i){
        la += BB[i] * (F[i][r] - F[i][l-1]);
      }
      l = le[l];
      r = ri[r];
      if(Ainb[l] == Ainb[r]){
        for(int i = l; i <= r; ++ i){
          la += SS[a[i]];
        }
      } else {
        for(int i = l; i <= Ari[Ainb[l]]; ++ i){
          la += SS[a[i]];
        }
        for(int i = Ale[Ainb[r]]; i <= r; ++ i){
          la += SS[a[i]];
        }
        for(int i = Ainb[l] + 1; i < Ainb[r]; ++ i){
          la += AA[i];
        }
      }
      printf("%u\n", la);
    }
  }
  return 0;
}

2. P13535 [IOI 2025] 纪念品(souvenirs)

https://www.luogu.com.cn/problem/P13535
题意:交互题。有 \(n\) 件纪念品,价格分别为 \(P_0,P_1,...,P_{n-1}\),均为正整数且严格递减。交互库刚开始只给你 \(n\)\(P_0\)。定义一次购买操作为:向交互库输出一个正整数 \(m\),交互库会执行:维护一个初始为空的 vector \(k\),将 \(i\)\(0\)\(n-1\) 遍历,若 \(m\geq P_i\),则将 \(i\) 插入 \(k\) 中,并且令 \(m\leftarrow m-P_i\),返回 \(k\) 以及最后的 \(m\) 值。你需要进行若干次购买操作,要求每次购买必须购买至少一件物品,若最后第 \(i\) 号物品恰好购买了 \(i\) 次,则通过题目(\(0\) 号物品当然只能购买 \(0\) 次)。

题目的限制其实非常严格,找到限制中唯一能走的一步跟着走就做完了。

首先,第一次询问肯定不能太小,否则买不到任何东西就寄了。于是选择询问 \(P_{0}-1\),这个数肯定不会出现问题。

然后,如果返回的那个集合中只有一个数价格没有确定,那么随之就确定了;否则有多个没有确定,可以知道他们的平均数,这个数肯定小于这个集合中价格最大的那个,考虑递归下去问这个平均数,求完这个集合内除掉最大值剩下的价格,就能确定最大值。

具体流程:定义函数 \(solve(x)\) 表示能够求得所有价格 \(\leq x\) 的纪念品的价格。初始调用 \(solve(P_0-1)\)

  1. 购买 \(x\),得到一个集合 \(S\) 以及他们的平均值。
  2. 反复执行 3, 4 操作使得集合大小为 \(1\)
  3. 将集合中那些已经确定价格的数删掉,更新平均值。
  4. 递归下去 \(solve\) 平均值,可以得到所有 \(\leq\) 平均值的价格,此时整个集合内已知价格一定会变多。
  5. 此时可以计算集合内唯一一个元素的价格(显然为价格最大的那个元素)。
  6. 找到后面所有的没有计算价格的元素中下标最小的一个 \(i\),调用 \(solve(P_{i-1}-1)\),因为此时 \(P_{i-1}\) 一定算过了。

可以发现,算法过程中以下标 \(i\) 作为最小下标的集合只有 \(1\) 个,所以此时每个物品的购买次数都是不大于题目要求的,然后知道所有价格后补全剩下的购买次数即可。

//P13535
#include <bits/stdc++.h>
using namespace std;
std::pair<std::vector<int>, long long> transaction(long long M);

int cnt[110], al;
typedef long long ll;
ll P[110];

void solve(ll val){
  pair<vector<int>, ll> now = transaction(val);
  for(auto i : now.first){
    ++ cnt[i];
  }
  vector<int> tmp;
  for(auto i : now.first){
    if(!P[i]){
      tmp.push_back(i);
    } else {
      now.second += P[i];
    }
  }
  swap(now.first, tmp);
  int l = 0, r = now.first.size()-1;
  while(l < r){
    ll sum = val - now.second;
    for(int i = r+1; i < now.first.size(); ++ i){
      sum -= P[now.first[i]];
    }
    ll pj = sum / (r - l + 1);
    solve(pj);
    while(P[now.first[r]]){
      -- r;
    }
  }
  ll sum = val - now.second;
  for(int i : now.first){
    sum -= P[i];
  }
  P[now.first[0]] = sum;
  for(int i = now.first[0] + 1; i < al; ++ i){
    if(!P[i]){
      solve(P[i-1] - 1);
      break;
    }
  }
}

void buy_souvenirs(int N, ll P0){
  al = N;
  P[0] = P0;
  cnt[0] = 0;
  for(int i = 1; i < N; ++ i){
    P[i] = cnt[i] = 0;
  }
  solve(P0 - 1);
  for(int i = 1; i < N; ++ i){
    while(cnt[i] < i){
      transaction(P[i]);
      ++ cnt[i];
    }
  }
}

3. P13537 [IOI 2025] 世界地图(worldmap)

https://www.luogu.com.cn/problem/P13537
给你一张 \(n\) 个点的无向连通图,要求构造一个 \(2n\times 2n\) 的矩形,每个元素 \(\in[1, n]\),要求:\(\forall u, v\in[1, n],u\neq v\),图中存在边 \((u,v)\) 等价于矩形中有两个边相邻的位置一个是 \(u\),一个是 \(v\)

思路是现提取生成树,然后再塞进去非树边。

dfs 生成树性质是好的:如果把非树边绑到浅节点上,叶子节点不会有非树边,非叶子节点的非树边个数小于 \(siz\)

构造方案:

1243

(最上面的 5 应该改为 4)

红色位置可以任意插入非树边。

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

vector<int> g[45], son[45];
int dfn[45], siz[45], dfc, st[45], stc, vis[45], fdf[45], dep[45];

void dfs(int x, int fa){
  dep[x] = dep[fa] + 1;
  vis[x] = 1;
  dfn[x] = ++ dfc;
  fdf[dfn[x]] = x;
  siz[x] = 1;
  st[x] = ++ stc;
  for(int i : g[x]){
    if(!vis[i]){
      dfs(i, x);
      ++ stc;
      siz[x] += siz[i];
    } else if(i != fa && dep[x] > dep[i]){
      son[i].push_back(x);
    }
  }
}

vector<vector<int> > create_map(int N, int M, vector<int> A, vector<int> B) {
  vector<vector<int> > ans(2 * N, vector<int>(2 * N, 0));
  dfc = stc = 0;
  for(int i = 1; i <= N; ++ i){
    vector<int> ().swap(g[i]);
    vector<int> ().swap(son[i]);
    vis[i] = dfn[i] = siz[i] = st[i] = fdf[i] = dep[i] = 0;
  }
  for(int i = 0; i < M; ++ i){
    g[A[i]].push_back(B[i]);
    g[B[i]].push_back(A[i]);
  }
  dfs(1, 0);
  for(int i = 1; i <= N; ++ i){
    for(int j = 2 * i - 2; j < 2 * N; ++ j){
      for(int k = st[fdf[i]]-1; k <= st[fdf[i]] + 2 * siz[fdf[i]] - 3; ++ k){
        ans[j][k] = fdf[i];
      }
    }
  }
  for(int i = 1; i <= N; ++ i){
    for(int j = 0; j < siz[fdf[i]] - 1; ++ j){
      ans[2*i][st[fdf[i]]+j*2] = fdf[i];
      if(j < son[fdf[i]].size()){
        ans[2*i-1][st[fdf[i]]+j*2] = son[fdf[i]][j];
      }
    }
  }
  for(int i = 0; i < 2 * N; ++ i){
    ans[i][2*N-1] = 1;
  }
  return ans;
}

4. P13540 [IOI 2025] 羊驼的坎坷之旅(obstacles)

https://www.luogu.com.cn/problem/P13540
题意:给一个 \(n\times m\) 的矩阵,每行一个权值 \(a\),每列一个权值 \(b\),一个格子可以走当且仅当 \(a_i>b_j\)。强制在线询问 \(L,R,S,T\),表示提取出 \([L,R]\) 内的列,点 \((0,S)\) 是否能四联通走到 \((0,T)\)
\(n,m,q\leq 2\times 10^5\)

首先不考虑 \(L,R\) 的限制。

不可行等价于有割。若将矩阵的左、下、右三面都加满一条障碍,那么割形如从 \((0,[S+1,T-1])\) 走到 \((0,[0,S-1])\) 或者 \((0,[T+1,m-1])\),可以证明这样的割一定是分为三段直线:向下、向两侧其中一侧、向上

证明:首先每一列的障碍集合一定两两包含或者不交,然后画出一条不符合上述条件的折线 \((0,S)\to (0, T)\),可以证明要么存在一条满足上述条件的 \(S\to T\),要么存在若干条满足上述条件的 \(S\to P_1,...,P_k\to T\)。所以只用提取出所有满足上述条件的极小割即可。

发现,如果两个割区间有交,他们都不是极小的,所以极小割只有 \(O(n)\) 组。极小割是好求的:对于 \(b_i\),找到它两侧第一个 \(\geq b_i\) 的位置,然后 check \(i\) 和这两个位置分别能不能构成割即可。

考虑 \(L,R\) 的限制,多了一种情况是 \(L,R\) 与原先不是割的一段构成割,此时维护 \(mxl,mxr\) 表示 \(b_i\) 向下后再向两侧最远能够到达哪两列,然后 rmq 即可。

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

const int N = 2e5 + 10;
int a[N], b[N], n, m;
int lm[N], rm[N], st[N], tp;
int mxle[N], mxri[N], mxdn[N];
int apmx[N], apmn[N];
int ST[20][N];

int qmn(int l, int r){
  int k = 31 ^ __builtin_clz(r - l + 1);
  return min(ST[k][l], ST[k][r-(1<<k)+1]);
}

int mn[N*4], mx[N*4], mmn[N*4], mmx[N*4];
void add(int p, int l, int r, int x, int v, int op){
  if(l == r){
    if(op == 0){
      mn[p] = min(mn[p], v);
    } else if(op == 1){
      mx[p] = max(mx[p], v);
    } else if(op == 2){
      mmn[p] = min(mmn[p], v);
    } else if(op == 3){
      mmx[p] = max(mmx[p], v);
    }
  } else {
    int mid = l + r >> 1;
    if(x <= mid){
      add(p<<1, l, mid, x, v, op);
    } else {
      add(p<<1|1, mid+1, r, x, v, op);
    }
    mn[p] = min(mn[p<<1], mn[p<<1|1]);
    mx[p] = max(mx[p<<1], mx[p<<1|1]);
    mmn[p] = min(mmn[p<<1], mmn[p<<1|1]);
    mmx[p] = max(mmx[p<<1], mmx[p<<1|1]);
  }
}
int qry(int p, int l, int r, int ql, int qr, int op){
  if(qr < l || r < ql){
    return (op & 1) ? 0 : m + 1;
  } else if(ql <= l && r <= qr){
    if(op == 0){
      return mn[p];
    } else if(op == 1){
      return mx[p];
    } else if(op == 2){
      return mmn[p];
    } else {
      return mmx[p];
    }
  } else {
    int mid = l + r >> 1;
    if(op & 1){
      return max(qry(p<<1, l, mid, ql, qr, op), qry(p<<1|1, mid+1, r, ql, qr, op));
    } else {
      return min(qry(p<<1, l, mid, ql, qr, op), qry(p<<1|1, mid+1, r, ql, qr, op));
    }
  }
}

void initialize(vector<int> T, vector<int> H){
  n = T.size(), m = H.size();
  apmn[0] = 2e9;
  for(int i = 1; i <= n; ++ i){
    a[i] = T[i-1];
    apmx[i] = max(a[i], apmx[i-1]);
    apmn[i] = min(a[i], apmn[i-1]);
  }
  a[++n] = 0;
  b[1] = b[m+2] = 1e9;
  for(int i = 2; i <= m+1; ++ i){
    b[i] = H[i-2];
  }
  m += 2;
  for(int i = 1; i <= m; ++ i){
    mn[i] = mn[i+m] = mn[i+m+m] = mn[i+m+m+m] = m + 1;
    mmn[i] = mmn[i+m] = mmn[i+m+m] = mmn[i+m+m+m] = m + 1;
    ST[0][i] = b[i];
    int L = 0, R = n;
    while(L < R){
      int mid = L + R + 1 >> 1;
      if(apmx[mid] <= b[i]){
        L = mid;
      } else {
        R = mid - 1;
      }
    }
    mxdn[i] = L;
  }
  for(int i = 1; i < 20; ++ i){
    for(int j = 1; j + (1 << i) - 1 <= m; ++ j){
      ST[i][j] = min(ST[i-1][j], ST[i-1][j+(1<<i-1)]);
    }
  }
  tp = 0;
  for(int i = 1; i <= m; ++ i){
    int mn = apmn[mxdn[i]];
    int L = 1, R = i;
    while(L < R){
      int mid = L + R >> 1;
      if(qmn(mid, i) >= mn){
        R = mid;
      } else {
        L = mid + 1;
      }
    }
    mxle[i] = L;
    L = i, R = m;
    while(L < R){
      int mid = L + R + 1 >> 1;
      if(qmn(i, mid) >= mn){
        L = mid;
      } else {
        R = mid - 1;
      }
    }
    mxri[i] = L;
    add(1, 1, m, i, mxle[i], 2);
    add(1, 1, m, i, mxri[i], 3);
    while(tp && b[st[tp]] < b[i]){
      -- tp;
    }
    if(tp){
      int mn = min(mxdn[i], mxdn[st[tp]]);
      if(apmn[mn] <= qmn(st[tp], i)){
        add(1, 1, m, i, st[tp], 0);
        add(1, 1, m, st[tp], i, 1);
      }
    }
    st[++tp] = i;
  }
  tp = 0;
  for(int i = m; i >= 1; -- i){
    while(tp && b[st[tp]] < b[i]){
      -- tp;
    }
    if(tp){
      int mn = min(mxdn[i], mxdn[st[tp]]);
      if(apmn[mn] <= qmn(i, st[tp])){
        add(1, 1, m, i, st[tp], 1);
        add(1, 1, m, st[tp], i, 0);
      }
    }
    st[++tp] = i;
  }
}

bool can_reach(int L, int R, int S, int D){
  if(S == D){
    return 1;
  }
  if(S > D){
    swap(S, D);
  }
  L += 1;
  R += 3;
  S += 2;
  D += 2;
  if(qry(1, 1, m, S+1, D-1, 0) < S){
    return 0;
  }
  if(qry(1, 1, m, S+1, D-1, 1) > D){
    return 0;
  }
  if(qry(1, 1, m, S+1, D-1, 2) <= L + 1){
    return 0;
  }
  if(qry(1, 1, m, S+1, D-1, 3) >= R - 1){
    return 0;
  }
  return 1;
}

5. P13606 [NWRRC 2022] IQ Game

https://www.luogu.com.cn/problem/P13606
题意:一个 \(len\) 个点的环上有 \(n\) 个人,一个关键人,每个人都在一个点上。每次随机选一个环上的点,然后从这个点开始顺时针经过的第一个人离场,关键人离场后结束。求期望离场人数。
\(len\leq 10^9,n\leq 200\)

\(O(n^4)\) 的歪解。

首先在关键人处断环为链,这样关键人就在序列的最后一个位置。

由期望线性性,期望离场人数等于枚举每个非关键人,这个人先于关键人离场的概率之和。

假设目前枚举到的人为 \(i\),然后枚举 \(i\) 离场时,在他左边操作的次数 \(x\);右边操作的次数 \(y\),如果能够求到 \(F\):左边操作 \(x\) 次,第 \(x\) 次使得人 \(i\) 离场;\(G\):右边操作 \(y\) 次,最后一个人不离场,那么答案是好算的,即:单个概率乘以考虑操作顺序后本质相同的方案数:

\[\dbinom{x+y-1}{y}\times F\times G \]

最后考虑 \(F,G\) 怎么求了,容易发现从后往前求是比较简单的:对于 \([i,n]\) 上的操作,只需满足 \([j,n]\) 操作数 \(\leq n-j\) 即可,那么记录 \(g_{i,j}\) 表示 \([i,n]\) 操作 \(j\) 次,最后一个人不离场的方案数:

\[g_{i,j}(j\leq n-i)=\sum_{k=0}^j g_{i+1,k}\times \left(\dfrac{a_i-a_{i-1}}{len}\right)^{j-k} \]

\(G\)\(g_{i+1,y}\) 即可。

\(F\) 同理,只不过要对于每个 \(i\) 从右往左算一次,取 \(f_{1,x-1}\times \dfrac{a_i}{len} - f_{1,x}\)

复杂度瓶颈在于每次转移 \(F\)。可能可以通过转置之类的东西优化到三次方,但是我不会。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P = 998244353;

const int N = 210;
int len, n, S, a[N];
ll C[N][N];

ll qp(ll x, ll y){
  ll ans = 1;
  while(y){
    if(y & 1){
      ans = ans * x % P;
    }
    x = x * x % P;
    y >>= 1;
  }
  return ans;
}

ll f[N][N], g[N][N];

int main(){
  scanf("%d%d%d", &len, &n, &S);
  C[0][0] = 1;
  for(int i = 1; i <= n; ++ i){
    C[i][0] = 1;
    for(int j = 1; j <= i; ++ j){
      C[i][j] = (C[i-1][j] + C[i-1][j-1]) % P;
    }
    scanf("%d", &a[i]);
    if(a[i] <= S){
      a[i] += len - S;
    } else {
      a[i] -= S;
    }
  }
  sort(a + 1, a + n + 1);
  ll in = qp(len, P - 2);
  g[n][0] = 1;
  for(int i = n-1; i >= 1; -- i){
    for(int j = 0; j <= n - i; ++ j){
      ll val = 1;
      for(int k = j; k >= 0; -- k){
        g[i][j] = (g[i][j] + g[i+1][k] * val % P * C[j][k]) % P;
        val = val * (a[i] - a[i-1]) % P * in % P;
      }
    }
  }
  ll ans = 0;
  for(int i = 1; i < n; ++ i){
    memset(f, 0, sizeof(f));
    f[i][0] = 1;
    for(int p = i-1; p >= 1; -- p){
      for(int j = 0; j <= i - p; ++ j){
        ll val = 1;
        for(int k = j; k >= 0; -- k){
          f[p][j] = (f[p][j] + f[p+1][k] * val % P * C[j][k]) % P;
          val = val * (a[p] - a[p-1]) % P * in % P;
        }
      }
    }
    for(int x = 1; x <= i; ++ x){
      for(int y = 0; y < n - i; ++ y){
        ll F = (f[1][x-1] * a[i] % P * in % P - f[1][x] + P) % P;
        ll G = g[i+1][y];
        ans = (ans + C[x+y-1][y] % P * F % P * G) % P;
      }
    }
  }
  printf("%lld\n", (ans + 1) % P);
  return 0;
}

6. P12573 [UOI 2023] An Array and XOR

https://www.luogu.com.cn/problem/P12573
题意:给定一个整数 \(m\),一个长度为 \(n\) 的非负整数数组 \(a\),以及 \(q\) 个形如 \(l_i\), \(r_i\) 的查询。数组 \(a\) 的所有元素都小于 \(2^m\)。定义函数 \(f_i(x) = \min_{j \in [l_i, r_i]} (a_j \oplus x)\),其中 \(\oplus\) 表示按位异或运算。对于每个查询,你需要找到 \(\max_{x \in \{0, 1, \ldots, 2^m-1\}} f_i(x)\) 的值。
\(n\leq 10^5,m\leq 50,q\leq 5\times 10^5\)

容易转化为:建 trie 树,\(a_i\) 的最大 \(x\) 是:代表 \(a_i\) 的那条链上,没有兄弟的点的权值和。

直接莫队可以做到 \(O(nm\sqrt q)\)

优化是:对于每一位 \(j\),找到 \(le_i,ri_i\) 表示 \(i\) 两侧第一个在 \(j\) 位处分开的数,转化为二维数点问题。

7. P11714 [清华集训 2014] 主旋律

https://www.luogu.com.cn/problem/P11714
题意:给你一张无重边自环的有向图 \(G(V,E)\),求 \(E\) 的子集 \(E'\) 数量,使得 \(G(V,E')\) 是强连通的。
\(n\leq 15\)

\(E_{S,T}\) 表示 \(\sum_{i\in S,j\in T}[(i,j)\in E]\)

首先考虑 DAG 计数:

\(f_S\) 表示 \(S\) 内的点构成 DAG 的方案数,转移的时候枚举 \(0\) 入度点集合 \(T\),那么 \(T\to S/T\) 的边可以任意连,\(T\) 内没有边,\(S\) 也为一个 DAG。但是不能保证所有 \(S/T\) 的点都有入度,所以需要容斥。

具体的,设 \(F_{T,S}\) 表示 \(S\) 内恰好 \(T\)\(0\) 入度点集合的方案数;\(G_{T,S}\) 表示 \(S\) 内选定 \(T\)\(0\) 入度点集合的方案数,则有:

\[G_{T,S}=2^{E_{T,S/T}}f_{S/T}=\sum_{T\subseteq R\subseteq S} F_{R,S} \]

子集反演:

\[F_{R,S}=\sum_{R\subseteq T\subseteq S}(-1)^{|T|-|R|} G_{T,S} \]

使用 \(F\)\(f\)

\[\begin{aligned} f_S&=\sum_{T\subseteq S, T\neq \emptyset} F_{T,S}\\ &=\sum_{T\subseteq S,T\neq \emptyset}\sum_{T\subseteq R\subseteq S}(-1)^{|R|-|T|}G_{R,S}\\ &=\sum_{R\subseteq S,R\neq \emptyset}(-1)^{|R|}G_{R,S}\sum_{T\subseteq R,T\neq \emptyset}(-1)^{|T|}\\ &=\sum_{R\subseteq S,R\neq \emptyset}(-1)^{|R|}G_{R,S}([R=\emptyset]-1)\\ &=\sum_{R\subseteq S,R\neq \emptyset}(-1)^{|R|+1}G_{R,S}\\ &=\sum_{T\subseteq S,T\neq \emptyset}(-1)^{|T|+1}2^{E_{T,S/T}}f_{S/T} \end{aligned}\]

这也就是容斥系数是 \((-1)^{|T|+1}\) 的原因。


现在开始强连通图计数。忘记上述的状态定义,重新定义 \(f_S\) 为使得 \(S\) 为强连通图的方案数。

同样的,强连通图缩点后为一个点;否则为一个 \(\geq 2\) 个点的 DAG。枚举 \(0\) 入度 SCC 由 \(S\) 内的点构成。

如果直接定义 \(g_S\) 表示 \(S\) 内点构成若干个 SCC 的方案数的话,容斥系数 \((-1)^{|T|+1}\) 中的 \(|T|\) 代表 SCC 个数就无法表示了,所以我们考虑将容斥系数与 \(g\) 统一定义:\(g_{S}\) 表示 \(S\) 内点构成奇数个 SCC 方案数减去构成偶数个 SCC 方案数。

写出式子:

\[f_S=2^{E_{S,S}}-\sum_{T\subseteq S,T\neq\emptyset}g_{T}2^{E_{T,S/T}}2^{E_{S/T,S/T}} \]

此时的 \(S/T\) 部分并没有要求是 DAG/SCC,而是任意一张图都可以。

\[g_S=f_S-\sum_{T\subset S,\operatorname{lowbit}(S)=\operatorname{lowbit}(T),S\neq\emptyset}f_Tg_{S/T} \]

注意到:此处 \(f\) 的式子有问题:如果将 \(g_S\) 带入 \(f_S\) 的式子中,两侧都出现了 \(f_S\)。但是考虑到此时的意义是:入度为 \(0\) 的 SCC 只有一个,且整张图由这一个 SCC 构成,不应该被带入到 \(f_S\) 的式子中。

解决方法是先当作 \(f_S=0\) 计算 \(g_S\),然后计算 \(f_S\),最后计算最终的 \(g_S\)

最后问题:计算 \(E\)。观察到有用的 \(E_{A,B}\) 要么是 \(A=B\),要么是 \(A\cup B=S,A\cap B=\emptyset\)

前者递推式:

\[E_{S,S}=E_{S-x,S-x}+\operatorname{popcount}(in_x\operatorname{and}S)+\operatorname{popcount}(out_x\operatorname{and}S) \]

后者,当计算到 \(S\) 时,递推:

\[E_{T,S/T}=E_{T+x,S/T-x}+\operatorname{popcount}(in_x\operatorname{and}T)-\operatorname{popcount}(out_x\operatorname{and}(S/T)) \]

总复杂度 \(O(3^n)\),空间 \(O(2^n)\)

//P11714
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ppc __builtin_popcount

const ll P = 1e9 + 7;
int n, m, in[15], out[15];
ll pw[220], E[1<<15], f[1<<15], g[1<<15], EE[1<<15];

int main(){
  pw[0] = 1;
  for(int i = 1; i < 220; ++ i){
    pw[i] = pw[i-1] * 2 % P;
  }
  scanf("%d%d", &n, &m);
  for(int i = 1; i <= m; ++ i){
    int x, y;
    scanf("%d%d", &x, &y);
    -- x;
    -- y;
    in[y] |= (1 << x);
    out[x] |= (1 << y);
  }
  for(int S = 1; S < (1 << n); ++ S){
    int x = 31 ^ __builtin_clz(S & -S);
    E[S] = E[S^(1<<x)] + ppc(in[x]&S) + ppc(out[x]&S);
  }
  for(int S = 1; S < (1 << n); ++ S){
    EE[S] = 0;
    for(int T = (S - 1) & S; T; T = (T - 1) & S){
      int x = 31 ^ __builtin_clz((S^T) & -(S^T));
      EE[T] = EE[T^(1<<x)] + ppc(in[x]&T) - ppc(out[x]&(S^T));
      if((S & -S) == (T & -T)){
        g[S] = (g[S] - f[T] * g[S^T] % P + P) % P;
      }
    }
    f[S] = pw[E[S]];
    for(int T = S; T; T = (T - 1) & S){
      f[S] = (f[S] - g[T] * pw[EE[T]] % P * pw[E[S^T]] % P + P) % P;
    }
    g[S] = (g[S] + f[S]) % P;
  }
  printf("%lld\n", f[(1<<n)-1]);
  return 0;
}

8. P11834 [省选联考 2025] 岁月

https://www.luogu.com.cn/problem/P11834
题意:给你一张 \(n\) 个点 \(m\) 条双向有向边的图 \(G\),每条有向边有 \(\dfrac 12\) 的概率消失,设得到的图为 \(G'\)。求图 \(G'\) 的最小外向生成树边权和等于 \(G\) 的的概率。
\(n\leq 15\)。特殊性质:边权 \(=1\)

特殊性质

此时只要有外向生成树存在,则满足题意。

外向生成树存在等价于:

  1. 存在一个点集 \(C\)\(C\) 内点强连通,定义这个 \(C\) 为此时的根集
  2. \(C\) 内点能到达 \(C\) 外所有点。
  3. \(C\) 外所有点到达不了 \(C\) 内任意一点。

三部分概率是独立的。

第一部分,等价于上一题主旋律,对于每个子集 \(S\) 求出其构成一个 SCC 的概率。

第二部分,考虑 dp。设目前枚举了点集 \(C\)\(dp_S\) 表示 \(C\) 能走到 \(S\) 内所有点的概率,容斥有一个集合 \(T\) 到达不了:

\[dp_S=1-\sum_{T\subset S,T\cap R=\emptyset}dp_{S/T}2^{-E_{S/T,T}} \]

起始状态为 \(dp_C\) 为第一部分的答案,终点为 \(dp_U\)

第三部分,答案就是 \(2^{-E_{U/C,C}}\),表示 \(U/C\to C\) 的边全部不能出现。

复杂度 \(O(4^n)\),瓶颈在于第二部分。

考虑转置原理:第二部分的转移,如果没有这个 \(1\),相当于 \(C\to U\) 的所有路径权值积的和。加上这个 \(1\) 就表示 \(C\to U\),每个中间点都能作为一次起点,所有路径权值积的和。

转置后就是所有 \(U\to P\) 的所有路径权值积的和,其中 \(P\) 能够到达 \(C\) 即可。

也就是一次 dp + 一次超集和。最后答案是每个 \(C\) 的上述三部分乘积加起来。复杂度 \(O(n^3)\)

正解

kruskal 的结论是:任意一棵 MST,只提取 \(\leq w\) 的边,构成的连通块集合都是相同的。

那么,从小到大考虑每个边权,设目前考虑到边权 \(w\)

  • 目前会形成若干个连通块,这个连通块形态只和原图形态有关。
  • \(bel_i\) 表示点 \(i\) 所在连通块点集。
  • \(sc_S\) 表示考虑边权 \(\leq x-1\),集合 \(S\) 内的所有点所在连通块并集。
  • \(to_i\) 表示一端为 \(i\),边权为 \(w\) 的另一端点点集。
  • \(eg\_cnt_S\) 表示有多少条边权为 \(w\) 的边两端都在 \(S\) 内。
  • \(is_S\) 边权 \(w-1\to w\) 时,集合 \(S\) 所在连通块是否会改变(即这一轮这个集合是否需要计算)。
  • 简单的计算:\(E(S,T)=eg\_cnt_{S|T}-eg\_cnt_S-eg\_cnt_T\)

对于连通块 \(B\),它的内部集合 \(S\) 会以一个概率成为 \(B\) 内的根集,设为 \(ans_B\)(如果 \(B\) 不在一个连通块内,\(ans_B\) 看作未定义)

那么当考虑所有 \(w\) 边,我们需要做的是合并若干个连通块,然后更新 \(ans\) 数组。同样分为三部分:

Part 1. 点集 \(R\) 内部强连通

计算 \(R\) 与每一个连通块 \(B_i\) 的交集 \(R_i\),则相当于 \(R_1,...,R_k\) 这些各自连通块内的根集用连通块之间的 \(w\) 边实现强连通。和主旋律大体相同,区别在于 \(B_i/R_i\to R_j\) 的边也可以看作 \(R_i\to R_j\) 的边,因为 \(R_i\to B_i/R_i\) 必定可行。把 \(B_i\to R_j\) 称作对 \(i\to j\) 有用的边。修改主旋律转移式系数即可。

\[g_S=f_S-\sum_{T\subset S,\operatorname{lowbit}(S)=\operatorname{lowbit}(T),sc_{S/T}\cap sc_T=0}f_Tg_{S/T}2^{-E(sc_{S/T},T)-E(sc_T,S/T)} \]

注意要求 \(sc_{S/T}\cap sc_T=0\),即 \(S/T,T\) 在不同的连通块内。此处的定义是 \(S/T\)\(S\) 分别在不同 SCC 内,之间的所有有用的边都要断开。

\[f_S=1-\sum_{T\subseteq S,\operatorname{lowbit}(S)=\operatorname{lowbit}(T),sc_{S/T}\cap sc_T=0}g_T2^{-E(sc_{S/T},T)} \]

此处要求 \(S/T\) 零入度。答案 \(f_R\)

Part 2. \(R\) 能到 \(sc_R\) 内所有点

\(dp_S\) 表示 \(R\) 能够走到 \(S\) 这个根集的概率(也就是说 \(R\) 能走到 \(sc_S\) 内所有点)。

这里需要考虑一下全集的概率,实际上是 \(S\) 在每个连通块内的部分都是根集的概率。设为 \(be_S= \prod ans_{S\operatorname{and}B_i}\)

转移:

\[dp_S=be_S-\sum_{T\subset S,sc_{S/T}\cap sc_T=0}2^{-E(sc_{S/T},T)}dp_{S/T}be_T \]

终点是所有 \(sc_S=sc_R\)\(S\)

转置方法是先找到这样的 \(S\) 然后令 \(dp_S=1\),接着倒着做这个 dp,然后每个位置 \(\times be_S\) 再贡献到它的子集作为答案的一部分。

Part 3. 没有边从完全与 \(R\) 不交的连通块里的点连进 \(R\)

对于 \(R\) 计算出这样连通块能连 \(cnt\) 条边,然后 \(\times 2^{-cnt}\) 即可。

最后用 \(dp_R\times 2^{-cnt}\times f_R\) 更新 \(ans_R\)

最后答案是 \(\sum_{S\neq \emptyset} ans_S\)

//P11834
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll P = 1e9 + 7;
ll pw[1000], ipw[1000];
ll qp(ll x, ll y){
  ll ans = 1;
  while(y){
    if(y & 1){
      ans = ans * x % P;
    }
    x = x * x % P;
    y >>= 1;
  }
  return ans;
}
const ll i2 = qp(2, P - 2);

int n, m;

struct edge{
  int u, v, w;
} eg[500];
bool cmp(edge x, edge y){
  return x.w < y.w;
}

int fa[15];
int gf(int x){
  return x == fa[x] ? x : fa[x] = gf(fa[x]);
}

ll ans[1<<15];//集合 S 作为根集概率
int sc[1<<15], new_sc[1<<15];//集合 S 所在连通块并集(本轮/下一轮)
int bel[15];//点 i 所在连通块集合
int to[1<<15];//点 i 邻域集合
ll be[1<<15];//集合 S 作为 sc_S 的根集的概率
int eg_cnt[1<<15];//集合 S 内目前需要考虑的边数
bool is[1<<15];//集合 S 目前是否需要考虑
ll f[1<<15], g[1<<15], dp[1<<15];

int E(int S, int T){//S,T 内边数
  return eg_cnt[S|T] - eg_cnt[S] - eg_cnt[T];
}

void solve(){
  scanf("%d%d", &n, &m);
  for(int i = 1; i <= m; ++ i){
    int u, v, w;
    scanf("%d%d%d", &u, &v, &w);
    eg[i] = {u - 1, v - 1, w};
  }
  sort(eg + 1, eg + m + 1, cmp);
  for(int i = 0; i < n; ++ i){
    fa[i] = i;
    ans[1<<i] = 1;
    bel[i] = 1 << i;
  }
  for(int S = 0; S < (1 << n); ++ S){
    new_sc[S] = S;
  }
  for(int l = 1, r = 1; l <= m; l = r + 1){
    r = l;
    while(r + 1 <= m && eg[r+1].w == eg[l].w){
      ++ r;
    }
    swap(sc, new_sc);
    memset(new_sc, 0, sizeof(new_sc));
    memset(is, 0, sizeof(is));
    memset(to, 0, sizeof(to));
    for(int i = l; i <= r; ++ i){
      if(gf(eg[i].u) != gf(eg[i].v)){
        bel[gf(eg[i].v)] |= bel[gf(eg[i].u)];
        fa[gf(eg[i].u)] = gf(eg[i].v);
      }
      to[eg[i].u] |= 1 << eg[i].v;
      to[eg[i].v] |= 1 << eg[i].u;
    }
    for(int i = 0; i < n; ++ i){
      bel[i] = bel[gf(i)];
    }
    
    be[0] = 1;
    eg_cnt[0] = 0;
    for(int S = 1; S < (1 << n); ++ S){
      int x = 31 ^ __builtin_clz(S & -S);
      be[S] = be[S^(S&sc[1<<x])] * ans[S&sc[1<<x]] % P;
      new_sc[S] = new_sc[S^(S&bel[x])] | bel[x];
      eg_cnt[S] = 0;
      for(int i = l; i <= r; ++ i){
        if(((S >> eg[i].u) & 1) && ((S >> eg[i].v) & 1)){
          ++ eg_cnt[S];
        }
      }
    }
    for(int i = 0; i < n; ++ i){
      if(gf(i) == i && sc[1<<i] != new_sc[1<<i]){
        for(int S = bel[i]; S; S = (S - 1) & bel[i]){
          is[S] = 1;
        }
      }
    }

    //Part 1. 主旋律
    memset(f, 0, sizeof(f));
    memset(g, 0, sizeof(g));
    for(int S = 1; S < (1 << n); ++ S){
      if(!is[S]){
        continue;
      }
      for(int T = (S - 1) & S; T; T = (T - 1) & S){
        if((sc[S^T] & sc[T]) == 0 && (S & -S) == (T & -T)){
          g[S] = (g[S] - f[T] * g[S^T] % P * ipw[E(sc[S^T],T)+E(sc[T],S^T)] % P + P) % P;
        }
      }
      f[S] = 1;
      for(int T = S; T; T = (T - 1) & S){
        if((sc[S^T] & sc[T]) == 0){
          f[S] = (f[S] - g[T] * ipw[E(sc[S^T],T)] % P + P) % P;
        }
      }
      g[S] = (g[S] + f[S]) % P;
    }
      
    //Part 2. Others
    for(int S = 1; S < (1 << n); ++ S){
      if(!is[S]){
        continue;
      }
      int cnt = 0;
      for(int i = 0; i < n; ++ i){
        if(new_sc[1<<i] == new_sc[S] && (sc[1<<i] & sc[S]) == 0){
          cnt += __builtin_popcount(S & to[i]);
        }
      }
      f[S] = f[S] * ipw[cnt] % P;
    }

    //Part 3. 转置
    memset(dp, 0, sizeof(dp));
    for(int S = 1; S < (1 << n); ++ S){
      if(!is[S]){
        continue;
      }
      int flg = 1;
      for(int i = 0; i < n; ++ i){
        if(new_sc[1<<i] == new_sc[S] && (sc[1<<i] & sc[S]) == 0){
          flg = 0;
          break;
        }
      }
      dp[S] = flg;
    }
    for(int S = (1 << n) - 1; S; -- S){
      if(!is[S]){
        continue;
      }
      for(int T = (S - 1) & S; T; T = (T - 1) & S){
        if((sc[S^T] & sc[T]) == 0){
          dp[S^T] = (dp[S^T] + dp[S] * (P - ipw[E(sc[S^T], T)]) % P * be[T]) % P;
        }
      }
    }
    for(ll S = 1; S < (1 << n); ++ S){
      if(dp[S]){
        dp[S] = dp[S] * be[S] % P;
        for(ll T = (S - 1) & S; T; T = (T - 1) & S){
          if((sc[S^T] & sc[T]) == 0){
            dp[T] = (dp[T] + dp[S]) % P;
          }
        }
      }
    }

    for(int S = 1; S < (1 << n); ++ S){
      if(!is[S]){
        continue;
      }
      ans[S] = f[S] * dp[S] % P;
    }
  }
  ll res = 0;
  for(int S = 1; S < (1 << n); ++ S){
    res = (res + ans[S]) % P;
  }
  printf("%lld\n", res);
  return;
}

int main(){
  pw[0] = ipw[0] = 1;
  for(int i = 1; i < 1000; ++ i){
    pw[i] = pw[i-1] * 2 % P;
    ipw[i] = ipw[i-1] * i2 % P;
  }
  int c, T;
  scanf("%d%d", &c, &T);
  while(T--){
    solve();
  }
  return 0;
}

9. P11118 [ROI 2024] 无人机比赛 (Day 2)

https://www.luogu.com.cn/problem/P11118
题意:有一条赛道,每隔若干格有一个存档点,坐标分别是 \(s_1,s_2,...,s_m\)\(n\) 个无人机进行比赛,第 \(i\) 个无人机飞 \(1\) 单位距离的时间是 \(t_i\)。每当一个无人机飞到一个存档点,它留在存档点,其他所有还未完赛的无人机全部返回到它最后一次存档的存档点(如果有多个无人机同时到达存档点,看作编号最小的到达其他的没有到达),定义一次返回存档点的操作为传送。一个无人机飞到存档点 \(m\) 即视为完赛,不参与后续的过程。对于每个 \(i\),求 \([1,i]\) 内的无人机进行比赛,整个过程中所有无人机在比赛结束之前一共将进行多少次传送。
\(n,m,s_m\leq 150000,t\leq 10^9\)

设无人机 \(i\) 从存档点 \(j-1\) 飞到存档点 \(j\) 的时间为 \(w_{i,j}=t_i(s_j-s_{j-1})\),则操作序列相当于将这些 \(w\) 进行归并,每次选开头的最小的 \(w\) 放入队列中。

观察到如果 \(w_{i,j}\geq w_{i,j+1}\),则 \(w_{i,j+1}\) 一定紧跟在 \(w_{i,j}\) 之后,则直接合并即可。然后对于一个 \(i\),每一段开头的 \(w\) 值严格递增,于是每个 \(i\)\(w\) 构成 \(\sqrt{s_m}\) 段。又发现 \(w\) 的大小关系和 \(i\) 无关,所以每一个 \(i\) 的分段都是相同的。

把操作序列每一步分别属于哪个无人机写出来,构成一个长度为 \(nm\),每个数出现 \(m\) 次的数列。计算这个数列对应的传送次数,我们可以发现等于 \(\sum_{i=1}^{nm}i[\forall j>i,col_i\neq col_j]-nm\),即找到每个无人机最后一次操作的下标累加起来再减去 \(nm\) 即可。

所以,对于那些不是最后一段的 \(w\),他们之间的大小关系是没有用的,只用关心 \(>\) 他们的最小 \(w_{*,m}\) 是什么即可。所以维护 \(n\) 个集合,每个集合表示小于某个 \(w_{*,m}\) 的操作段有哪些,然后遍历 \(i\in[1,n]\),将 \(w_{i,*}\) 插入对应集合中,然后激活 \(w_{i,m}\) 对应的集合(即这个集合的下标要计入答案),我们得到了一个正确的,看起来很好优化的做法。

问题 1:如何找到一个 \(w\) 对应的集合?对于段 \(p\),我们将 \(w_{i,p}\) 排序后双指针即可。复杂度 \(O(n\sqrt{s_m})\)

问题 2:答案如何快速计算?我们会有 \(O(n\sqrt{s_m})\) 次集合大小的单点加,然后 \(n\) 次激活,每次激活查询一个前缀,使用 \(O(1)-O(\sqrt n)\) 分块。然后每次单点加的贡献是它后面的激活集合数,使用 \(O(\sqrt n)-O(1)\) 维护后缀激活集合数。

总复杂度 \(O(n\sqrt{s_m}+n\sqrt n)\)

//P11118
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 150010, B = 550;
int n, m, t[N], s[N];
int mm, S[N], len[N];

pair<ll, int> bar[N];
int pos[N], hav[N];
int to[551][N];

int ind[N], le[N];
struct SQRT{
  ll val[N], blv[N];
  void add(int x, ll v){
    val[x] += v;
    blv[ind[x]] += v;
  }
  ll ask(int x){
    ll ans = 0;
    for(int i = 1; i < ind[x]; ++ i){
      ans += blv[i];
    }
    for(int i = le[ind[x]]; i <= x; ++ i){
      ans += val[i];
    }
    return ans;
  }
} T1;
struct SQRT2{
  int val[N], blv[N];
  void add(int x, int v){
    for(int i = 1; i < ind[x]; ++ i){
      blv[i] += v;
    }
    for(int i = le[ind[x]]; i <= x; ++ i){
      val[i] += v;
    }
  }
  int ask(int x){
    return val[x] + blv[ind[x]];
  }
} T2;

int main(){
  scanf("%d%d", &n, &m);
  for(int i = 1; i <= n; ++ i){
    scanf("%d", &t[i]);
    ind[i] = (i - 1) / B + 1;
    if(ind[i] != ind[i-1]){
      le[ind[i]] = i;
    }
  }
  for(int i = 1; i <= m; ++ i){
    scanf("%d", &s[i]);
  }
  for(int i = m; i >= 1; -- i){
    s[i] = s[i] - s[i-1];
  }
  for(int i = 1; i <= m; ++ i){
    if(s[i] > S[mm]){
      S[++mm] = s[i];
    }
    ++ len[mm];
  }
  swap(mm, m);
  for(int i = 1; i <= n; ++ i){
    bar[i] = make_pair(1ll * t[i] * S[m], i);
  }
  sort(bar + 1, bar + n + 1);
  for(int i = 1; i <= n; ++ i){
    pos[bar[i].second] = i;
  }
  for(int i = 1; i < m; ++ i){
    int pp = 1;
    for(int j = 1; j <= n; ++ j){
      ll val = bar[j].first / S[m];
      int id = bar[j].second;
      while(bar[pp] <= make_pair(1ll * val * S[i], id)){
        ++ pp;
      }
      to[i][id] = pp;
    }
  }
  ll ans = 0;
  for(int i = 1; i <= n; ++ i){
    for(int j = 1; j < m; ++ j){
      T1.add(to[j][i], len[j]);
      ans += 1ll * len[j] * T2.ask(to[j][i]);
    }
    ans += 1ll * len[m] * T2.ask(pos[i]);
    T1.add(pos[i], len[m]);
    ans += T1.ask(pos[i]);
    T2.add(pos[i], 1);
    printf("%lld\n", ans - 1ll * mm * i);
  }
  return 0;
}
posted @ 2025-09-08 14:12  KiharaTouma  阅读(6)  评论(0)    收藏  举报