​Link​

A. 整数数列的解码

我的思路比较奇特。

用树状数组存下来前缀 \(-1\) 的个数,对于每个编码前的序列单独求答案。

不难看出第 \(i\) 个序列的第一个元素是 \(b_i\),第二个元素是\(b_{n+i}\),但是如果有一个序列已经结束了,就不是 \(n+i\) 了,而是 \(n+i-1\),所以步长应该是 \(n-qry(j)\),其中 \(qry(j)\) 为 \(j\) 前面 \(-1\) 的个数。

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

int rd()
{
int x = 0, f = 1;
char c = getchar();
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x * f;
}

const int N = 3e5 + 5;
int n, m;
int b[N], a[N];
bool flag[N];
vector <int> ans[N];
int c[N];

void add(int x)
{
for(; x <= m; x += x&-x)
c[x]++;
}

int qry(int x)
{
int res = 0;
for(; x; x -= x&-x)
res += c[x];
return res;
}

int main()
{
m = rd();
for(int i = 1; i <= m; i++)
{
b[i] = rd();
if(b[i] == -1) n++, add(i);
}
printf("%d\n", n);
for(int i = 1; i <= n; i++)
{
a[0] = 0;
for(int j = i; j <= m; j += n - qry(j))
{
if(b[j] == -1) break;
a[++a[0]] = b[j];
}
for(int j = 0; j <= a[0]; j++)
printf("%d ", a[j]);
puts("");
}
return 0;
}


还有一种写法是记录两个变量,一个扫 \(m\),一个扫 \(n\),同时记录下来第几个序列已经结束了,在被扫到时跳过即可。

但是这样的做法会被卡,只有59pts。

B. 优秀的子集

设 \(f_{i}\) 表示覆盖到 \(j\) 的线段集合的方案数。

先将序列以 \(l\) 为第一关键字,\(r\) 为第二关键字从小到大排序,将 \(n\) 个序列按照“连通性”分成若干段,每一段都独立计算,最后将这几段的方案数乘起来就是答案。

怎么分段?

先选定线段 \(a_i\),然后用一个指针 \(pos\) 往后扫,直到 ​​a[pos].l > a[i].r​​,

接下来 ​​i = pos + 1​​。

思考枚举到一段线段 \([i,pos]\) 是“连通”的,如何更新答案。

  1. \(f[a[i].r]++\) 因为线段 \(a_i\) 是必选的,所以会多一种覆盖到 \(a[i].r\)方案。
  2. \(f[a[j].r] += \sum_{k = a[j].l}^{a[j].r}f[k]\ (i+1 \le j \le pos)\) 因为线段 \(a[j]\) 覆盖 \([a[j].l,\ a[j].r]\),所以覆盖到这段区间内的任意一个点之后,再选 \(a_j\),都可以覆盖到 \(a[j].r\)。
  3. 对于每一个 \(j\ (i+1\le j \le pos)\),$f[k] *= 2 \ (a[j].r + 1\le k \le a[i].r) $ 因为线段 \(a_j\) 影响不到 \(k\) 所以选与不选都一样,因此 \(\times 2\)。
  4. 如果 \(a[j].l == a[i].l \ (i + 1 \le j \le pos)\),$f[a[j].r]++ $ 因为线段 \(a_j\) 完全覆盖了 \(a_i\) 所以 \(a_i\) 可以不选,方案数 \(+1\)。

相当于对一个数组进行 单点加、区间乘、区间求和,可以用线段树维护。

注意数据范围,需要离散化。

#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long
#define lc rt<<1
#define rc rt<<1|1

using namespace std;

int rd()
{
int x = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x;
}

const int N = 2e5 + 5;
const int p = 998244353;
int n, cnt;
struct node
{
int l, r;
}a[N];
int b[N << 1];

bool cmp(node x, node y)
{
return x.l == y.l ? x.r < y.r : x.l < y.l;
}

ll sum[N << 3], add[N << 3], mul[N << 3];
void pushup(int rt)
{
sum[rt] = (sum[lc] + sum[rc]) % p;
}

void pushdown(int l, int r, int rt)
{
int mid = (l + r) >> 1;
sum[lc] = (sum[lc] * mul[rt] % p + add[rt] * (mid - l + 1) % p) % p;
sum[rc] = (sum[rc] * mul[rt] % p + add[rt] * (r - mid) % p) % p;

mul[lc] = mul[lc] * mul[rt] % p;
mul[rc] = mul[rc] * mul[rt] % p;

add[lc] = (add[lc] * mul[rt] % p + add[rt]) % p;
add[rc] = (add[rc] * mul[rt] % p + add[rt]) % p;

mul[rt] = 1;
add[rt] = 0;
}

