FJWC2020 Day5 题解
T1
Description
有一个长度为 \(n\) 的序列 \(a_1, a_2,\cdots a_n\),\(a_i\) 为在 \([l_i, r_i]\) 中独立均匀随机生成的实数。
若 \(1 \leq i < j \leq n\) 且 \(a_i > a_j\),我们称 \((i, j)\) 为一个逆序对。
你需要求出这个序列逆序对个数的期望值。为了简单起见,你只需要求出这个期望值对 \(998244353\) 取模的值。
\(n\le 10^5\),时空限制 \(1s/1G\)。
Solution
首先,答案为
考虑对于两个区间 \([l,r],[s,t]\),若 \(a\in[l,r],b\in[s,t]\),怎么计算 \(P(a>b)\)。
记 \(f(x,y,z)\) 表示在区间 \([x,y]\) 中选一个点 \(t\),\(t<z\) 的概率。
那么
我们把所有区间的端点都 \(+2\)。
接下来,根据
可得
因此
到这里,我们已经把区间 \([l,r]\) 都拆成了 \([1,r]\) 减去 \([1,l]\) 的形式。
记
其中
所以画出 \(g(x)\) 的函数图像,计算面积即可得到 \(\int_1^rg(x)\text{d}x\)。
计算另外三项同理。
直接枚举两个区间是 \(O(n^2)\) 的,考虑优化。
把所有区间端点离散化,倒序枚举区间,并用树状数组维护:
对 \(r,t,r,s,l,t,l,s\) 的大小关系分类讨论即可。
时间复杂度 \(O(n\log n)\)。
当然也可以不要把区间 \([l,r]\) 拆成 \([1,r]-[1,l]\),直接分类讨论 \(l,r,s,t\) 之间的大小关系。时间复杂度 \(O(n\log^2n)\),只要你的电脑跑的够快,你的常数够优秀,就能跑进 \(1s\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
template <class t>
inline void read(t & res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + (ch ^ 48);
}
const int e = 200005, mod = 998244353, inv2 = 499122177;
struct point
{
int l, r;
}a[e];
int n, ans, b[e], m, pos_l[e], pos_r[e], inv_len[e];
int cf[e], cg[e], ch[e], df[e], dg[e], dh[e];
inline void add(int &x, int y)
{
(x += y) >= mod && (x -= mod);
}
inline void del(int &x, int y)
{
(x -= y) < 0 && (x += mod);
}
inline int plu(int x, int y)
{
add(x, y);
return x;
}
inline int sub(int x, int y)
{
del(x, y);
return x;
}
inline int s2(int x)
{
return (ll)x * x % mod;
}
inline int ksm(int x, int y)
{
int res = 1;
while (y)
{
if (y & 1) res = (ll)res * x % mod;
y >>= 1;
x = (ll)x * x % mod;
}
return res;
}
inline int calc1(int x, int y)
{
return (ll)s2(x - 1) * inv2 % mod * ksm(y - 1, mod - 2) % mod;
}
inline int calc2(int x, int y)
{
return sub(x - 1, (ll)(y - 1) * inv2 % mod);
}
inline void add_pre(int *c, int x, int v)
{
for (int i = x; i <= m; i += i & -i) add(c[i], v);
}
inline void add_suf(int *c, int x, int v)
{
for (int i = x; i; i -= i & -i) add(c[i], v);
}
inline int ask_pre(int *c, int x)
{
int res = 0;
for (int i = x; i; i -= i & -i) add(res, c[i]);
return res;
}
inline int ask_suf(int *c, int x)
{
int res = 0;
for (int i = x; i <= m; i += i & -i) add(res, c[i]);
return res;
}
int main()
{
freopen("rng.in", "r", stdin);
freopen("rng.out", "w", stdout);
int i, j;
read(n);
b[m = 1] = 1;
for (i = 1; i <= n; i++)
{
read(a[i].l); read(a[i].r);
a[i].l += 2; a[i].r += 2;
b[++m] = a[i].l; b[++m] = a[i].r;
inv_len[i] = ksm(a[i].r - a[i].l, mod - 2);
}
sort(b + 1, b + m + 1);
m = unique(b + 1, b + m + 1) - b - 1;
for (i = 1; i <= n; i++)
{
pos_l[i] = lower_bound(b + 1, b + m + 1, a[i].l) - b;
pos_r[i] = lower_bound(b + 1, b + m + 1, a[i].r) - b;
}
for (i = n; i >= 1; i--)
{
int l = a[i].l, r = a[i].r, pl = pos_l[i], pr = pos_r[i], rt, rs, lt, ls;
rt = (ll)ask_suf(cf, pr) * inv2 % mod * s2(r - 1) % mod;
rt = (rt + (ll)ask_pre(cg, pr - 1) * (r - 1)) % mod;
rt = (rt - (ll)ask_pre(ch, pr - 1) * inv2) % mod;
rt += rt >> 31 & mod;
rs = (ll)ask_suf(df, pr) * inv2 % mod * s2(r - 1) % mod;
rs = (rs + (ll)ask_pre(dg, pr - 1) * (r - 1)) % mod;
rs = (rs - (ll)ask_pre(dh, pr - 1) * inv2) % mod;
rs += rs >> 31 & mod;
lt = (ll)ask_suf(cf, pl) * inv2 % mod * s2(l - 1) % mod;
lt = (lt + (ll)ask_pre(cg, pl - 1) * (l - 1)) % mod;
lt = (lt - (ll)ask_pre(ch, pl - 1) * inv2) % mod;
lt += lt >> 31 & mod;
ls = (ll)ask_suf(df, pl) * inv2 % mod * s2(l - 1) % mod;
ls = (ls + (ll)ask_pre(dg, pl - 1) * (l - 1)) % mod;
ls = (ls - (ll)ask_pre(dh, pl - 1) * inv2) % mod;
ls += ls >> 31 & mod;
add_suf(cf, pr, inv_len[i]);
add_pre(cg, pr, (ll)(r - 1) * inv_len[i] % mod);
add_pre(ch, pr, (ll)s2(r - 1) * inv_len[i] % mod);
add_suf(df, pl, inv_len[i]);
add_pre(dg, pl, (ll)(l - 1) * inv_len[i] % mod);
add_pre(dh, pl, (ll)s2(l - 1) * inv_len[i] % mod);
int res = plu(rt, ls);
del(res, plu(rs, lt));
res = (ll)res * inv_len[i] % mod;
add(ans, res);
}
cout << ans << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
T2
Description
有两个正整数 \(n\) 和 \(m\)。
我们考虑所有长度为 \(n\),每个元素在 \([1,m]\) 的整数序列。对于所有整数序列,设 \(\mathrm{lcm}\) 为这个序列中元素的最小公倍数,\(\mathrm{gcd}\) 为这个序列中元素的最大公约数,我们希望求出 \(\mathrm{lcm}^{\mathrm{gcd}}\)。你需要对于所有这些整数序列,计算 \(\mathrm{lcm}^{\mathrm{gcd}}\) 之积 \(\bmod\ 998244353\)。
形式化地,你需要计算
\(n\le 2×10^5\),时空限制 \(1s/1G\)。
Solution
前置知识:\(\mathrm{lcm}-\gcd\) 容斥:$$\mathrm{lcm}(T)=\prod_{S\in T}\gcd(S)^{op(S)}$$
其中 \(op(S)=(-1)^{|S|+1}\)。
那么原式化为:
记 \(b(d)\) 为:
那么
记 \(a(d)\) 为:
反演得到:
问题转化为求 \(a\) 数组。
根据 \(d\ |\gcd(T)\) 可得 \(T\) 中所有元素都是 \(d\) 的倍数,那么我们把它们全部除以 \(d\),也就是每个 \(\gcd(S)\) 都要除以 \(d\)。
接下来,预处理 \(c[\lfloor\frac{m}{d}\rfloor]\) 表示:
其中 \(T\) 只含 \(1\sim\lfloor\frac{m}{d}\rfloor\) 的数。
记 \(f(k)\) 为:
那么 \(c[\lfloor\frac{m}{d}\rfloor]\) 就是:$$\prod_kk^{f(k)}$$
记 \(g(k)\) 为:
反演得到:
问题转化为求 \(g(k)\)。
令 \(a=\lfloor\frac{m}{kd}\rfloor,b=\lfloor\frac{m}{d}\rfloor\),易得 \(g(k)\) 为:
发现这很像二项式定理,也就是说,\(g(k)\) 为:
预处理出 \(1^n\sim m^n\) 的值即可 \(O(1)\) 求 \(g(k)\)。
接下来,考虑怎么由 \(c[\lfloor\frac{m}{d}\rfloor]\) 得到 \(a(d)\)。
刚才我们把每个 \(\gcd(S)\) 都除以 \(d\),那么现在要乘上的 \(d\) 的个数就是:
\(T\) 的大小都是 \(n\),那么记 \(cnt_T\) 表示满足 \(d\ |\gcd(T)\) 的 \(T\) 的个数,可得乘上的 \(d\) 的个数为:
显然 \(\sum op(S)\) 为在 \(T\) 中选出非空奇数大小子集的方案数减去选出非空偶数大小子集的方案数,即 \(\sum op(S)=1\)。
而 \(cnt_T\) 就是每个元素都在 \([1,\lfloor\frac{m}{d}\rfloor]\) 的长度为 \(n\) 的序列的个数,即 \(\lfloor\frac{m}{d}\rfloor^n\)。
注意所有作为指数的元素在计算时都要 \(\text{mod}\ 998244352\)。
时间复杂度 \(O(\sum_{i=1}^m\frac{m}{i}×\log(\frac{m}{i}))\),不会超过 \(O(m\log^2m)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod2 = 998244352, mod3 = mod2 + 1, e = 4e5 + 5;
bool bo[e];
int miu[e], p2[e], p3[e], n, m, g[e], f[e], a[e], b[e], c[e], ans = 1, inv_a[e];
int real_a[e];
inline int ksm2(int x, int y)
{
int res = 1;
while (y)
{
if (y & 1) res = (ll)res * x % mod2;
y >>= 1;
x = (ll)x * x % mod2;
}
return res;
}
inline int ksm3(int x, int y)
{
int res = 1;
while (y)
{
if (y & 1) res = (ll)res * x % mod3;
y >>= 1;
x = (ll)x * x % mod3;
}
return res;
}
inline void del2(int &x, int y)
{
(x -= y) < 0 && (x += mod2);
}
inline void init()
{
int i, j;
for (i = 1; i <= m; i++) p2[i] = ksm2(i, n), p3[i] = ksm3(i, n);
for (i = 1; i <= m; i++) miu[i] = 1;
for (i = 2; i <= m; i++)
if (!bo[i])
{
miu[i] = -1;
for (j = i << 1; j <= m; j += i)
{
bo[j] = 1;
if (j / i % i == 0) miu[j] = 0;
else miu[j] = -miu[j];
}
}
}
inline int solve(int m)
{
int i, j, res = 1;
for (i = 1; i <= m; i++)
{
g[i] = p2[m - m / i];
f[i] = 0;
del2(g[i], p2[m]);
if (g[i]) g[i] = mod2 - g[i];
}
for (i = 1; i <= m; i++)
for (j = i; j <= m; j += i)
{
int op = miu[j / i];
op == 1 && (f[i] += g[j]) >= mod2 && (f[i] -= mod2);
op == -1 && (f[i] -= g[j]) < 0 && (f[i] += mod2);
}
for (i = 1; i <= m; i++) res = (ll)res * ksm3(i, f[i]) % mod3;
return res;
}
int main()
{
freopen("lg.in", "r", stdin);
freopen("lg.out", "w", stdout);
cin >> n >> m;
init();
int i, j;
for (i = 1; i <= m; i = j + 1)
{
j = min(m, m / (m / i));
c[m / i] = solve(m / i);
}
for (i = 1; i <= m; i++)
{
a[i] = (ll)ksm3(i, p2[m / i]) * c[m / i] % mod3;
inv_a[i] = ksm3(a[i], mod3 - 2);
}
for (i = 1; i <= m; i++) b[i] = real_a[i] = 1;
for (i = 1; i <= m; i++)
for (j = i; j <= m; j += i)
if (miu[j / i] == 1) b[i] = (ll)b[i] * a[j] % mod3;
else if (miu[j / i] == -1) b[i] = (ll)b[i] * inv_a[j] % mod3;
for (i = 1; i <= m; i++) ans = (ll)ans * ksm3(b[i], i) % mod3;
cout << ans << endl;
return 0;
}
T3
Description
有一个 \(\{1,2,\cdots,n\}\) 的排列 \(\{a_1,a_2,\cdots,a_n\}\),你需要把它变成 \(\{1,2,\cdots,n\}\)。
你可以进行两个阶段的操作:
- 第一阶段中,你可以重复若干次,每次任选两个相邻元素并进行交换。
- 第二阶段中,你可以重复若干次,每次修改一个位置上的元素。第二阶段中,你可以任意修改元素,即使中间过程中序列不再是排列仍然可行。
你需要最小化两个阶段操作的总次数,并给出一种总操作次数最小的合法方案。
为了简单起见,你只需要输出第一阶段的操作,SPJ 会自动计算第二阶段的最优操作,并将操作总次数和最优总次数比较。
\(n\le 200000\),时空限制 \(1s/1G\)。
Solution
首先,如果存在一次操作交换了 \((a_i,a_{i+1})\),则把 \(i\) 和 \(i+1\) 连边。
这样使得 \(n\) 个点被划分为若干个段。
显然交换相邻两个数,使得排列有序的最小次数为排列的逆序对数。
因为每次可以先把最大的数换到最后,再把第二大的数换到倒数第二个,以此类推,显然最优交换次数就是逆序对数。
那么段 \([l,r]\) 的代价就是 \(a_l\sim a_r\) 的逆序对数,记为 \(x\)。
如果 \(x\ge n\),可以直接把 \(a_l\sim a_r\) 在第二阶段一个一个改掉。
因为 \([l,r]\) 连通,所以 \(x\ge n-1\),也就是说,只要考虑 \(x=n-1\) 的段。
答案的上界为 \(n\),而每个满足 \(\lceil\) \(x=n-1\) 且 \(a_l\sim a_r\) 正好包含 \([l,r]\) 中所有数 \(\rfloor\) 的段都会使答案减 \(1\)。
问题转化为取出最多互不相交的满足条件的段。
考虑对于每个 \(r\),找到最大的 \(l\) 使得满足以下条件:
- \(a_l\sim a_r\) 正好包含了 \([l,r]\) 中的所有数
- \(a_l\sim a_r\) 的逆序对数为 \(r-l\)
为什么是最大的 \(l\) ?
因为如果 \([l,r]\) 有一个子区间 \([s,r]\) 也满足条件,我们可以舍弃 \([l,r]\),取 \([s,r]\),答案不会变劣。
怎么找最大的 \(l\)?
显然满足条件 \(1,2\) 的最大的 \(l\) 也是满足条件 \(1\) 的最大的 \(l\)。
条件 \(1\) 可以看作是:
- \(\max(a_l\sim a_r)=r\)
- \(\sum_{i=l}^ra_i-i=0\)
因此,枚举 \(r=1\sim n\),记 \(b[r]=\sum_{i=1}^ra_i-i\)。
找到满足 \(b[l-1]=b[r]\) 的最大的 \(l\),判断是否满足 \(\max(a_l\sim a_r)=r\) 即可。
接下来考虑条件 \(2\),即求区间逆序对数。
由于 \(a_l\sim a_r\) 正好包含 \([l,r]\) 中的所有数,所以 \(a_1\sim a_{l-1}\) 中的每个数,要么大于 \(a_l\sim a_r\) 中的每一个数,要么小于 \(a_l\sim a_r\) 中的每一个数。
也就是说,记 \(c[i]\) 表示 \(a_1\sim a_i\) 的逆序对数,那么 \(a_l\sim a_r\) 的逆序对数就是 \(c[r]-c[l-1]+a_1\sim a_{l-1}\) 中大于 \(r\) 的数的个数 \(\times(r-l+1)\)。
利用可持久化线段树或树状数组即可求出。
接下来,由于每个 \(r\) 对应唯一的 \(l\),可以 \(\text{dp}\) 求出最多能取出多少段。
\(\text{dp}\) 的过程记录转移,即可取出最优方案下的所有段,然后把每个段冒泡排序即可。
时间复杂度 \(O(n\log n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
template <class t>
inline void read(t & res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + (ch ^ 48);
}
const int e = 2e5 + 5, o = e * 20;
struct node
{
int l, r, w;
}tr[o];
int tot, a[e], n, f[e], rt[e], st[e][18], c[e], logn[e], pool;
int pos[e], g[e], L[e], pre[e], ans[e];
ll b[e];
map<ll, int>lst;
inline void change(int x, int v)
{
for (int i = x; i; i -= i & -i) c[i] += v;
}
inline int query(int x)
{
int res = 0;
for (int i = x; i <= n; i += i & -i) res += c[i];
return res;
}
inline void insert(int y, int &x, int l, int r, int s)
{
tr[x = ++pool] = tr[y];
tr[x].w++;
if (l == r) return;
int mid = l + r >> 1;
if (s <= mid) insert(tr[y].l, tr[x].l, l, mid, s);
else insert(tr[y].r, tr[x].r, mid + 1, r, s);
}
inline int query(int x, int l, int r, int s, int t)
{
if (l == s && r == t) return tr[x].w;
int mid = l + r >> 1;
if (t <= mid) return query(tr[x].l, l, mid, s, t);
else if (s > mid) return query(tr[x].r, mid + 1, r, s, t);
else return query(tr[x].l, l, mid, s, mid) + query(tr[x].r, mid + 1, r, mid + 1, t);
}
inline void init()
{
int i, j;
logn[0] = -1;
for (i = 1; i <= n; i++) logn[i] = logn[i >> 1] + 1, st[i][0] = a[i];
for (j = 1; (1 << j) <= n; j++)
for (i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
inline int ask(int l, int r)
{
int k = logn[r - l + 1];
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
int main()
{
freopen("pm.in", "r", stdin);
freopen("pm.out", "w", stdout);
int i, j; ll sum = 0;
read(n); lst[0] = 1;
for (i = 1; i <= n; i++)
{
read(a[i]);
insert(rt[i - 1], rt[i], 1, n, a[i]);
b[i] = b[i - 1] + query(a[i] + 1);
change(a[i], 1);
sum += a[i] - i;
L[i] = lst[sum];
lst[sum] = i + 1;
pos[a[i]] = i;
}
init();
for (i = 1; i <= n; i++)
{
if (L[i])
{
int l = L[i], r = i;
ll res = b[r] - b[l - 1];
res -= (ll)(r - l + 1) * query(rt[l - 1], 1, n, r, n);
if (res == r - l && ask(l, r) == r)
{
f[i] = f[pre[l - 1]] + 1;
g[i] = pre[l - 1];
}
}
pre[i] = pre[i - 1];
if (f[pre[i - 1]] < f[i]) pre[i] = i;
}
int r = pre[n];
while (r)
{
int l = L[r];
for (i = r; i >= l; i--)
{
int x = pos[i];
for (j = x; j < i; j++)
{
swap(a[j], a[j + 1]);
ans[++tot] = j;
pos[a[j]] = j;
pos[a[j + 1]] = j + 1;
}
}
r = g[r];
}
printf("%d\n", tot);
for (i = 1; i <= tot; i++) printf("%d\n", ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}