2019杭电多校第三场

1002. Blow up the city

upsloved

有一个DAG,出度为\(0\)的点是指挥中心,\(q\)次询问,每次给出两个点,这两个点存有关键物资,你可以炸掉一个点和它的邻接边,使得这两个点中任意一个点的物资不能到达指挥中心(有一个点不能到达任意一个指挥中心即可),求方案数,询问独立 并不会支配树,比赛的时候想到了这个东西的概念,但是不会实现,赛后学习一波

把图反过了,建一个额外的起点指向指挥中心,求出支配树,每次询问u,v就只要计算u,v以及它们的LCA的深度就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <bits/stdc++.h>
using LL = long long;
using namespace std;

const int N = 200010;
struct Node{int to,next;};
int T, n, m, q, u, v, dfn[N], clo, rev[N], f[N], semi[N], idom[N], deg[N], dep[N], fa[N][18];

struct Graph{
Node E[N << 1]; int head[N], tot;
inline void clear(){
tot = 0;
for(int i = 0; i <= n; ++i) head[i] = -1;
}
inline void link(int u, int v){
E[tot] = (Node){v, head[u]}; head[u] = tot++;
}
}pre, nxt, dom;

struct uset{
int fa[N], Mi[N];
inline void init(){
for(int i = 1; i <= n; ++i)
fa[i] = Mi[i] = semi[i] = i;
}
inline int find(int x){
if(fa[x] == x) return x;
int fx = fa[x], y = find(fa[x]);
if(dfn[semi[Mi[fx]]] < dfn[semi[Mi[x]]]) Mi[x] = Mi[fx];
return fa[x] = y;
}
}uset;

inline void tarjan(int x) {
dfn[x] = ++clo; rev[clo] = x;
for(int e = nxt.head[x]; ~e; e = nxt.E[e].next){
if(!dfn[nxt.E[e].to])
f[nxt.E[e].to] = x, tarjan(nxt.E[e].to);
}
}

inline void dfs(int x, int pre, int depth) {
dep[x] = depth;
fa[x][0] = pre;
for(int i = 1; i < 18; ++i)
fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(int e = dom.head[x]; ~e; e = dom.E[e].next)
dfs(dom.E[e].to, x, depth + 1);
}

inline int LCA(int u, int v) {
if(dep[u] < dep[v]) swap(u, v);
for(int i = 17; ~i; --i) {
if(dep[fa[u][i]] >= dep[v])
u = fa[u][i];
}
if(u == v) return u;
for(int i = 17; ~i; --i) {
if(fa[u][i] != fa[v][i])
u = fa[u][i], v = fa[v][i];
}
return fa[u][0];
}

inline void calc(){
for(int i = n; i >= 2; --i){
int y = rev[i], tmp = n;
for(int e = pre.head[y]; ~e; e = pre.E[e].next){
int x = pre.E[e].to; if(!dfn[x]) continue;
if(dfn[x] < dfn[y]) tmp = min(tmp, dfn[x]);
else uset.find(x), tmp = min(tmp, dfn[semi[uset.Mi[x]]]);
}
semi[y] = rev[tmp]; uset.fa[y] = f[y];
dom.link(semi[y], y);
y = rev[i - 1];
for(int e = dom.head[y]; ~e; e = dom.E[e].next){
int x = dom.E[e].to; uset.find(x);
if(semi[uset.Mi[x]] == y) idom[x] = y;
else idom[x] = uset.Mi[x];
}
}
for(int i = 2; i <= n; ++i){
int x = rev[i];
if(idom[x] != semi[x])
idom[x] = idom[idom[x]];
}
dom.clear();
for(int i = 1; i < n; ++i)
dom.link(idom[i], i);
dfs(n, 0, 0);
}

int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
clo = 0; n++; dep[0] = -1;
for(int i = 1; i <= n; ++i)
deg[i] = dfn[i] = rev[i] = semi[i] = idom[i] = f[i] = 0;
pre.clear(); nxt.clear(); dom.clear(); uset.init();
for(int i = 1; i <= m; ++i){
scanf("%d%d", &u, &v);
pre.link(u, v);
nxt.link(v, u);
deg[u]++;
}
for(int i = 1; i < n; ++i) {
if(deg[i] == 0) {
nxt.link(n, i);
pre.link(i, n);
}
}
tarjan(n);
calc();
scanf("%d", &q);
while(q--) {
scanf("%d%d", &u, &v);
printf("%d\n", dep[u] + dep[v] - dep[LCA(u, v)]);
}
}
return 0;
}

1004. Distribution of books

upsloved

有一个长为\(n\)的数列\(A\), 你需要将找\(k\)个飞空不相交区间,这些区间并起来是\(A\)的一个前缀区间,使得这\(k\)个区间每个区间的和的最大值最小

二分答案,线段树优化dp套路,代码不放了

1006. Fansblog

solved at 00:54

队友做的,素数分布,威尔逊定理,int128

1007. Find the answer

solved at 01:24(+3)

离散化之后线段树维护区间和以及区间数的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

struct node {
int val, id;
bool operator<(const node &rhs) const {
return val < rhs.val;
}
}a[N];

int rnk[N], T, n, ans[N], m, sum, mn, b[N];

long long tree[N << 2];
int cnt[N << 2];

void pushup(int rt) {
tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
cnt[rt] = cnt[rt << 1] + cnt[rt << 1 | 1];
}

void build(int rt, int l, int r) {
tree[rt] = 0; cnt[rt] = 0;
if(l == r) return;
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
}

void update(int rt, int l, int r, int pos, int val) {
if(l == r) {tree[rt] += val, cnt[rt]++; return;}
int mid = l + r >> 1;
if(pos <= mid) update(rt << 1, l, mid, pos, val);
else update(rt << 1 | 1, mid + 1, r, pos, val);
pushup(rt);
}

int query(int rt, int l, int r, long long val) {
if(l == r) return tree[rt] <= val ? cnt[rt] : 0;
int mid = l + r >> 1;
if(tree[rt << 1] <= val) return cnt[rt << 1] + query(rt << 1 | 1, mid + 1, r, val - tree[rt << 1]);
else return query(rt << 1, l, mid, val);
}

int main() {
read(T);
while(T--) {
sum = ans[0] = 0;
read(n); read(m);
for(int i = 1; i <= n; ++i) {
read(a[i].val);
b[i] = a[i].val;
a[i].id = i;
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i)
rnk[a[i].id] = i;
build(1, 1, n);
for(int i = 1; i <= n; ++i) {
ans[i] = i - 1 - query(1, 1, n, m - b[i]);
update(1, 1, n, rnk[i], b[i]);
}
for(int i = 1; i <= n; ++i)
printf("%d ", ans[i]);
puts("");
}
return 0;
}


1009. K subsequence

solved at 04:42(+14)

有一个长为\(n\)的数列,你需要找出\(k\)个不相交的非降子序列使得选出的数和最大\(1<=n<=2000, 1<=k<=10\)

最小费用最大流经典题,拆点建图,每个点\(i\)拆成\(i_1\)\(i_2\)\(i_1\)\(i_2\)建容量为\(1\),费用为\(-a_i\)的边,\(s\)\(i_1\)建容量为\(1\),费用为\(0\)的边,\(i_2\)\(t\)建容量为\(1\),费用为\(0\)的边,如果\(a[i]<=a[j]\)\(i<j\)\(i_2\)\(j_1\)建容量为\(1\),费用为\(0\)的边,最后\(ss\)\(s\)建容量为\(k\),费用为\(0\)的边,\(ss\)\(t\)\(MCMF\),最小费用取相反数就是答案

1011. Squrirrel

树型dp,待补