void upd_mul(int L, int R, int v, int l, int r, int rt)
{
if(l > R || r < L) return;
if(L <= l && r <= R)
{
mul[rt] = (mul[rt] * v) % p;
add[rt] = (add[rt] * v) % p;
sum[rt] = (sum[rt] * v) % p;
return;
}
pushdown(l, r, rt);
int mid = (l + r) >> 1;
upd_mul(L, R, v, l, mid, lc);
upd_mul(L, R, v, mid + 1, r, rc);
pushup(rt);
}

void upd_add(int L, int R, int v, int l, int r, int rt)
{
if(l > R || r < L) return;
if(L <= l && r <= R)
{
add[rt] = (add[rt] + v) % p;
sum[rt] = (sum[rt] + v) % p; //单点加
return;
}
pushdown(l, r, rt);
int mid = (l + r) >> 1;
upd_add(L, R, v, l, mid, lc);
upd_add(L, R, v, mid + 1, r, rc);
pushup(rt);
}

ll qry(int L, int R, int l, int r, int rt)
{
if(l > R || r < L) return 0;
if(L <= l && r <= R) return sum[rt];
pushdown(l, r, rt);
int mid = (l + r) >> 1;
return (qry(L, R, l, mid, lc) + qry(L, R, mid + 1, r, rc)) % p;
}

int main()
{
n = rd();
for(int i = 1; i <= n; i++)
{
a[i].l = rd(), a[i].r = rd();
b[++cnt] = a[i].l;
b[++cnt] = a[i].r;
}
sort(b + 1, b + 1 + cnt);
int tot = unique(b + 1, b + 1 + cnt) - (b + 1);
for(int i = 1; i <= n; i++)
{
a[i].l = lower_bound(b + 1, b + 1 + tot, a[i].l) - b;
a[i].r = lower_bound(b + 1, b + 1 + tot, a[i].r) - b;
}
sort(a + 1, a + 1 + n, cmp);
// puts("a:");
// for(int i = 1; i <= n; i++)
// cout<<a[i].l<<" "<<a[i].r<<endl;
ll ans = 1;
for(int i = 1, pos = 1; i <= n; i = pos + 1)
{
int l = a[i].l, r = a[i].r;
while(pos < n && a[pos + 1].l <= r) r = max(r, a[++pos].r);
upd_add(a[i].r, a[i].r, 1, 1, tot, 1);
for(int j = i + 1; j <= pos; j++)
{
ll v = qry(a[j].l, a[j].r, 1, tot, 1);
upd_add(a[j].r, a[j].r, v, 1, tot, 1);
upd_mul(a[j].r + 1, r, 2, 1, tot, 1);
if(a[j].l == l) upd_add(a[j].r, a[j].r, 1, 1, tot, 1);
}
ans = ans * qry(r, r, 1, tot, 1) % p;
}
printf("%lld\n", ans);
return 0;
}


C. 传统艺能

考场上并没有推出来式子(还是太菜了

考虑对于任意两个球统计贡献。

有 \(C_{n}^{2}\) 的概率选中,因为有 \(m\) 个位置,所以有 \(m\) 种情况小Q得分,其中 \(m = \frac{n}{2}\),所以概率为 \(\frac{1}{2 \times (n-1)}\) ,期望和概率一样。

假设第 \(i\) 个球有 \(k_i\) 个球的点数小于 \(a_i\),那么选了 \(i\) 后期望得分为 \(\frac{k_i}{2\times (n-1)}\)。

答案即为 \(\frac{\sum k_i}{2\times (n-1)}\)。

所以只需要求出 \(\sum k_i\)。

这个求法有很多,我还写了离散化。

#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long

using namespace std;

int rd()
{
int x = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x;
}

const int N = 1e6 + 5;
const int p = 998244353;
int n, a[N], s[N];
int b[N], sum;

int qpow(int a, int b, int p)
{
int res = 1;
a %= p;
while(b)
{
if(b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res % p;
}

signed main()
{
n = rd();
for(int i = 1; i <= n; i++)
a[i] = rd(), b[i] = a[i];
sort(b + 1, b + 1 + n);
int tot = unique(b + 1, b + 1 + n) - (b + 1);
for(int i = 1; i <= n; i++)
{
int t = lower_bound(b + 1, b + 1 + tot, a[i]) - b;
s[t]++;
}
// for(int i = 1; i <= tot; i++)
// cout<<i<<" "<<s[i]<<endl;
for(int i = 1; i <= tot; i++)
{
sum = (sum + s[i] * s[i - 1] % p) % p;
s[i] = (s[i] + s[i - 1]) % p;
}
printf("%lld\n", sum * qpow(2 * (n - 1), p - 2, p) % p);
return 0;
}