定义状态
f
[
u
]
[
i
]
f[u][i]
f[u][i] 表示
i
i
i 时刻在点
u
u
u 刚刚上车
g
[
u
]
[
i
]
g[u][i]
g[u][i] 表示
i
i
i 时刻在点
u
u
u 刚刚下车
显然有用的状态只有
O
(
m
)
O(m)
O(m) 个
对于
f
[
u
]
[
i
]
f[u][i]
f[u][i] 直接枚举其在哪个时刻哪个点下车即可转移
对于
g
[
u
]
[
i
]
g[u][i]
g[u][i] 我们有
g
[
u
]
[
i
]
=
min
j
≥
i
{
g
[
u
]
[
j
]
+
A
×
(
j
−
i
)
2
+
B
×
(
j
−
i
)
+
C
}
g[u][i]=\min_{j\ge i}\{g[u][j]+A\times(j-i)^2+B\times(j-i)+C\}
g[u][i]=j≥imin{g[u][j]+A×(j−i)2+B×(j−i)+C}
这是一个显然的斜率优化,对每个点
u
u
u 开一个单调队列维护凸壳即可解决问题
O
(
n
+
m
)
O(n+m)
O(n+m)
只是不知道为什么 sb 暴力能水到 95 ~ 100 分
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
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);if(bo) res =~res +1;}template<classT>inline T Min(const T &a,const T &b){return a < b ? a : b;}typedeflonglong ll;constint N =1e5+5, M = N *3;int n, m, A, B, C, tot =1, ecnt, nxt[M], adj[M], go[M], lst[N], len[N];
ll f[M], g[M];struct point
{int u, tm, id;} a[M];struct pt
{
ll x, y;} p[M];
std::vector<int> conv[N];
std::map<int,int> tim[N];boolcheck(int i,int j,int k){return(p[j].x - p[i].x)*(p[k].y - p[i].y)-(p[j].y - p[i].y)*(p[k].x - p[i].x)<=0;}
ll calc(int t,int i){return p[i].y - p[i].x * A *2* t;}inlineboolcomp(point a, point b){return a.tm > b.tm;}voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v;}intmain(){int x, y, s, t;read(n);read(m);read(A);read(B);read(C);
a[tot =1]=(point){1,0,1}; tim[1][0]=1;while(m--){read(x);read(y);read(s);read(t);if(!tim[x].count(s)) tot++, a[tim[x][s]= tot]=(point){x, s, tot};if(!tim[y].count(t)) tot++, a[tim[y][t]= tot]=(point){y, t, tot};add_edge(tim[x][s], tim[y][t]);}
std::sort(a +1, a + tot +1, comp);for(int i =1; i <= tot; i++){int u = a[i].id, t = a[i].tm, x = a[i].u;if(x == n){g[u]= t;continue;}
f[u]= g[u]=1e18;for(int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
f[u]=Min(f[u], g[v]);if(f[u]<1e18){
p[u]=(pt){t, f[u]+ A * t * t + B * t};while(lst[x]+1< len[x]&&check(u, conv[x][len[x]-1], conv[x][len[x]-2]))
len[x]--, conv[x].pop_back();
len[x]++; conv[x].push_back(u);}while(lst[x]+1< len[x]&&calc(t, conv[x][lst[x]])>=calc(t, conv[x][lst[x]+1]))
lst[x]++;if(lst[x]< len[x])
g[u]=calc(t, conv[x][lst[x]])+ A * t * t - B * t + C;}return std::cout << g[1]<< std::endl,0;}
D1T2
Solution:DP + 多项式插值
不难想到 50 分的 DP
f
[
l
]
[
r
]
[
i
]
f[l][r][i]
f[l][r][i] 表示区间
[
l
,
r
]
[l,r]
[l,r] 的最大值为
i
i
i 的方案数
f
[
l
]
[
r
]
[
i
]
=
∑
l
≤
m
i
d
≤
r
,
A
m
i
d
≤
i
≤
B
m
i
d
,
∣
(
m
i
d
−
l
)
−
(
r
−
m
i
d
)
∣
≤
2
(
∑
j
≤
i
f
[
l
]
[
m
i
d
−
1
]
[
j
]
)
×
(
∑
j
<
i
f
[
m
i
d
+
1
]
[
r
]
j
]
)
f[l][r][i]=\sum_{l\le mid\le r,A_{mid}\le i\le B_{mid},|(mid-l)-(r-mid)|\le 2}(\sum_{j\le i}f[l][mid-1][j])\times(\sum_{j<i}f[mid+1][r]j])
f[l][r][i]=l≤mid≤r,Amid≤i≤Bmid,∣(mid−l)−(r−mid)∣≤2∑(j≤i∑f[l][mid−1][j])×(j<i∑f[mid+1][r]j])
由于有
∣
(
m
i
d
−
l
)
−
(
r
−
m
i
d
)
∣
≤
2
|(mid-l)-(r-mid)|\le 2
∣(mid−l)−(r−mid)∣≤2 的限制
所以实际上合法的
[
l
,
r
]
[l,r]
[l,r] 很少,可拿 50 分
对于取值范围很大的情况,我们也不难想到把所有的取值区间离散化成
O
(
n
)
O(n)
O(n) 段
即
f
[
l
]
[
r
]
[
i
]
[
j
]
f[l][r][i][j]
f[l][r][i][j] 表示区间
[
l
,
r
]
[l,r]
[l,r] 的最大值在第
i
i
i 段内且为
j
j
j
这看上去仿佛复杂度更劣了,但是可以通过简单的归纳得出
在
j
j
j 位于第
i
i
i 段内的前提下,
f
[
l
]
[
r
]
[
i
]
[
j
]
f[l][r][i][j]
f[l][r][i][j] 是关于
j
j
j 的
r
−
l
r-l
r−l 次多项式
所以利用多项式进行 DP ,
F
[
l
]
[
r
]
[
i
]
F[l][r][i]
F[l][r][i] 表示区间
[
l
,
r
]
[l,r]
[l,r] 的最大值在第
i
i
i 段内,方案数关于最大值的
r
−
l
r-l
r−l 次多项式
回到一开始的转移
发现如果
A
m
i
d
≤
i
≤
B
m
i
d
A_{mid}\le i\le B_{mid}
Amid≤i≤Bmid (这里的
A
A
A 和
B
B
B 是离散过的)
那么我们相当于要对
F
[
l
]
[
m
i
d
−
1
]
[
i
]
F[l][mid-1][i]
F[l][mid−1][i] 和
F
[
m
i
d
+
1
]
[
r
]
[
i
]
F[mid+1][r][i]
F[mid+1][r][i] 各求一遍多项式前缀和之后乘起来
(多项式
f
(
x
)
f(x)
f(x) 的前缀和即为
g
(
x
)
=
∑
i
=
0
x
f
(
i
)
g(x)=\sum_{i=0}^xf(i)
g(x)=∑i=0xf(i))
对于多项式前缀和
我们可以先通过插值,预处理出多项式
f
k
(
x
)
=
∑
i
=
0
x
i
k
f_k(x)=\sum_{i=0}^xi^k
fk(x)=∑i=0xik
这样就能
O
(
l
2
)
O(l^2)
O(l2) 地求一个
l
l
l 次多项式的前缀和
其他实现细节略
使用记忆化搜索实现 DP ,复杂度为
O
(
n
∑
状
态
F
[
l
]
[
r
]
[
…
 
]
有
效
(
r
−
l
)
2
)
O(n\sum_{状态F[l][r][\dots]有效}(r-l)^2)
O(n状态F[l][r][…]有效∑(r−l)2)
复杂度看上去好像不太好分析
由于
∣
(
m
i
d
−
l
)
−
(
r
−
m
i
d
)
∣
≤
2
|(mid-l)-(r-mid)|\le 2
∣(mid−l)−(r−mid)∣≤2
故
[
l
,
r
]
[l,r]
[l,r] 转到
[
l
,
m
i
d
]
[l,mid]
[l,mid] 和
[
m
i
d
+
1
]
[
r
]
[mid+1][r]
[mid+1][r] 转不超过
O
(
log
n
)
O(\log n)
O(logn) 次之后会转到
[
i
,
i
]
[i,i]
[i,i]
而
[
l
,
r
]
[l,r]
[l,r] 确定的情况下合法的
m
i
d
mid
mid 最多
3
3
3 个
所以转移到第
i
i
i 层会有不超过
O
(
3
i
)
O(3^i)
O(3i) 个长度为
O
(
n
2
i
)
O(\frac n{2^i})
O(2in) 的区间 ,其中
[
1
,
n
]
[1,n]
[1,n] 在第
0
0
0 层
所以复杂度不超过
O
(
n
∑
状
态
F
[
l
]
[
r
]
[
…
 
]
有
效
(
r
−
l
)
2
)
≤
O
(
n
∑
i
=
0
log
n
(
n
2
i
)
2
×
3
i
)
O(n\sum_{状态F[l][r][\dots]有效}(r-l)^2)\le O(n\sum_{i=0}^{\log n}(\frac n{2^i})^2\times3^i)
O(n状态F[l][r][…]有效∑(r−l)2)≤O(ni=0∑logn(2in)2×3i)
O
(
n
3
∑
i
=
0
log
n
(
3
4
)
i
)
=
O
(
n
3
)
O(n^3\sum_{i=0}^{\log n}(\frac34)^i)=O(n^3)
O(n3i=0∑logn(43)i)=O(n3)
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
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);if(bo) res =~res +1;}constint N =305, M = N <<1, L =2250, ZZQ =1e9+7;int n, a[N], b[N], real[M], tot, prod[N], tmp[N], mpt[N], pmt[N], ans, id[N][N], T;
std::vector<int> f[L][M], cof[N];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;}voidcalc(int n){for(int i =0; i <= n; i++) prod[i]=!i, tmp[i]=0;for(int i =0; i < n; i++)for(int j = i; j >=0; j--)
prod[j +1]=(prod[j +1]+ prod[j])% ZZQ,
prod[j]=(1ll* ZZQ * ZZQ -1ll* prod[j]* i)% ZZQ;int res =0;for(int i =0; i < n; i++){
res =(res +qpow(i, n -2))% ZZQ;for(int j =0; j <= n; j++) mpt[j]= prod[j];int divi =1;for(int j =0; j < n; j++)if(i != j)
divi =1ll* divi *(i - j + ZZQ)% ZZQ;
divi =qpow(divi, ZZQ -2);for(int j = n; j >0; j--)
pmt[j -1]= mpt[j], mpt[j -1]=(1ll* i * mpt[j]+ mpt[j -1])% ZZQ;for(int j =0; j < n; j++)
tmp[j]=(1ll* divi * pmt[j]% ZZQ * res + tmp[j])% ZZQ;}for(int i =0; i < n; i++) cof[n -2].push_back(tmp[i]);}intcalcval(std::vector<int> A,int x){int n = A.size(), res =0;for(int i = n -1; i >=0; i--)
res =(1ll* res * x + A[i])% ZZQ;return res;}
std::vector<int>poly_sum(std::vector<int> A,bool is){
std::vector<int> res;int n = A.size();for(int i =0; i <= n; i++) res.push_back(0);for(int i =0; i < n; i++)for(int j =0; j <= i +1; j++)
res[j]=(1ll* A[i]* cof[i][j]+ res[j])% ZZQ;if(is)for(int i =0; i < n; i++)
res[i]=(res[i]- A[i]+ ZZQ)% ZZQ;return res;}voidDP(int l,int r){if(l > r || id[l][r])return;
id[l][r]=++T;int u = id[l][r];for(int i =1; i < tot; i++)for(int j =0; j <= r - l; j++)
f[u][i].push_back(0);for(int mid = l; mid <= r; mid++){if(abs((mid <<1)- l - r)>2)continue;DP(l, mid -1);DP(mid +1, r);int pl =0, pr =0;for(int i =1; i < tot; i++){
std::vector<int> poly_l, poly_r;if(l < mid){
poly_l =poly_sum(f[id[l][mid -1]][i],0);int delta =(pl -calcval(poly_l, real[i]-1)+ ZZQ)% ZZQ;
poly_l[0]=(poly_l[0]+ delta)% ZZQ;}else poly_l.push_back(1);if(mid < r){
poly_r =poly_sum(f[id[mid +1][r]][i],1);int delta =(pr -calcval(poly_r, real[i])+ ZZQ)% ZZQ;
poly_r[0]=(poly_r[0]+ delta)% ZZQ;}else poly_r.push_back(1);if(a[mid]<= i && i <= b[mid]){
std::vector<int> delta;for(int j =0; j <= r - l; j++) delta.push_back(0);for(int j =0; j < poly_l.size(); j++)for(int k =0; k < poly_r.size(); k++)
delta[j + k]=(1ll* poly_l[j]* poly_r[k]+ delta[j + k])% ZZQ;for(int j =0; j <= r - l; j++)
f[u][i][j]=(f[u][i][j]+ delta[j])% ZZQ;}if(l < mid) pl =calcval(poly_l, real[i +1]-1);if(mid < r) pr =calcval(poly_r, real[i +1]);}}}intmain(){read(n);for(int i =1; i <= n; i++)read(a[i]),read(b[i]),
real[++tot]= a[i], real[++tot]= b[i]+1;
std::sort(real +1, real + tot +1);
tot = std::unique(real +1, real + tot +1)- real -1;for(int i =1; i <= n; i++){
a[i]= std::lower_bound(real +1, real + tot +1, a[i])- real;
b[i]= std::lower_bound(real +1, real + tot +1, b[i]+1)- real -1;}for(int i =2; i <= n +1; i++)calc(i);DP(1, n);for(int i =1; i < tot; i++){
std::vector<int> res =poly_sum(f[1][i],0);
ans =(ans +(calcval(res, real[i +1]-1)-calcval(res, real[i]-1)+ ZZQ)% ZZQ)% ZZQ;}return std::cout << ans << std::endl,0;}
D1T3
Solution:模拟费用流(实际上是贪心)
好题, orz 现场切掉此题的神仙们
考虑构建一张网络流图
所有
n
n
n 个点拆成两个
i
1
i_1
i1 和
i
2
i_2
i2
S
S
S 向所有的
i
1
i_1
i1 连边,容量
1
1
1 费用
a
i
a_i
ai
所有的
i
2
i_2
i2 向
T
T
T 连边,容量
1
1
1 费用
b
i
b_i
bi
i
1
i_1
i1 向
i
2
i_2
i2 连边,容量
1
1
1 费用
0
0
0
外加两个虚拟点
A
A
A 和
B
B
B
<
i
1
,
A
,
1
,
0
>
<i_1,A,1,0>
<i1,A,1,0>
<
B
,
i
2
,
1
,
0
>
<B,i_2,1,0>
<B,i2,1,0>
<
A
,
B
,
K
−
L
,
0
>
<A,B,K-L,0>
<A,B,K−L,0>
不难发现这个图跑
K
K
K 的流量后的最大费用就是答案
考虑每次只增广
1
1
1 的流量,可以发现有五种情况
(1)
S
→
i
1
→
i
2
→
T
S\rightarrow i_1\rightarrow i_2\rightarrow T
S→i1→i2→T
在序列上对应了当
a
i
a_i
ai 和
b
i
b_i
bi 都还没被选上时,将
a
i
a_i
ai 和
b
i
b_i
bi 都选上
(2)
S
→
i
1
→
A
→
B
→
j
2
→
T
S\rightarrow i_1\rightarrow A\rightarrow B\rightarrow j_2\rightarrow T
S→i1→A→B→j2→T
在序列上即当下标不对应的对数
c
n
t
<
K
−
L
cnt<K-L
cnt<K−L 时将
a
i
a_i
ai 和
b
j
b_j
bj 都选上,然后
c
n
t
cnt
cnt 加一
不过这里我们为了保证
c
n
t
cnt
cnt 一定会加一,钦定这时
i
≠
j
i\ne j
i̸=j 且
a
j
a_j
aj 和
b
i
b_i
bi 都没有被选过
(3)
S
→
i
1
→
i
2
→
B
→
A
→
j
1
→
j
2
→
T
S\rightarrow i_1\rightarrow i_2\rightarrow B\rightarrow A\rightarrow j_1\rightarrow j_2\rightarrow T
S→i1→i2→B→A→j1→j2→T
这在序列上还是对应将
a
i
a_i
ai 和
b
j
b_j
bj 都选上,但是没有
c
n
t
<
K
−
L
cnt<K-L
cnt<K−L 的限制,且
c
n
t
cnt
cnt 这时要减一
且在这之前
a
j
a_j
aj 和
b
i
b_i
bi 都已经被选上
(4)
S
→
i
1
→
A
→
j
1
→
j
2
→
T
S\rightarrow i_1\rightarrow A\rightarrow j_1\rightarrow j_2\rightarrow T
S→i1→A→j1→j2→T
即
c
n
t
cnt
cnt 不变,把
a
i
a_i
ai 和
b
j
b_j
bj 都选上,需要保证
a
j
a_j
aj 已经被选上但
b
i
b_i
bi 还未被选上
(5)
S
→
i
1
→
i
2
→
B
→
j
2
→
T
S\rightarrow i_1\rightarrow i_2\rightarrow B\rightarrow j_2\rightarrow T
S→i1→i2→B→j2→T
即
c
n
t
cnt
cnt 不变,把
a
i
a_i
ai 和
b
j
b_j
bj 都选上,需要保证
a
j
a_j
aj 还未被选上但
b
i
b_i
bi 已经被选上
(6)
S
→
i
1
→
A
→
j
1
→
j
2
→
B
→
k
2
→
T
S\rightarrow i_1\rightarrow A\rightarrow j_1\rightarrow j_2\rightarrow B\rightarrow k_2\rightarrow T
S→i1→A→j1→j2→B→k2→T
这种情况一定与之前讨论的情况中的一种等价,故这种情况没有实际意义
我们有了一个贪心策略,即重复
K
K
K 次,下面四种情况中选贡献最大的
(1)选出
a
i
a_i
ai 和
b
i
b_i
bi
(2)在
a
j
a_j
aj 和
b
i
b_i
bi 未被选出且
c
n
t
<
K
−
L
cnt<K-L
cnt<K−L 的情况下,选出
a
i
a_i
ai 和
b
j
b_j
bj ,并让
c
n
t
cnt
cnt 加一
(3)在
a
j
a_j
aj 和
b
i
b_i
bi 都被选出的情况下,选出
a
i
a_i
ai 和
b
j
b_j
bj ,并让
c
n
t
cnt
cnt 减一
(4)在
a
j
a_j
aj 和
b
i
b_i
bi 恰有其一被选出的情况下,选出
a
i
a_i
ai 和
b
j
b_j
bj ,这时
c
n
t
cnt
cnt 不变
用几个优先队列即可实现
O
(
(
∑
n
)
log
n
)
O((\sum n)\log n)
O((∑n)logn)
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
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);if(bo) res =~res +1;}typedeflonglong ll;constint N =2e5+5;int n, k, l, a[N], b[N];bool visa[N], visb[N];struct node
{int id, val;friendinlinebooloperator<(node a, node b){return a.val < b.val;}} INF;
std::priority_queue<node> no_ab, no_a, no_b, is_a, is_b;voidwork(){
ll ans =0;int cnt =0;read(n);read(k);read(l);for(int i =1; i <= n; i++)read(a[i]);for(int i =1; i <= n; i++)read(b[i]);memset(visa,0,sizeof(visa));memset(visb,0,sizeof(visb));while(!no_ab.empty()) no_ab.pop();while(!no_a.empty()) no_a.pop();while(!no_b.empty()) no_b.pop();while(!is_a.empty()) is_a.pop();while(!is_b.empty()) is_b.pop();for(int i =1; i <= n; i++)
no_ab.push((node){i, a[i]+ b[i]}),
no_a.push((node){i, a[i]}), no_b.push((node){i, b[i]});for(int i =1; i <= k; i++){while(!no_ab.empty()&&(visa[no_ab.top().id]|| visb[no_ab.top().id]))
no_ab.pop();while(!no_a.empty()&&(visa[no_a.top().id]|| visb[no_a.top().id]))
no_a.pop();while(!no_b.empty()&&(visa[no_b.top().id]|| visb[no_b.top().id]))
no_b.pop();while(!is_a.empty()&&(visa[is_a.top().id]||!visb[is_a.top().id]))
is_a.pop();while(!is_b.empty()&&(!visa[is_b.top().id]|| visb[is_b.top().id]))
is_b.pop();
node s1 = no_ab.empty()? INF : no_ab.top(), s2, s3, s4, s5;if(no_a.empty()|| no_b.empty()) s2 = s3 = INF;else s2 = no_a.top(), s3 = no_b.top();if(is_a.empty()|| is_b.empty()) s4 = s5 = INF;else s4 = is_a.top(), s5 = is_b.top();if((cnt == k - l || s1.val >= s2.val + s3.val)&& s1.val >= s4.val + s5.val
&& s1.val >= s4.val + s3.val && s1.val >= s2.val + s5.val){
ans += s1.val; visa[s1.id]= visb[s1.id]=1;continue;}if(s2.val + s3.val > s4.val + s5.val && s2.val > s4.val && s3.val > s5.val
&& cnt < k - l){
ans += s2.val + s3.val; visa[s2.id]= visb[s3.id]=1;
is_a.push((node){s3.id, a[s3.id]});
is_b.push((node){s2.id, b[s2.id]});
cnt++;continue;}if(s5.val > s3.val && s4.val > s2.val){
ans += s4.val + s5.val; visa[s4.id]= visb[s5.id]=1;
cnt--;continue;}if(s4.val + s3.val > s2.val + s5.val)
ans += s4.val + s3.val, visa[s4.id]= visb[s3.id]=1,
is_a.push((node){s3.id, a[s3.id]});else ans += s2.val + s5.val, visa[s2.id]= visb[s5.id]=1,
is_b.push((node){s2.id, b[s2.id]});}printf("%lld\n", ans);}intmain(){
INF =(node){0,-0x3f3f3f3f};int T;read(T);while(T--)work();return0;}
D2T1
Solution:K-D Tree + Dijkstra 单源最短路
看到二维区间以及空间限制只有 128M ,不难想到 K-D Tree 优化连边
注意到 Dijikstra 的算法流程中重复的一步是从优先队列中取出一个点
u
u
u
然后更新与
u
u
u 相连的点的最短路
而回到本题,一个二维区间可以表示成 K-D Tree 上的
O
(
n
)
O(\sqrt n)
O(n) 个子树
于是取出点
u
u
u 后转移到这
O
(
n
)
O(\sqrt n)
O(n) 个子树即可,具体地,可以在这些子树上打取
min
\min
min 的标记
O
(
n
log
n
+
m
n
)
O(n\log n+m\sqrt n)
O(nlogn+mn)
一个剪枝:在 K-D Tree 上查找
O
(
n
)
O(\sqrt n)
O(n) 个子树时如果无法更新子树里任何点的
d
i
s
dis
dis 就 return 掉
建议把 K-D Tree 写成线段树的形式,使得点
u
u
u 转移到的一定是一些子树而没有孤立点
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){char c;while((c =getchar())<'0'|| c >'9');
res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);}template<classT>inlinevoidwrite(const T &res){if(res >9)write(res /10);putchar(res %10+'0');}template<classT>inline T Min(const T &a,const T &b){return a < b ? a : b;}template<classT>inline T Max(const T &a,const T &b){return a > b ? a : b;}constint N =7e4+5, M = N <<1, W =15e4+5, INF =0x3f3f3f3f;int n, m, w, h, X[N], Y[N], op, lc[M], rc[M], minx[M], maxx[M], miny[M], maxy[M],
tot, pt[M], dis[M], Root, t[W], L[W], R[W], D[W], U[W], cnt, pts, tmp,
mark[M];bool vis[M];
std::vector<int> bel[N];struct point
{int x, y, id;} a[N];struct node
{int u, dis;friendinlinebooloperator<(const node &a,const node &b){return a.dis > b.dis;}};
std::priority_queue<node> pq;inlineboolcomp(const point &a,const point &b){if(!op)return a.x < b.x ||(a.x == b.x && a.y < b.y);elsereturn a.y < b.y ||(a.y == b.y && a.x < b.x);}intbuild(int l,int r,int dir){int p;if(l == r){
p = a[l].id;
minx[p]= maxx[p]= a[l].x;
miny[p]= maxy[p]= a[l].y;
mark[p]= INF;return p;}int mid = l + r >>1; op = dir;
std::sort(a + l, a + r +1, comp);
p =++pts;
lc[p]=build(l, mid, dir ^1);
rc[p]=build(mid +1, r, dir ^1);
minx[p]=Min(minx[lc[p]], minx[rc[p]]);
maxx[p]=Max(maxx[lc[p]], maxx[rc[p]]);
miny[p]=Min(miny[lc[p]], miny[rc[p]]);
maxy[p]=Max(maxy[lc[p]], maxy[rc[p]]);
mark[p]= INF;return p;}inlinevoidrelax(constint&u,constint&d){if(!vis[u]&& d < dis[u]) dis[u]= d, pq.push((node){u, d});}inlinevoidget_point(constint&l,constint&r,constint&d,constint&u,constint&p){if(r < minx[p]|| l > maxx[p]|| u < miny[p]|| d > maxy[p]|| mark[p]<= tmp)return;if(l <= minx[p]&& maxx[p]<= r && d <= miny[p]&& maxy[p]<= u)return mark[p]=Min(mark[p], tmp),relax(p, tmp);
mark[lc[p]]=Min(mark[lc[p]], mark[p]);
mark[rc[p]]=Min(mark[rc[p]], mark[p]);get_point(l, r, d, u, lc[p]);get_point(l, r, d, u, rc[p]);}intmain(){int x;read(n);read(m);read(w);read(h); pts = n;for(int i =1; i <= n; i++)read(a[i].x),read(a[i].y), a[i].id = i,
X[i]= a[i].x, Y[i]= a[i].y;for(int i =1; i <= m; i++){read(x);read(t[i]);read(L[i]);read(R[i]);read(D[i]);read(U[i]);
bel[x].push_back(i);}memset(dis, INF,sizeof(dis));
dis[1]=0; pq.push((node){1,0});
Root =build(1, n,1);while(!pq.empty()){
node x = pq.top(); pq.pop();int u = x.u;if(vis[u])continue; vis[u]=1;if(lc[u])relax(lc[u], dis[u]), mark[lc[u]]=Min(mark[lc[u]], mark[u]);if(rc[u])relax(rc[u], dis[u]), mark[rc[u]]=Min(mark[rc[u]], mark[u]);if(u > n)continue;for(int i =0; i < bel[u].size(); i++){int it = bel[u][i];
tmp = dis[u]+ t[it];get_point(L[it], R[it], D[it], U[it], Root);}}for(int i =2; i <= n; i++)write(dis[i]),putchar('\n');return0;}
D2T2
Solution:组合数学 + DP
这是一个打表乱搞可过的题,不过这里讲正解
不难发现每次操作是把前
A
A
A 张牌和后
n
−
A
n-A
n−A 张牌等概率随机地归并
即前
A
A
A 张牌之间相对顺序不改变,后
n
−
A
n-A
n−A 张也不变
考虑这个操作的逆过程
也就是这
n
n
n 张牌中随机选
A
A
A 张牌放到前面,剩下
n
−
A
n-A
n−A 张牌放到后面
连续
m
m
m 次操作就相当于
b
a
s
e
base
base 为
2
2
2 的基数排序
于是问题转化为有
n
n
n 个
m
m
m 位二进制数,对于从高到低(以下省略)第
i
i
i 位,随机
n
n
n 个数中的
A
i
A_i
Ai 个该位为
0
0
0 ,其他数的该位为
1
1
1
询问转化为求第
c
c
c 个二进制数的排名的
t
y
p
e
type
type 次幂的期望值
(排名为数值第一关键字,下标第二关键字)
如果
t
y
p
e
=
1
type=1
type=1
那么可以预处理出对于任意
1
≤
i
<
j
≤
n
1\le i<j\le n
1≤i<j≤n ,第
i
i
i 和第
j
j
j 个数相同的概率(显然对于所有的
i
,
j
i,j
i,j 都一样),记为
p
p
p
那么第
i
i
i 个数严格小于第
j
j
j 个数的概率就是
1
−
p
2
\frac{1-p}2
21−p
第
i
i
i 个数小于等于第
j
j
j 个数的概率为
1
+
p
2
\frac{1+p}2
21+p
询问结果就是
(
c
−
1
)
×
1
+
p
2
+
(
n
−
c
)
×
1
−
p
2
+
1
(c-1)\times\frac{1+p}2+(n-c)\times\frac{1-p}2+1
(c−1)×21+p+(n−c)×21−p+1
否则
t
y
p
e
=
2
type=2
type=2
注意到
E
(
r
a
n
k
2
)
=
2
E
(
(
r
a
n
k
−
1
2
)
)
+
3
E
(
r
a
n
k
)
−
2
E(rank^2)=2E(\binom{rank-1}2)+3E(rank)-2
E(rank2)=2E((2rank−1))+3E(rank)−2
E
(
r
a
n
k
)
E(rank)
E(rank) 前面已经求出来了
E
(
(
r
a
n
k
−
1
2
)
)
E(\binom{rank-1}2)
E((2rank−1)) 的组合意义是有多少对满足
i
<
j
,
i
≠
c
,
j
≠
c
i<j,i\ne c,j\ne c
i<j,i̸=c,j̸=c 的
(
i
,
j
)
(i,j)
(i,j) 使得
c
c
c 的排名比
i
i
i 和
j
j
j 都大
同样地可以分
a
c
>
a
i
,
a
c
>
a
j
a_c>a_i,a_c>a_j
ac>ai,ac>aj 、
a
c
≥
a
i
,
a
c
>
a
j
a_c\ge a_i,a_c>a_j
ac≥ai,ac>aj 和
a
c
≥
a
i
,
a
c
≥
a
j
a_c\ge a_i,a_c\ge a_j
ac≥ai,ac≥aj 三种情况讨论,显然这三种情况出现的概率与
c
,
i
,
j
c,i,j
c,i,j 无关
记上面三种情况出现的概率分别为
p
1
,
p
2
,
p
3
p_1,p_2,p_3
p1,p2,p3
a
i
a_i
ai 为第
i
i
i 个二进制数
可以用简单容斥求出
a
i
,
a
j
,
a
c
a_i,a_j,a_c
ai,aj,ac 三者互不相同的概率,将其乘上
1
3
\frac 13
31 好像就是
p
1
p_1
p1
但上面忽略了
a
c
>
a
i
=
a
j
a_c>a_i=a_j
ac>ai=aj 的概率,这个概率可以数位 DP 求
f
[
i
]
[
0
/
1
]
f[i][0/1]
f[i][0/1] 表示
a
i
a_i
ai 和
a
j
a_j
aj 的前
i
i
i 位相等,
a
i
a_i
ai 和
a
j
a_j
aj 的前
i
i
i 位等于 / 小于
a
c
a_c
ac 的前
i
i
i 位的概率
f
[
i
]
f[i]
f[i] 直接转移到
f
[
i
+
1
]
f[i+1]
f[i+1]
p
1
p_1
p1 即为
a
i
,
a
j
,
a
c
a_i,a_j,a_c
ai,aj,ac 三者互不相同的概率乘
1
3
\frac 13
31 再加上
f
[
m
]
[
1
]
f[m][1]
f[m][1]
p
2
p_2
p2 为
p
1
p_1
p1 加上
a
c
=
a
i
>
a
j
a_c=a_i>a_j
ac=ai>aj 的概率(可以利用
f
[
m
]
[
1
]
f[m][1]
f[m][1] 计算出)
p
3
p_3
p3 为
p
2
p_2
p2 加上
a
c
=
a
j
>
a
i
a_c=a_j>a_i
ac=aj>ai 的概率再加上
a
c
,
a
i
,
a
j
a_c,a_i,a_j
ac,ai,aj 两两相同的概率
E
(
(
r
a
n
k
−
1
2
)
)
=
(
c
−
1
2
)
×
p
3
+
(
c
−
1
)
×
(
n
−
c
)
×
p
2
+
(
n
−
c
2
)
×
p
1
E(\binom{rank-1}2)=\binom{c-1}2\times p_3+(c-1)\times(n-c)\times p_2+\binom{n-c}2\times p_1
E((2rank−1))=(2c−1)×p3+(c−1)×(n−c)×p2+(2n−c)×p1
O
(
m
)
O(m)
O(m)
Code
#include<bits/stdc++.h>template<classT>inlinevoidread(T &res){
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);if(bo) res =~res +1;}constint N =5e5+5, ZZQ =998244353, I2 =499122177, I3 =332748118;int n, m, typ, a[N], q, same2 =1, same3 =1, invn, invn2, invn3, less, leq,
dif_same, less_all, less_leq, leq_all, f[N][2];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(){int x;read(n);read(m);read(typ);for(int i =1; i <= m; i++)read(a[i]);
invn =qpow(n, ZZQ -2);
invn2 =qpow(1ll* n *(n -1)% ZZQ, ZZQ -2);
invn3 =qpow(1ll* n *(n -1)% ZZQ *(n -2)% ZZQ, ZZQ -2);for(int i =1; i <= m; i++){
same2 =(1ll*(n - a[i]-1)*(n - a[i])+1ll*(a[i]-1)* a[i])% ZZQ * invn2 % ZZQ * same2 % ZZQ;
same3 =(1ll*(n - a[i]-2)*(n - a[i]-1)% ZZQ *(n - a[i])+1ll*(a[i]-2)*(a[i]-1)% ZZQ * a[i])% ZZQ * invn3 % ZZQ * same3 % ZZQ;}
less =1ll* I2 *(1- same2 + ZZQ)% ZZQ;
leq =1ll* I2 *(1+ same2)% ZZQ;
dif_same =(same2 - same3 + ZZQ)% ZZQ;
less_all =1ll* I3 *(1-3ll* dif_same % ZZQ - same3 + ZZQ + ZZQ)% ZZQ;
f[0][0]=1;for(int i =0; i < m; i++){int x = a[i +1];
f[i +1][1]=((1ll*(n - x -1)*(n - x)+1ll*(x -1)* x)% ZZQ * invn2 % ZZQ * f[i][1]+ f[i +1][1])% ZZQ;
f[i +1][0]=((1ll*(n - x -2)*(n - x -1)% ZZQ *(n - x)+1ll*(x -2)*(x -1)% ZZQ * x)% ZZQ * invn3 % ZZQ * f[i][0]+ f[i +1][0])% ZZQ;
f[i +1][1]=(1ll*(n - x)* invn % ZZQ *(x -1)% ZZQ * x % ZZQ
* invn3 % ZZQ * n % ZZQ * f[i][0]+ f[i +1][1])% ZZQ;}
less_all =(f[m][1]+ less_all)% ZZQ;
less_leq =(1ll* dif_same - f[m][1]+ less_all + ZZQ)% ZZQ;
leq_all =(2ll*(dif_same - f[m][1]+ ZZQ)+ less_all + same3)% ZZQ;read(q);while(q--){read(x);int ans =(1ll*(x -1)* leq +1ll*(n - x)* less +1)% ZZQ;if(typ ==1){printf("%d\n", ans);continue;}
ans =(3ll* ans + ZZQ -2)% ZZQ;
ans =(1ll*(x -1)*(x -2)% ZZQ * leq_all
+2ll*(x -1)*(n - x)% ZZQ * less_leq
+1ll*(n - x)*(n - x -1)% ZZQ * less_all + ans)% ZZQ;printf("%d\n", ans);}return0;}
D2T3
Solution:整体二分 + 随机化
先说特殊性质 B 的做法(一棵树,除
0
0
0 外每个点都向编号更小的点连了恰好一条边)
考虑整体二分
即过程
s
o
l
v
e
(
S
,
l
,
r
)
solve(S,l,r)
solve(S,l,r) 表示为点集
S
S
S 确定父亲,已知它们的父亲编号在
[
l
,
r
]
[l,r]
[l,r] 内
如果
l
=
r
l=r
l=r 直接返回,点集
S
S
S 内所有点的父亲全部确定
否则令
m
i
d
=
⌊
l
+
r
2
⌋
mid=\lfloor\frac{l+r}2\rfloor
mid=⌊2l+r⌋
把编号从
l
l
l 到
m
i
d
mid
mid 的点全部执行 modify 操作
对于一个点
u
∈
S
u\in S
u∈S
如果
u
≤
m
i
d
u\le mid
u≤mid 或者
u
u
u 点在这
m
i
d
−
l
+
1
mid-l+1
mid−l+1 次 modify 操作之后由白变黑
那么
u
u
u 的父亲一定在
[
l
,
m
i
d
]
[l,mid]
[l,mid] 内,否则一定在
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r] 内
往下递归即可, modify 和 query 的次数都是
O
(
n
log
n
)
O(n\log n)
O(nlogn)
可以计算出:期望意义下随机的一个排列,对于任意一个点
u
u
u (需保证以
u
u
u 为端点的边还没全部被找出来)
点
u
u
u 向编号更小的点连的边数为奇数的概率至少
1
3
\frac 13
31
故我们算法的具体过程为首先有一个
0
0
0 到
n
−
1
n-1
n−1 的排列
p
p
p 满足
p
i
=
i
p_i=i
pi=i (否则特殊性质为 B 的任务过不去)
重复执行下面的操作
(1)如果
p
i
=
u
p_i=u
pi=u 则把点
u
u
u 的编号定为
i
i
i
(2)在重新编号之后的图上跑整体二分,然后如果已经找出所有
m
m
m 条边就返回
(3)使用 check 操作去掉满足
u
u
u 的所有出边都已被找出的点
u
u
u
(4)把
p
p
p 内的元素改为所有还未被删掉的所有点并随机打乱
注意上面的每一次循环相当于是把找到的所有边都删掉了
故我们需要对于这一次循环找到的所有边,在后续过程中去掉这些边的影响
来分析一波复杂度
每次当还剩下
M
M
M 条边时,由于我们用 check 操作去掉了孤立点,所以点数不超过
O
(
M
)
O(M)
O(M)
我们会用
O
(
M
log
M
)
O(M\log M)
O(MlogM) 的复杂度去进行整体二分
我们有
T
(
M
)
=
T
(
M
−
X
M
)
+
O
(
M
log
M
)
T(M)=T(M-X_M)+O(M\log M)
T(M)=T(M−XM)+O(MlogM) ,其中
X
M
X_M
XM 的期望至少为
M
3
\frac M3
3M
类似于求 K-th ,我们有
T
(
M
)
=
O
(
M
log
M
)
T(M)=O(M\log M)
T(M)=O(MlogM)
Code
#include<bits/stdc++.h>#include"explore.h"#define p2 p << 1#define p3 p << 1 | 1constint N =2e5+5, M = N <<2;int n, m, ecnt, nxt[M], adj[N], st[M], go[M], tmp[N], to[N], perm[N];bool vis[N];voidadd_edge(int u,int v){report(u, v);
nxt[++ecnt]= adj[u]; adj[u]= ecnt; st[ecnt]= u; go[ecnt]= v;
nxt[ecnt + m]= adj[v]; adj[v]= ecnt + m; st[ecnt + m]= v; go[ecnt + m]= u;}booleg(int u,int l,int r){bool res =query(u);for(int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])if(!vis[v]&& to[v]>= l && to[v]<= r) res ^=1;return res;}voiddfs(std::vector<int> que,int l,int r){int n = que.size();if(l == r){for(int i =0; i < n; i++)if(l < que[i])add_edge(perm[l], perm[que[i]]);return;}int mid = l + r >>1;
std::vector<int> le, ri;for(int i = l; i <= mid; i++)modify(perm[i]);for(int i =0; i < n; i++)if(que[i]<= mid ||eg(perm[que[i]], l, mid)) le.push_back(que[i]);else ri.push_back(que[i]);for(int i = l; i <= mid; i++)modify(perm[i]);dfs(le, l, mid);dfs(ri, mid +1, r);}voidexplore(int N,int M){
n = N; m = M;if(n <=500){for(int i =0; i < n -1; i++){modify(i);for(int j = i +1; j < n; j++)if(vis[j]^query(j))report(i, j), vis[j]^=1;}return;}for(int i =0; i < n; i++) perm[i]= i;while(1){for(int i =0; i < n; i++) to[perm[i]]= i;int lst = ecnt;
std::vector<int> fake;for(int i =0; i < n; i++) fake.push_back(i);dfs(fake,0, n -1);if(ecnt == m)return;for(int i = lst +1; i <= ecnt; i++){if(check(st[i])) vis[st[i]]=1;if(check(go[i])) vis[go[i]]=1;}int tn =0;for(int i =0; i < n; i++)if(!vis[perm[i]]) tmp[tn++]= perm[i];
n = tn;for(int i =0; i < n; i++) perm[i]= tmp[i];
std::random_shuffle(perm, perm + n);}}