2025.2
2025.2
1. CF1091F - New Year and the Mallard Expedition
https://www.luogu.com.cn/problem/CF1091F
显然答案为所有 \(b-a-1,c-b-1\) 的 sg 异或和。问题变为怎样求 sg。
发现 sg 式子十分公式,形如 \(sg_i=\operatorname{xor}\{sg_{i-a_j}\}\),这种式子在值域较小时可以使用 bitset 优化。设 \(p_{i,j}\) 表示是否存在一个 \(sg_k=j\) 使得 \(k\) 能贡献到 \(i\),那么这个时候就简单了:
- 更新,则令
p[sg_k] |= k << ok; - 查询,只用查询所有 \(p\) 的第 \(i\) 位即可。
复杂度 \(O(nV+\dfrac{n^2}w)\),时间复杂度 \(O(\dfrac{nV}w)\)。
点击查看代码
// Problem: H. New Year and the Tricolore Recreation
// Contest: Codeforces - Good Bye 2018
// URL: https://codeforces.com/problemset/problem/1091/H
// Memory Limit: 256 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int mx;
int n, b, f[N];
int p[N], v[N], pc, isp[N];
bitset<N> ok, sg[70];
int main(){
mx = N - 10;
for(int i = 2; i <= mx; ++ i){
if(!v[i]){
p[++pc] = i;
isp[i] = 1;
ok.set(i);
}
for(int j = 1; j <= pc && i * p[j] <= mx; ++ j){
v[i*p[j]] = 1;
if(i % p[j] == 0){
break;
}
}
}
for(int i = 1; i <= pc; ++ i){
for(int j = 1; j <= pc && p[i] * p[j] <= mx; ++ j){
ok.set(p[i]*p[j]);
}
}
scanf("%d%d", &n, &b);
ok.set(b, 0);
for(int i = 0; i <= mx; ++ i){
int val = 0;
for(int j = 0; j < 70; ++ j){
if(!sg[j].test(i)){
val = j;
break;
}
}
f[i] = val;
sg[val] |= ok << i;
}
int ans = 0;
while(n--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
ans ^= f[b-a-1] ^ f[c-b-1];
}
puts(ans ? "Alice\nBob" : "Bob\nAlice");
return 0;
}
2. qoj4228 - Double Sort
https://qoj.ac/contest/943/problem/4228
不考虑这个输出小数。来点 \(O(n^2+m\log m)\) 做法。
原题意可以转化为:对于所有 \(\sum b\leq m\) 的正整数序列,对于每一个 \(i\) 求从小到大排序后第 \(i\) 项在所有方案中的和。
其中 \(f(i,k)\) 表示至少 \(i\) 个数 \(\geq k\) 的方案数。
如何计算这个 \(f\)?考虑设 \(g(i,k)\) 表示恰好 \(i\) 个数 \(\geq k\) 的方案数;\(h(i,k)\) 表示选定 \(1\sim i\) 下标上的数 \(\geq k\) 的方案数。
根据定义有:\(f(i,k)=\sum_{j=i}^n g(j,k)\)。
根据二项式反演有:\(g(j,k) = \sum_{p=j}^n \dbinom nph(p,k)\dbinom{p}{j}(-1)^{p-j}\)。
根据插版法有:\(h(p,k)=\sum_{x=1}^m\dbinom{x-p(k-1)-1}{n-1} = \dbinom{x-p(k-1)}n\)。
求 \(f(i,k)\):
最后一步可以在杨惠三角上直接证明。
带入,得:
由于 \(h(p,k)\neq 0\) 的必要条件是 \(pk \leq m\),所以只用算 \(O(m\log m)\) 次。总复杂度 \(O(n^2+m\log m)\)。
3. LuoguP4707 - 重返现世
https://www.luogu.com.cn/problem/P4707
一直想写这个题,今天写一下,发现写起来很简单。
首先要知道扩展 min-max 容斥式子:
题面求的是第 \(n-k+1\) 大的期望(下文中的 \(k\) 指 \(n-k+1\))。那么可以转化为求若干最小期望。这个是好求的,集合 \(S\) 内的最小期望为 \(\dfrac{m}{\sum_{i\in S}p_i}\)。
容易列出 dp:设 \(f_{i,j}\) 表示集合中有 \(i\) 个数,\(p\) 和为 \(j\) 的方案数,复杂度 \(O(n^2m)\)。
考虑利用上 \(k\) 很小(即原题面中的 \(|n-k|\leq 10\)),考虑将这个组合数利用组合意义拆开来:即 \(i\) 个数中选定 \(k\) 个数,要求第一个数必选。
那么新的 dp 状态就是:设 \(f_{i,j}\) 表示和为 \(i\),且已经选了 \(j\) 个数,方案数乘上 \(-1\) 的选的数的个数次方。对于每个 \(p\),\(p\) 在集合中的转移有两条(按照背包 dp 的方式转移,\(p\) 不在集合中的不用考虑):
- \(f_{i,j} \leftarrow -f_{i-p,j}\),表示 \(p\) 放入,但是不选;要求 \(i-p\neq 0\)(即不是第一个数);
- \(f_{i,j}\leftarrow -f_{i-p,j-1}\),表示 \(p\) 放入且选;要求 \(j>0\)(显然)。
初值 \(f_{0,0}=(-1)^k\),答案为 \(\sum\dfrac mi f_{i,k}\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P = 998244353;
int f[10010][13];
int n, k, m, p[1010];
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;
}
int main(){
f[0][0] = 1;
scanf("%d%d%d", &n, &k, &m);
k = n - k + 1;
for(int i = 1; i <= n; ++ i){
scanf("%d", &p[i]);
for(int v = m; v >= p[i]; -- v){
for(int c = 0; c <= k; ++ c){
if(c){
f[v][c] -= f[v-p[i]][c-1];
if(f[v][c] < 0){
f[v][c] += P;
}
}
if(v > p[i]){
f[v][c] -= f[v-p[i]][c];
if(f[v][c] < 0){
f[v][c] += P;
}
}
}
}
}
ll ans = 0;
for(int i = 1; i <= m; ++ i){
ans += qp(i, P-2) % P * f[i][k] % P;
ans %= P;
}
printf("%lld\n", ans * qp(P-1, k) % P * m % P);
return 0;
}

浙公网安备 33010602011771号