线段树分治 + 可撤销并查集动态维护直径,dp + 分块,贪心,dp + 树状数组
# A. 越野赛车问题
考虑对值域进行线段树分治,把在分治区间 的边连上,动态维护树的直径即可。
关于如何维护直径,就是把原来直径的两个端点和新加进来的边的两个端点组合一下,距离最远的两个点就是新的直径。
再开个可撤销并查集维护历史直径信息,就可以动态更新了。
直接贺板子,好耶。
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 1e5 + 10;
int n, m;
struct node{
int u, v;
}e[N];
vector <int> g[N], t[N << 2];
int fa[N], siz[N], son[N], dep[N];
inline void dfs1(int x, int f){
fa[x] = f, siz[x] = 1, dep[x] = dep[f] + 1;
for(auto y : g[x]){
if(y == f) continue;
dfs1(y, x), siz[x] += siz[y];
if(siz[y] > siz[son[x]]) son[x] = y;
}
}
int top[N];
inline void dfs2(int x, int topfa){
top[x] = topfa;
if(son[x]) dfs2(son[x], topfa);
for(auto y : g[x])
if(y != fa[x] && y != son[x]) dfs2(y, y);
}
inline int lca(int x, int y){
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[x];
}
return dep[x] < dep[y] ? x : y;
}
inline int dist(int x, int y){
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
}
struct Path{
int p1, p2, dis;
Path() {}
Path(int _p1, int _p2) {p1 = _p1, p2 = _p2, dis = dist(p1, p2);}
inline bool operator < (const Path &b) const {
return dis < b.dis;
}
friend inline Path merge(Path x, Path y){
Path p = max(x, y);
p = max(p, max(Path(x.p1, y.p1), Path(x.p1, y.p2)));
p = max(p, max(Path(x.p2, y.p1), Path(x.p2, y.p2)));
return p;
}
};
struct Dsu{
Path p;
int fa, siz;
}dsu[N];
struct Stack{
int x, y;
Path px, py;
Stack() {}
Stack(int _x, int _y, Path _px, Path _py){
x = _x, y = _y, px = _px, py = _py;
}
}stk[N];
int stop;
inline int find(int x){
return dsu[x].fa == x ? x : find(dsu[x].fa);
}
inline int Union(int x, int y){
x = find(x), y = find(y);
if(dsu[x].siz < dsu[y].siz) swap(x, y);
stk[++stop] = Stack(x, y, dsu[x].p, dsu[y].p);
dsu[y].fa = x, dsu[x].siz += dsu[y].siz;
dsu[x].p = merge(dsu[x].p, dsu[y].p);
return dsu[x].p.dis;
}
inline void Delete(){
Stack s = stk[stop--];
int x = s.x, y = s.y;
dsu[y].fa = y, dsu[x].siz -= dsu[y].siz;
dsu[x].p = s.px, dsu[y].p = s.py;
}
int ans[N];
inline void update(int L, int R, int x, int l, int r, int rt){
if(L > R || L > r || R < l) return;
if(L <= l && r <= R) return t[rt].pb(x), void();
update(L, R, x, l, mid, ls), update(L, R, x, mid + 1, r, rs);
}
inline void solve(int len, int l, int r, int rt){
int now = stop;
for(auto x : t[rt]) len = max(len, Union(e[x].u, e[x].v));
if(l == r) ans[l] = ~ans[l] ? ans[l] : len;
else solve(len, l, mid, ls), solve(len, mid + 1, r, rs);
while(stop != now) Delete();
}
signed main(){
n = read(), m = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read(), l = read(), r = read();
e[i] = (node){u, v}, update(l, r, i, 1, n, 1);
g[u].pb(v), g[v].pb(u);
}
dfs1(1, 0), dfs2(1, 1);
memset(ans, -1, sizeof(ans));
for(int i = 1; i <= n; ++i) dsu[i] = (Dsu){Path(i, i), i, 1};
solve(0, 1, n, 1);
for(int i = 1; i <= m; ++i) write(ans[read()]), puts("");
return 0;
}
# B. Native - DP II
CF101E Candies and Stones
非常显然的 ,设 表示走到 的最大收益,那么转移就是:
但是这样空间复杂度是 的,不知道比 高了多少。
考虑如何优化空间。
我们把 的矩阵按行分块,即每处理 行存一下答案,记录 值,以及路径。
最后再倒着枚举每一块跑一遍 ,找路径。
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
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 2e4 + 10;
const int B = 150;
int n, m, mod, t, l = 1;
int x[N], y[N], f[B][N], g[B][N], df[B][N], dg[2][N];
int dir[2][N], dp[2][N];
stack <int> ans;
inline int add(int x) {return x >= mod ? x % mod : x;}
signed main(){
n = read() - 1, m = read() - 1, mod = read(), t = max(sqrt(n), 1.0);
for(int i = 0; i <= n; ++i) x[i] = read();
for(int i = 0; i <= m; ++i) y[i] = read();
for(int i = 0; i <= n; ++i, l ^= 1){
memset(dir[l], 0, sizeof(dir[l]));
memset(dp[l], 0, sizeof(dp[l]));
for(int j = 0; j <= m; ++j){
dp[l][j] = dp[l ^ 1][j] + add(x[i] + y[j]), dir[l][j] = 1;
if(j && dp[l][j] < dp[l][j - 1] + add(x[i] + y[j]))
dp[l][j] = dp[l][j - 1] + add(x[i] + y[j]), dir[l][j] = 0;
}
if(i % t == 0){
memcpy(f[i / t], dp[l], sizeof(dp[l]));
memcpy(g[i / t], dir[l], sizeof(dir[l]));
}
}
int tx = n, ty = m;
for(int i = n / t; i >= 0; --i){
memcpy(df[0], f[i], sizeof(f[i]));
memcpy(dg[0], g[i], sizeof(g[i]));
for(int j = i * t + 1; j <= min(n, (i + 1) * t); ++j){
for(int k = 0; k <= m; ++k){
df[j - i * t][k] = df[j - i * t - 1][k] + add(x[j] + y[k]), dg[j - i * t][k] = 1;
if(k && df[j - i * t][k] < df[j - i * t][k - 1] + add(x[j] + y[k]))
df[j - i * t][k] = df[j - i * t][k - 1] + add(x[j] + y[k]), dg[j - i * t][k] = 0;
}
}
while(tx >= (i * t + 1)){
ans.push(dg[tx - i * t][ty]);
if(!dg[tx - i * t][ty]) ty--;
else tx--;
}
}
while(ty) ans.push(0), ty--;
write(dp[l ^ 1][m]), puts("");
while(!ans.empty()) putchar(ans.top() ? 'C' : 'S'), ans.pop();
return 0;
}
# C. 椅子
AT2645 [ARC076D] Exhausted?
可以直接暴力最大流,能拿不少部分分。
然后考场上以为是线段树优化建图什么的,然而并不是。
本题大力贪心即可。
首先一个假贪心:
-
按照 从小到大排序,相同的按 从小到大排序。
-
对于每个人先在左边找,让他在 的最右边的空位置坐下,如果左边没有位置了,让他去右边找,在 的最左边的空位置坐下,如果都没有就加一张椅子。
-
这个贪心可以被以下数据 :
1
2
3
43 4
1 3
2 5
2 5
如何避免这一情况呢?
重新考虑这一过程,还是和上面一样的操作。
我们考虑到了第 个人,在 左边找到了空位置,让 坐下。
这时第 个人来了,但是我们在 左边找不到空位置了,我们考虑是否让 起来,去右边找位置,并让 坐在 坐的位置上(排过序了,所以 坐的位置 肯定能坐)。
如何维护这一“反悔”操作呢?
我们开一个小根堆,如果 在 找到了空位置,我们把 插入到小根堆中。
还是考虑如果 在左边找不到位置,此时堆顶为 ,我们分两种情况讨论:
- ,那就直接让 去右边找个空位置坐下就行。
- ,我们让 去坐 的位置,然后让 去 找空位置。
然后就 了。
代码中找空位置坐下跟上面略有不同,自己看代码理解一下吧 QwQ
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
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 2e5 + 10;
int n, m;
struct node{
int l, r;
inline bool operator < (const node &b) const{
return l != b.l ? l < b.l : r < b.r;
}
}p[N];
priority_queue <int, vector<int>, greater<int> > q;
int pos = 1, ans, tot;
int R[N];
signed main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i) p[i] = (node){read(), read()};
sort(p + 1, p + 1 + n);
for(int i = 1; i <= n; ++i){
q.push(p[i].r);
if(pos > p[i].l || pos > m) R[++tot] = q.top(), q.pop();
else pos++;
}
sort(R + 1, R + 1 + tot);
for(int i = tot, j = m; i >= 1; --i){
if(j >= pos && j >= R[i]) j--;
else ans++;
}
write(ans), puts("");
return 0;
}
# D. 方舟系统
本来是没这题的,但是 题大家之前都做过,所以就加了道题 /kk
的暴力 非常好想,设 表示处理到了第 个数,最少的代价。
转移方程:
能得 ,好耶。
那么如何优化呢?
我们转移过程中就取了个最小值,所以我们只要能够维护一手前缀最小值即可。
先把这个绝对值打开,即 或者 。
是定值,不用管它,所以就剩下了 和 。
所以我们开两个树状数组,分别维护 和 的最小值。
不要忘记这两个值可不是想用就能用的,绝对值的符号是固定的,所以一个是前缀最小值,一个是后缀最小值,自己判断一下吧。
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
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 1e5 + 10;
const int inf = 1e9 + 1e5;
int n;
int a[N], f[N];
unordered_map <int, int> c1, c2;
inline void add1(int x, int y){
for(; x; x -= x & (-x))
c1[x] = c1.find(x) != c1.end() ? min(c1[x], y) : y;
}
inline int qry1(int x){
int res = inf;
for(; x <= inf; x += x & (-x))
res = c1.find(x) != c1.end() ? min(res, c1[x]) : res;
return res;
}
inline void add2(int x, int y){
for(; x <= inf; x += x & (-x))
c2[x] = c2.find(x) != c2.end() ? min(c2[x], y) : y;
}
inline int qry2(int x){
int res = inf;
for(; x; x -= x & (-x))
res = c2.find(x) != c2.end() ? min(res, c2[x]) : res;
return res;
}
signed main(){
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i){
int r1 = qry1(i), r2 = qry2(i);
f[i] = min(r1 - i, r2 + i);
add1(a[i] + i, f[i - 1] + a[i] + i), add2(a[i] + i, f[i - 1] - a[i] - i);
}
write(f[n]), puts("");
return 0;
}