两个序列
a
a
a 和
b
b
b ,长度为
n
n
n ,每个数均为
0
0
0 或者
1
1
1 到
n
n
n 之间的整数,且
1
1
1 到
n
n
n 的每个数出现且仅出现一次
每次操作可以在第一个序列里抽走一个数放到第二个序列的末尾,并把第二个序列的开头元素拿到第一个序列内
(从第一个序列中抽走的数的位置是任意的)
求把第二个序列变成依次从
1
1
1 到
n
n
n 的最少步数
1
≤
n
≤
2
×
1
0
5
1\le n\le2\times10^5
1≤n≤2×105
Solution
先判定
b
b
b 是否存在形如
1
2
3
…
x
1\text{ }2\text{ }3\text{ }\dots\text{ }x
123…x 的后缀
如果存在,则尝试在第
i
i
i 次操作时把数
x
+
i
x+i
x+i 放到序列末尾
如果尝试成功(操作时对于每个
i
i
i 都满足数
x
+
i
x+i
x+i 存在于序列
a
a
a 内)那么这就是最优方案,操作次数
n
−
x
n-x
n−x
否则回到开始,每一次操作都把
0
0
0 放到序列末尾,直到可以放
1
1
1 为止
当然,在何时放
1
1
1 需要满足一定的条件
可以发现,如果
b
b
b 中
x
x
x 在位置
y
y
y (
x
≠
0
x\ne 0
x̸=0 ),那么
1
1
1 至少需要在第
y
−
x
+
2
y-x+2
y−x+2 次操作时被放入序列,否则数
x
x
x 需要被放入序列时
x
x
x 不在序列
a
a
a 内
放了
1
1
1 之后,就可以依次把
2
2
2 到
n
n
n 的数放到末尾了
于是这时答案为
n
−
1
+
max
b
i
≠
0
{
i
−
b
i
+
2
}
n-1+\max_{b_i\ne 0}\{i-b_i+2\}
n−1+bi̸=0max{i−bi+2}
注意
b
b
b 全
0
0
0 的情况
O
(
n
)
O(n)
O(n)
Code
#include<bits/stdc++.h>inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}constint N =2e5+5;int n, a[N], pos1, ans, lst;intmain(){
n =read();for(int i =1; i <= n; i++)read();for(int i =1; i <= n; i++) a[i]=read();for(int i =1; i <= n; i++){if(a[i]==1) pos1 = i;if(a[i]) lst = i;}bool is =1;for(int i = pos1; i <= n; i++)if(a[i]!= i - pos1 +1) is =0;if(is){bool can =1;for(int i =1; i < pos1; i++)if(a[i]&&
i -(a[i]-(n - pos1 +1)-1)>=1) can =0;if(can)return std::cout << pos1 -1<< std::endl,0;}
pos1 =2;for(int i =1; i <= n; i++)if(a[i])
pos1 = std::max(pos1, i -(a[i]-3));
std::cout << pos1 + n -2<< std::endl;return0;}
B
Meaning
给定一棵树
求有多少个不同的
1
1
1 到
n
n
n 排列
p
p
p
使得对于任意两个满足树上存在边
(
p
i
,
p
j
)
(p_i,p_j)
(pi,pj) 和
(
p
x
,
p
y
)
(p_x,p_y)
(px,py) 的二元组
(
i
,
j
)
(
x
,
y
)
(i,j)(x,y)
(i,j)(x,y) 都满足圆周上有
n
n
n 个点的圆环上点
i
i
i 和
j
j
j 连成的边以及点
x
x
x 和
y
y
y 连成的边不在除端点之外的点相交
求方案数对
998244353
998244353
998244353 取模
2
≤
n
≤
2
×
1
0
5
2\le n\le2\times10^5
2≤n≤2×105
Solution
转化一下
定
1
1
1 为根,先把
1
1
1 在圆环上定一个位置,有
n
n
n 种方案
然后对每个子树分配环上的一个连续段
然后对每个子树(连续段)分别处理
定义
f
[
u
]
f[u]
f[u] 表示为子树
u
u
u 分配位置的方案数(不包括为
1
1
1 定位)
当
u
=
1
u=1
u=1 时显然有
d
u
!
d_u!
du! 种方案(
d
u
d_u
du 为
u
u
u 的子节点数)
否则在子树
u
u
u 所属的连续段上,相当于需要为
u
u
u 的所有子树,包括上
u
u
u 分别分配一个连续段,共
d
u
+
1
d_u+1
du+1 个连续段,方案数
(
d
u
+
1
)
!
(d_u+1)!
(du+1)!
f
[
u
]
=
d
u
!
×
(
d
u
+
1
)
[
u
>
1
]
∏
v
∈
s
o
n
(
u
)
f
[
v
]
f[u]=d_u!\times(d_u+1)^{[u>1]}\prod_{v\in son(u)}f[v]
f[u]=du!×(du+1)[u>1]v∈son(u)∏f[v]
这样答案就是所有点的度数(无根树)阶乘之积乘上
n
n
n
O
(
n
)
O(n)
O(n)
Code
#include<bits/stdc++.h>inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}constint N =2e5+5, M = N <<1, ZZQ =998244353;int n, ecnt, nxt[M], adj[N], go[M], f[N];voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v;
nxt[++ecnt]= adj[v]; adj[v]= ecnt; go[ecnt]= u;}voiddfs(int u,int fu){int cnt =0, fc =1, fac =1;
f[u]=1;for(int e = adj[u], v; e; e = nxt[e])if((v = go[e])!= fu)dfs(v, u), cnt++;for(int i =1; i <= cnt; i++)
fac =1ll* fac * i % ZZQ;
f[u]= u ==1? fac :1ll* fac *(cnt +1)% ZZQ;for(int e = adj[u], v; e; e = nxt[e])if((v = go[e])!= fu)
f[u]=1ll* f[u]* f[v]% ZZQ;}intmain(){int x, y;
n =read();for(int i =1; i < n; i++)
x =read(), y =read(),add_edge(x, y);dfs(1,0);
std::cout <<1ll* n * f[1]% ZZQ << std::endl;return0;}
C
Meaning
有
n
n
n 个二元组,第
i
i
i 个形如
(
a
i
,
w
i
)
(a_i,w_i)
(ai,wi)
a
i
a_i
ai 是一个
0
/
1
0/1
0/1 值
w
i
w_i
wi 是一个正整数
接下来有
m
m
m 次操作
每次操作会随机地选择一个二元组,其中选出第
i
i
i 个二元组的概率为
w
i
∑
j
=
1
n
w
j
\frac{w_i}{\sum_{j=1}^nw_j}
∑j=1nwjwi
先定义状态
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示
i
i
i 次操作中选出
a
a
a 为
0
0
0 的二元组恰好
j
j
j 次的概率
f
[
0
]
[
0
]
=
1
f[0][0]=1
f[0][0]=1
f
[
i
+
1
]
[
j
]
+
=
s
1
+
i
−
j
s
0
+
s
1
+
i
−
2
j
f
[
i
]
[
j
]
f[i+1][j]+=\frac{s_1+i-j}{s_0+s_1+i-2j}f[i][j]
f[i+1][j]+=s0+s1+i−2js1+i−jf[i][j]
f
[
i
+
1
]
[
j
+
1
]
+
=
s
0
−
j
s
0
+
s
1
+
i
−
2
j
f
[
i
]
[
j
]
f[i+1][j+1]+=\frac{s_0-j}{s_0+s_1+i-2j}f[i][j]
f[i+1][j+1]+=s0+s1+i−2js0−jf[i][j]
其中
s
0
=
∑
a
i
=
0
w
i
s_0=\sum_{a_i=0}w_i
s0=∑ai=0wi ,
s
1
=
∑
a
i
=
1
w
i
s_1=\sum_{a_i=1}w_i
s1=∑ai=1wi
乍一看这个 DP 好像没什么用,不妨分析一下性质
以
a
i
=
1
a_i=1
ai=1 为例,可以发现一个很优秀的性质
如果某一次选出了的二元组的
a
a
a 为
0
0
0 ,那么在下一次选出的 a 为 1 的条件下,选到的是第
i
i
i 个二元组的条件概率不变,仍然为现在的
w
i
w_i
wi 除以现在
a
=
1
a=1
a=1 的
w
w
w 之和
a
i
=
0
a_i=0
ai=0 同理
定义一次操作「对
x
x
x 有效」,当且仅当这次操作选出的二元组的
a
a
a 和第
x
x
x 个二元组的
a
a
a 一样
再设状态
g
x
[
i
]
g_x[i]
gx[i] 表示第
x
x
x 个二元组,在
i
i
i 次对
x
x
x 有效的操作之后
w
x
w_x
wx 的期望
答案就很好算了
如果
a
x
=
0
a_x=0
ax=0 则最后
w
x
w_x
wx 的期望为
∑
i
=
0
m
f
[
m
]
[
i
]
×
g
x
[
i
]
\sum_{i=0}^mf[m][i]\times g_x[i]
i=0∑mf[m][i]×gx[i]
否则
a
x
=
1
a_x=1
ax=1
∑
i
=
0
m
f
[
m
]
[
i
]
×
g
x
[
m
−
i
]
\sum_{i=0}^mf[m][i]\times g_x[m-i]
i=0∑mf[m][i]×gx[m−i]
考虑怎么求这个
g
x
[
i
]
g_x[i]
gx[i] ,直接求和算答案是
O
(
n
m
)
O(nm)
O(nm) ,无法通过 C2
以
a
x
=
1
a_x=1
ax=1 为例
g
x
[
0
]
=
w
x
g_x[0]=w_x
gx[0]=wx
g
x
[
i
]
=
g
x
[
i
−
1
]
+
g
x
[
i
−
1
]
s
1
+
i
−
1
g_x[i]=g_x[i-1]+\frac{g_x[i-1]}{s_1+i-1}
gx[i]=gx[i−1]+s1+i−1gx[i−1]
a
x
=
0
a_x=0
ax=0 时
g
x
[
i
]
=
g
x
[
i
−
1
]
−
g
x
[
i
−
1
]
s
0
−
i
+
1
g_x[i]=g_x[i-1]-\frac{g_x[i-1]}{s_0-i+1}
gx[i]=gx[i−1]−s0−i+1gx[i−1]
即
i
−
1
i-1
i−1 次操作之后的期望再加上第
i
i
i 次选出第
i
i
i 个二元组的概率
看上去还是
O
(
n
m
)
O(nm)
O(nm) 的,所以我们需要找性质
把两个式子整理一下
g
x
[
i
]
=
g
x
[
i
−
1
]
×
{
s
0
−
i
s
0
−
i
+
1
a
x
=
0
s
1
+
i
s
1
+
i
−
1
a
x
=
1
g_x[i]=g_x[i-1]\times\begin{cases}\frac{s_0-i}{s_0-i+1}&a_x=0\\\frac{s_1+i}{s_1+i-1}&a_x=1\end{cases}
gx[i]=gx[i−1]×{s0−i+1s0−is1+i−1s1+iax=0ax=1
根据常识
g
x
[
i
]
=
w
x
×
{
s
0
−
i
s
0
a
x
=
0
s
1
+
i
s
1
a
x
=
1
g_x[i]=w_x\times\begin{cases}\frac{s_0-i}{s_0}&a_x=0\\\frac{s_1+i}{s_1}&a_x=1\end{cases}
gx[i]=wx×{s0s0−is1s1+iax=0ax=1
代回答案,当
a
x
=
0
a_x=0
ax=0 时答案为
∑
i
=
0
m
f
[
m
]
[
i
]
×
w
x
×
s
0
−
i
s
0
=
w
x
×
(
∑
i
=
0
m
f
[
m
]
[
i
]
−
∑
i
=
0
m
i
×
f
[
m
]
[
i
]
s
0
)
\sum_{i=0}^mf[m][i]\times w_x\times\frac{s_0-i}{s_0}=w_x\times(\sum_{i=0}^mf[m][i]-\frac{\sum_{i=0}^mi\times f[m][i]}{s_0})
i=0∑mf[m][i]×wx×s0s0−i=wx×(i=0∑mf[m][i]−s0∑i=0mi×f[m][i])
同理
a
x
=
1
a_x=1
ax=1 时
∑
i
=
0
m
f
[
m
]
[
i
]
×
w
x
×
s
1
+
m
−
i
s
1
=
w
x
×
(
∑
i
=
0
m
f
[
m
]
[
i
]
+
∑
i
=
0
m
(
m
−
i
)
×
f
[
m
]
[
i
]
s
1
)
\sum_{i=0}^mf[m][i]\times w_x\times\frac{s_1+m-i}{s_1}=w_x\times(\sum_{i=0}^mf[m][i]+\frac{\sum_{i=0}^m(m-i)\times f[m][i]}{s_1})
i=0∑mf[m][i]×wx×s1s1+m−i=wx×(i=0∑mf[m][i]+s1∑i=0m(m−i)×f[m][i])
∑
i
=
0
m
f
[
m
]
[
i
]
\sum_{i=0}^mf[m][i]
∑i=0mf[m][i] ,
∑
i
=
0
m
i
×
f
[
m
]
[
i
]
\sum_{i=0}^mi\times f[m][i]
∑i=0mi×f[m][i] ,
∑
i
=
0
m
(
m
−
i
)
×
f
[
m
]
[
i
]
\sum_{i=0}^m(m-i)\times f[m][i]
∑i=0m(m−i)×f[m][i] 可以 DP 后预处理直接使用
O
(
n
+
m
2
log
(
s
0
+
s
1
+
m
)
)
O(n+m^2\log(s_0+s_1+m))
O(n+m2log(s0+s1+m)) (DP 时需要求逆元)
如果预处理
s
0
+
s
1
±
m
s_0+s_1\pm m
s0+s1±m 的逆元可以做到
O
(
n
+
m
2
)
O(n+m^2)
O(n+m2)
Code
#include<bits/stdc++.h>inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}constint N =2e5+5, M =3005, ZZQ =998244353;int n, m, a[N], b[N], s0, s1, f[M][M], ft, tft, tfmt, i0, i1;intqpow(int a,int b){int res =1;while(b){if(b &1) res =1ll* res * a % ZZQ;
a =1ll* a * a % ZZQ;
b >>=1;}return res;}intmain(){
n =read(); m =read();for(int i =1; i <= n; i++) a[i]=read();for(int i =1; i <= n; i++){
b[i]=read();if(a[i]) s1 += b[i];else s0 += b[i];}
f[0][0]=1;for(int i =0; i < m; i++)for(int j =0; j <= i; j++){int it =qpow(s0 - j + s1 + i - j, ZZQ -2);
f[i +1][j]=(1ll*(s1 + i - j)* it % ZZQ *
f[i][j]+ f[i +1][j])% ZZQ;
f[i +1][j +1]=(1ll*(s0 - j)* it % ZZQ *
f[i][j]+ f[i +1][j +1])% ZZQ;}for(int i =0; i <= m; i++){
ft =(ft + f[m][i])% ZZQ;
tft =(1ll* i * f[m][i]+ tft)% ZZQ;
tfmt =(1ll*(m - i)* f[m][i]+ tfmt)% ZZQ;}
i0 =qpow(s0, ZZQ -2); i1 =qpow(s1, ZZQ -2);for(int i =1; i <= n; i++)if(a[i])printf("%d\n",(1ll* b[i]* ft +1ll* b[i]* i1 % ZZQ * tfmt)% ZZQ);elseprintf("%d\n",(1ll* b[i]* ft -1ll* b[i]* i0 % ZZQ * tft % ZZQ + ZZQ)% ZZQ);return0;}
D
Meaning
一个
n
×
n
n\times n
n×n 的网格,要求在一些格子上放传送门
一对传送门
(
A
,
B
)
(A,B)
(A,B) 表示走到
A
A
A 会传送到
B
B
B ,走到
B
B
B 会传送到
A
A
A
现在需要构造一种放置传送门的方案(必须成对放,一个格子不能放超过一个传送门)
使得对于所有的
i
i
i ,从第
1
1
1 列第
i
i
i 个格子出发一直向右走最终达到第
n
n
n 列第
r
i
r_i
ri 个格子,并且从第
1
1
1 行第
i
i
i 个格子出发一直向下走最终达到第
n
n
n 行第
c
i
c_i
ci 个格子
1
≤
n
≤
1000
1\le n\le 1000
1≤n≤1000
r
r
r 和
c
c
c 都是
1
1
1 到
n
n
n 的排列
Solution
考虑如何缩减问题的规模从
n
×
n
n\times n
n×n 到
(
n
−
1
)
×
(
n
−
1
)
(n-1)\times(n-1)
(n−1)×(n−1)
如果
r
1
=
c
1
=
1
r_1=c_1=1
r1=c1=1 跳过
否则如果
r
x
=
c
y
=
1
r_x=c_y=1
rx=cy=1 ,那么放一对传送门,位置为
(
x
,
1
)
(
1
,
y
)
(x,1)(1,y)
(x,1)(1,y)
这样显然从第
(
x
,
1
)
(x,1)
(x,1) 出发会最终到达
(
1
,
n
)
(1,n)
(1,n) ,
(
1
,
y
)
(1,y)
(1,y) 出发最终达到
(
n
,
1
)
(n,1)
(n,1)
#include<bits/stdc++.h>inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}constint N =1005;int n, m, ir[N], ic[N], a[N], ia[N], b[N], ib[N],
ans1x[N], ans1y[N], ans2x[N], ans2y[N];intmain(){
n =read();for(int i =1; i <= n; i++) ir[read()]= a[i]= ia[i]= i;for(int i =1; i <= n; i++) ic[read()]= b[i]= ib[i]= i;for(int i =1; i <= n; i++){if(ir[i]== a[i]&& ic[i]== b[i])continue;int x = ia[ir[i]], y = ib[ic[i]];
ans1x[++m]= x; ans1y[m]= ans2x[m]= i;
ans2y[m]= y;
a[x]= a[i]; b[y]= b[i];
ia[a[i]]= x; ib[b[i]]= y;}
std::cout << m << std::endl;for(int i =1; i <= m; i++)printf("%d %d %d %d\n", ans1x[i], ans1y[i], ans2x[i], ans2y[i]);return0;}