0%

题目链接:POJ - 2796

这是一个典型问题:需要你在短时间内求出所有的点左边/右边第一个比它大/小的位置。

应用:

方法一:利用已经得到的信息转移

以找到某个点左边/右边第一个比它小的位置为例(这里不包括那个比它小的点):

  • \(a(i) < a(l_i - 1)\) 时,\(l_i = l_{l_i - 1}\),直到满足 \(a(i) < a(l_i - 1)\)
  • \(a(i) > a(r_i + 1)\) 时,\(r_i = r_{r_i + 1}\),直到满足 \(a(i) > a(r_i + 1)\)

初始化\(a_0 = a_{n+1} = -\infty\),保证不会越界。

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
#include <cstdio>
#include <algorithm>
#include <climits>
using namespace std;
typedef long long LL;
const int MAX_N = 1000000 + 5;
int n, a[MAX_N], lNext[MAX_N], rNext[MAX_N];
LL pre[MAX_N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", a + i), pre[i] = pre[i - 1] + a[i];
a[0] = a[n + 1] = -INT_MAX;
for (int i = 1; i <= n; ++i) lNext[i] = rNext[i] = i;
for (int i = 1; i <= n; ++i) {
while (a[i] <= a[lNext[i] - 1]) lNext[i] = lNext[lNext[i] - 1];
}
for (int i = n; i >= 1; --i) {
while (a[i] <= a[rNext[i] + 1]) rNext[i] = rNext[rNext[i] + 1];
}
LL ans = -INT_MAX;
int ansL = -1, ansR = -1;
for (int i = 1; i <= n; ++i) {
if (ans < a[i] * (pre[rNext[i]] - pre[lNext[i] - 1])) {
ans = a[i] * (pre[rNext[i]] - pre[lNext[i] - 1]);
ansL = lNext[i]; ansR = rNext[i];
}
}
printf("%lld\n%d %d\n", ans, ansL, ansR);
return 0;
}

方法二:单调栈

维护一个单调栈,单调栈的顶部是左边/右边第一个比当前点小的点,如果需要不包含这个点,则左边+1右边-1即可。

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
#include <cstdio>
#include <stack>
#include <algorithm>
#include <climits>
using namespace std;
typedef long long LL;
const int MAX_N = 1000000 + 5;
int n, a[MAX_N], lNext[MAX_N], rNext[MAX_N];
LL pre[MAX_N];
stack <int> s;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", a + i), pre[i] = pre[i - 1] + a[i];
while (!s.empty()) s.pop();
for (int i = 1; i <= n; ++i) {
while (!s.empty() && a[s.top()] >= a[i]) s.pop();
lNext[i] = (s.empty() ? 1 : s.top() + 1);
s.push(i);
}
while (!s.empty()) s.pop();
for (int i = n; i >= 1; --i) {
while (!s.empty() && a[s.top()] >= a[i]) s.pop();
rNext[i] = (s.empty() ? n : s.top() - 1);
s.push(i);
}
LL ans = -INT_MAX;
int ansL = -1, ansR = -1;
for (int i = 1; i <= n; ++i) {
if (ans < a[i] * (pre[rNext[i]] - pre[lNext[i] - 1])) {
ans = a[i] * (pre[rNext[i]] - pre[lNext[i] - 1]);
ansL = lNext[i]; ansR = rNext[i];
}
}
printf("%lld\n%d %d\n", ans, ansL, ansR);
return 0;
}

分层最短路,这题需要处理重边和自环,处理方法是自环不添加到图中,重边只算最短的。

相关题:BZOJ 2763,计蒜客 A1958 [TODO: add links]

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename Int>
void getInt(Int &x) {
x = 0; char ch = getchar(); Int sgn = 1;
while (ch < '0' || ch > '9') { if (ch == '-') sgn = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); }
x *= sgn;
}
const LL MAX_N = 1000 + 5;
const LL MAX_K = 10 + 5;
struct Edge {
LL from, to, cost;
Edge(LL from_ = 0, LL to_ = 0, LL cost_ = 0)
: from(from_), to(to_), cost(cost_) {}
};
vector <Edge> g[MAX_N];
LL n, m, k, s, t;
struct Node {
LL val, id, used;
Node(LL val_ = 0, LL id_ = 0, LL used_ = 0)
: val(val_), id(id_), used(used_) {}
friend bool operator > (const Node &x, const Node &y) {
return x.val > y.val;
}
};
priority_queue < Node, vector <Node>, greater <Node> > q;
LL d[MAX_N][MAX_K];
map < pair <LL, LL>, LL> mp;
int main() {
getInt(n), getInt(m), getInt(s), getInt(t), getInt(k);
LL u, v, w;
for (LL i = 1; i <= m; ++i) {
getInt(u), getInt(v), getInt(w);
if (u == v) continue;
else if (mp.find({u, v}) != mp.end()) mp[{u, v}] = min(mp[{u, v}], w);
else if (mp.find({v, u}) != mp.end()) mp[{v, u}] = min(mp[{v, u}], w);
else mp[{u, v}] = w;
}
for (auto const &e: mp) {
u = e.first.first; v = e.first.second; w = e.second;
g[u].push_back(Edge(u, v, w));
g[v].push_back(Edge(v, u, w));
}
for (LL i = 0; i < MAX_N; ++i) {
for (LL j = 0; j < MAX_K; ++j) {
d[i][j] = INT_MAX;
}
}
d[s][0] = 0;
q.push(Node(0, s, 0));
while (!q.empty()) {
Node x = q.top(); q.pop();
for (unsigned i = 0; i < g[x.id].size(); ++i) {
Edge &e = g[x.id][i];
// pay
if (x.val + e.cost < d[e.to][x.used]) {
d[e.to][x.used] = x.val + e.cost;
q.push(Node(d[e.to][x.used], e.to, x.used));
}
// else
if (x.used < k && x.val + 0 < d[e.to][x.used + 1]) {
d[e.to][x.used + 1] = x.val + 0;
q.push(Node(d[e.to][x.used + 1], e.to, x.used + 1));
}
}
}
LL ans = INT_MAX;
for (LL i = 0; i <= k; ++i) ans = min(ans, d[t][i]);
printf("%lld\n", ans);
return 0;
}

题目链接:2019牛客多校第四场C

参考我的ICPC 2019 南昌邀请赛网络预赛,计蒜客38228 Max answer 题解。 [TODO: add links]

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 3000000 + 5;
const int Q_MIN = 0;
const int Q_MAX = 1;
const int Q_PRE = 0;
const int Q_SUF = 1;
const LL INF = 3 * LL(1E12) + 5;
int n;
int a[MAX_N], b[MAX_N];
int lNext[MAX_N], rNext[MAX_N];
LL pre[MAX_N], suf[MAX_N];
LL preMin[MAX_N << 2], preMax[MAX_N << 2], sufMin[MAX_N << 2], sufMax[MAX_N << 2];
inline int ls(int o) {
return o << 1;
}
inline int rs(int o) {
return o << 1 | 1;
}
inline void pushUp(int o) {
preMin[o] = min(preMin[ls(o)], preMin[rs(o)]);
preMax[o] = max(preMax[ls(o)], preMax[rs(o)]);
sufMin[o] = min(sufMin[ls(o)], sufMin[rs(o)]);
sufMax[o] = max(sufMax[ls(o)], sufMax[rs(o)]);
}
void build(int o, int l, int r) {
if (l == r) {
preMin[o] = preMax[o] = pre[l];
sufMin[o] = sufMax[o] = suf[l];
return;
}
int mid = (l + r) >> 1;
build(ls(o), l, mid);
build(rs(o), mid + 1, r);
pushUp(o);
}
LL query(int qType, int qArr, int o, int l, int r, int L, int R) {
if (L <= l && r <= R) {
if (qType == Q_MIN && qArr == Q_PRE) return preMin[o];
if (qType == Q_MAX && qArr == Q_PRE) return preMax[o];
if (qType == Q_MIN && qArr == Q_SUF) return sufMin[o];
if (qType == Q_MAX && qArr == Q_SUF) return sufMax[o];
}
int mid = (l + r) >> 1;
LL ret = (qType == Q_MIN ? INF : -INF);
if (L <= mid) {
if (qType == Q_MIN) ret = min(ret, query(qType, qArr, ls(o), l, mid, L, R));
if (qType == Q_MAX) ret = max(ret, query(qType, qArr, ls(o), l, mid, L, R));
}
if (R > mid) {
if (qType == Q_MIN) ret = min(ret, query(qType, qArr, rs(o), mid + 1, r, L, R));
if (qType == Q_MAX) ret = max(ret, query(qType, qArr, rs(o), mid + 1, r, L, R));
}
return ret;
}
void getNext() {
for (int i = 1; i <= n; ++i) lNext[i] = rNext[i] = i;
a[0] = a[n + 1] = -(int(1E6) + 5);
for (LL i = 1; i <= n; ++i) {
while(a[i] <= a[lNext[i] - 1]) lNext[i] = lNext[lNext[i] - 1];
}
for (LL i = n; i >= 1; --i) {
while(a[i] <= a[rNext[i] + 1]) rNext[i] = rNext[rNext[i] + 1];
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
for (int i = 1; i <= n; ++i) scanf("%d", b + i);
for (int i = 1; i <= n; ++i) pre[i] = pre[i - 1] + b[i];
for (int i = n; i >= 1; --i) suf[i] = suf[i + 1] + b[i];
build(1, 1, n);
getNext();
LL ans = -INF;
for (int i = 1; i <= n; ++i) {
if (a[i] > 0) {
LL lCon = 0, rCon = 0;
if (lNext[i] <= i) lCon = query(Q_MAX, Q_SUF, 1, 1, n, lNext[i], i) - suf[i + 1];
if (i + 1 <= rNext[i]) rCon = query(Q_MAX, Q_PRE, 1, 1, n, i + 1, rNext[i]) - pre[i];
ans = max(ans, a[i] * (lCon + rCon));
} else if (a[i] == 0) {
ans = max(ans, 0LL);
} else {
LL lCon = 0, rCon = 0;
if (lNext[i] <= i) lCon = query(Q_MIN, Q_SUF, 1, 1, n, lNext[i], i) - suf[i + 1];
if (i + 1 <= rNext[i]) rCon = query(Q_MIN, Q_PRE, 1, 1, n, i + 1, rNext[i]) - pre[i];
ans = max(ans, a[i] * (lCon + rCon));
}
}
printf("%lld\n", ans);
return 0;
}

题目链接:2019牛客多校第三场J

使用链表来模拟题目中的 Array,这样插入删除都是 \(O(1)\) 的,记录每个元素在内存中的位置,即可以在短时间内查询到某个元素的迭代器,这里我使用 map 从 \(s\) 映射到在链表中对应的迭代器的位置。坑点:01 和 001 不一样,所有的 \(s\) 要在前面加 \(1\)

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;
typedef long long LL;
int T, q, m;
LL s;
struct Node {
int v;
LL id;
};
list <Node> lst;
map < LL, list <Node>::iterator > h;
void getInt(LL &x) {
x = 1; char ch = getchar(); LL sgn = 1;
while (!isdigit(ch)) { if (ch == '-') sgn = -1; ch = getchar(); }
while (isdigit(ch)) { x = x * 10 + (ch ^ 48); ch = getchar(); }
x *= sgn;
}
int main() {
scanf("%d", &T);
for (int cs = 1; cs <= T; ++cs) {
lst.clear(); h.clear();
scanf("%d%d", &q, &m);
int op, v;
for (int i = 1; i <= q; ++i) {
scanf("%d", &op); getInt(s); scanf("%d", &v);
switch (op) {
case 0: {
if (h.find(s) == h.end()) {
lst.push_back({v, s});
auto it = lst.end(); --it;
h[s] = it;
if (lst.size() > m) {
h.erase(lst.front().id);
lst.pop_front();
}
printf("%d\n", v);
} else {
int ele = h[s]->v;
lst.erase(h[s]);
lst.push_back({ele, s});
auto it = lst.end(); --it;
h[s] = it;
printf("%d\n", ele);
}
break;
}
case 1: {
if (h.find(s) == h.end()) {
puts("Invalid");
} else {
auto it = h[s];
if (v == 0) {
it = it;
} else if (v == 1) {
if (it == lst.end()) it = lst.end();
else ++it;
} else if (v == -1) {
if (it == lst.begin()) it = lst.end();
else --it;
}
if (it == lst.end()) puts("Invalid");
else printf("%d\n", it->v);
}
break;
}
}
}
}
return 0;
}

题目链接:HDU 6601

首先可以确定为了在线性时间内判断和获取一个可以形成最大三角形的三元组,我们需要获得的是一个已经排序的序列。已经从大到小排序的序列,只有 \(d_i \leq d_{i+1} + d_{i + 2}\) 的时候不能构成三角形,我们考虑最极端的情况,即 \(d_i = d_{i+1} + d_{i+2}\),根据斐波那契数列的性质,最多这个数列会在 45 项内收敛到 0。所以我们只要维护区间前 45 大的数,然后 \(O(45)\) 地判断最大三角形就可以了,这里可以用线段树实现,把 push-up 操作改成归并即可。

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 100000 + 5;
const int MT_N = 45;
struct Node {
int mt[MT_N];
int tot;
Node() { tot = 0; }
} tree[MAX_N * 4];
inline int ls(const int &o) {
return o << 1;
}
inline int rs(const int &o) {
return o << 1 | 1;
}
void pushUp(const int &o) {
int *l = tree[ls(o)].mt;
int *r = tree[rs(o)].mt;
int *x = tree[o].mt;
int lTot = tree[ls(o)].tot, rTot = tree[rs(o)].tot;
int lPtr = 0, rPtr = 0, oPtr = 0;
while (oPtr < MT_N) {
if (lPtr < lTot && rPtr < rTot) {
if (l[lPtr] > r[rPtr]) x[oPtr++] = l[lPtr++];
else x[oPtr++] = r[rPtr++];
} else if (lPtr == lTot) {
x[oPtr++] = r[rPtr++];
} else if (rPtr == rTot) {
x[oPtr++] = l[lPtr++];
}
if (lPtr == lTot && rPtr == rTot) break;
}
tree[o].tot = oPtr;
}
void build(int *a, int o, int l, int r) {
if (l == r) {
tree[o].tot = 1;
tree[o].mt[0] = a[l];
return;
}
int mid = (l + r) >> 1;
build(a, ls(o), l, mid);
build(a, rs(o), mid + 1, r);
pushUp(o);
}
Node query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return tree[o];
}
int mid = (l + r) >> 1;
Node ret, lAns, rAns;
if (L <= mid) lAns = query(ls(o), l, mid, L, R);
if (R > mid) rAns = query(rs(o), mid + 1, r, L, R);
int *lArr = lAns.mt, *rArr = rAns.mt, *x = ret.mt, *oArr = ret.mt;
int lPtr = 0, rPtr = 0, oPtr = 0, lTot = lAns.tot, rTot = rAns.tot;
while (oPtr < MT_N) {
if (lPtr < lTot && rPtr < rTot) {
if (lArr[lPtr] > rArr[rPtr]) x[oPtr++] = lArr[lPtr++];
else x[oPtr++] = rArr[rPtr++];
} else if (lPtr == lTot) {
x[oPtr++] = rArr[rPtr++];
} else if (rPtr == rTot) {
x[oPtr++] = lArr[lPtr++];
}
if (lPtr == lTot && rPtr == rTot) break;
}
ret.tot = oPtr;
return ret;
}
int n, q, a[MAX_N];
int main() {
while (scanf("%d%d", &n, &q) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
build(a, 1, 1, n);
int u, v;
for (int i = 1; i <= q; ++i) {
scanf("%d%d", &u, &v);
Node t = query(1, 1, n, u, v);
if (t.tot < 3) {
puts("-1");
continue;
} else {
bool ok = false;
LL ans;
for (int j = 2; j < t.tot; ++j) {
if (t.mt[j - 1] > t.mt[j - 2] - t.mt[j]) {
ans = LL(t.mt[j - 1]) + LL(t.mt[j]) + LL(t.mt[j - 2]);
ok = true;
break;
}
}
if (ok) printf("%lld\n", ans);
else puts("-1");
}
}
}
return 0;
}

题目链接:HDU 6582

找到左右可能是最短路的边,利用这些边重新建图求最小割。寻找这些边的方法是,在跑完单源最短路后,\(d_u + c(u, v) = d_v\) 的边就是新图中的边。

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
147
148
149
150
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename Int>
void getInt(Int &x) {
x = 0; char ch = getchar(); Int sgn = 1;
while (!isdigit(ch)) { if (ch == '-') sgn = -1; ch = getchar(); }
while (isdigit(ch)) { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); }
x *= sgn;
}
namespace NetworkFlow {
const LL maxn = 10000 * 4 + 100;
LL n, m, s, t, tot;
struct Edge {
LL from, to, cap, flow;
Edge(LL from_ = 0, LL to_ = 0, LL cap_ = 0, LL flow_ = 0)
: from(from_), to(to_), cap(cap_), flow(flow_) {}
};
vector <Edge> edges;
vector <LL> g[maxn];
void addEdge(LL from, LL to, LL cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
tot = edges.size();
g[from].push_back(tot - 2);
g[to].push_back(tot - 1);
}
bool vis[maxn];
LL d[maxn], cur[maxn];
queue <LL> q;
bool bfs() {
memset(vis, 0, sizeof vis);
while (!q.empty()) q.pop();
q.push(s); d[s] = 0; vis[s] = 1;
while (!q.empty()) {
LL x = q.front(); q.pop();
for (unsigned i = 0; i < g[x].size(); ++i) {
Edge &e = edges[g[x][i]];
if (!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
LL dfs(LL x, LL a) {
if (x == t || a == 0) return a;
LL flow = 0, f;
for (LL &i = cur[x]; i < g[x].size(); ++i) {
Edge &e = edges[g[x][i]];
if (d[x] + 1 == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f; edges[g[x][i] ^ 1].flow -= f;
flow += f; a -= f;
if (!a) break;
}
}
return flow;
}
LL maxFlow() {
LL flow = 0;
while (bfs()) {
memset(cur, 0, sizeof cur);
flow += dfs(s, INT_MAX);
}
return flow;
}
void init() {
edges.clear();
for (LL i = 0; i < maxn; ++i) g[i].clear();
memset(vis, 0, sizeof vis);
memset(d, 0, sizeof d);
memset(cur, 0, sizeof cur);
while (!q.empty()) q.pop();
n = m = s = t = tot = 0;
}
};
const LL MAX_N = 10000 + 5;
LL T, n, m;
struct Edge {
LL from, to, cost;
Edge(LL from_ = 0, LL to_ = 0, LL cost_ = 0)
: from(from_), to(to_), cost(cost_) {}
};
vector <Edge> g[MAX_N];
vector <Edge> edgeVec;
LL d[MAX_N];
void ssspInit() {
for (LL i = 0; i <= n; ++i) {
g[i].clear();
}
edgeVec.clear();
}
struct Status {
LL val, to;
Status(LL val_ = 0, LL to_ = 0)
: val(val_), to(to_) {}
friend bool operator > (const Status &sx, const Status &sy) {
return sx.val > sy.val;
}
};
priority_queue < Status, vector <Status>, greater <Status> > q;
LL dijkstra() {
while (!q.empty()) q.pop();
for (LL i = 1; i <= n; ++i) d[i] = LL(1E18);
d[1] = 0; q.push(Status(0, 1));
while (!q.empty()) {
Status f = q.top(); q.pop();
for (unsigned i = 0; i < g[f.to].size(); ++i) {
Edge &e = g[f.to][i];
if (f.val + e.cost < d[e.to]) {
d[e.to] = f.val + e.cost;
q.push(Status(d[e.to], e.to));
}
}
}
return d[n];
}
int main() {
scanf("%lld", &T);
for (LL cs = 1; cs <= T; ++cs) {
scanf("%lld%lld", &n, &m);
LL u, v, c;
ssspInit();
for (LL i = 1; i <= m; ++i) {
scanf("%lld%lld%lld", &u, &v, &c);
g[u].push_back(Edge(u, v, c));
edgeVec.push_back(Edge(u, v, c));
}
LL sssp = dijkstra();
// cout << "SSSP = " << sssp << endl;
if (sssp == LL(1E18)) {
puts("0");
continue;
}
NetworkFlow::init();
for (auto const &e: edgeVec) {
if (d[e.from] + e.cost == d[e.to]) {
NetworkFlow::addEdge(e.from, e.to, e.cost);
}
}
// NetworkFlow::addEdge(1, 1 + 2, INT_MAX);
// NetworkFlow::addEdge(2, n + 2, INT_MAX);
NetworkFlow::s = 1; NetworkFlow::t = n;
LL ans = NetworkFlow::maxFlow();
printf("%lld\n", ans);
}
return 0;
}

题目链接:2019牛客多校第三场B

Substring:从第一个位置开始,为 0 就 -1,为 1 就 +1,两个值为 \(x\) 的位置中间的是合法串,统计所有的合法串中最长的即可。

Subsequence:记 0 的个数为 \(n_0\),1的个数为 \(n_1\),答案为 \(\min \{ n_0, n_1\}\)

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 100000 + 5;
int n, z, o;
char s[MAX_N];
map < int, vector <int> > pos;
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
int cnt = 0;
pos[0].push_back(0);
for (int i = 1; i <= n; ++i) {
if (s[i] == '0') --cnt;
else ++cnt;
pos[cnt].push_back(i);
}
int maxString = 0;
for (auto const &e: pos) {
if (e.second.size() >= 2) {
maxString = max(maxString, e.second.back() - e.second.front());
}
}
for (int i = 1; i <= n; ++i) {
z += (s[i] == '0');
o += (s[i] == '1');
}
int maxSub = min(o, z);
printf("%d %d\n", maxString, maxSub * 2);
return 0;
}

题目链接:2019 牛客多校第二场 F

\(2n\) 个人分成 \(n+n\) 两组,那么一共是 \({2n \choose n}\) 种情况,如果每一次都暴力计算竞争力总和,那么每一次的时间复杂度将是 \(O(n^2)\),总时间复杂度是 \(O({\rm C}_{2n}^{n} \times n^2)\),对于 \(n \leq 14\) 这个数据范围来说是过不去的。

考虑如何惰性计算这个竞争力总和,也就是说从状态 \(A\) 转移到状态 \(B\) 的时候根据状态的变化来计算贡献,这是一个常用的思路。

起初,我尝试讨论第 \(i\) 个元素是被分到哪一组,元素 \(x\) 加入 \(A\) 组对总和的贡献就是 \(\sum_{i \neq x} v_{ix}\),加到一个变量里求和即可,渐进时间复杂度 \(O({\rm C}_{2n}^{n} \times n)\),但是可能由于姿势不当 TLE 了。后来在题解中看到了一种思路类似的解法——算贡献的时候不用加,用减。假设当前没有分组的变量的竞争值就是 \(\sum_{i=1}^{n} \sum_{j = i+1}^{n} v_{ij}\),每次将一个元素分到某一组后,这个元素和原来这个组里的所有元素的贡献需要减去,那么在 \(n+n\) 全分配完之后得到的这个变量的值就是还没有被“割断”的“边”。这样做有个好处就是有一个可以减枝的条件:如果当前还没有被“割断”的“边”的总和已经小于当前的最大值了,这条路就没有必要继续枚举了,所以在同是 \(O({\rm C}_{2n}^{n} \times n)\) 的复杂度的情况下,这个算法快了很多,1676/4000ms。

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 14 * 2 + 5;
int n, tot;
int v[MAX_N][MAX_N];
int a[MAX_N], aPtr, b[MAX_N], bPtr;
LL sum, maxAns = -1;
void dfs(int x, int cnt) {
if (sum < maxAns) return;
if (cnt > n || cnt + (tot - x + 1) < n) return;
if (x == tot + 1) {
maxAns = max(maxAns, sum);
return;
}
LL contribute;
contribute = 0;
for (int i = 1; i <= aPtr; ++i) contribute -= v[x][a[i]];
a[++aPtr] = x; sum += contribute;
dfs(x + 1, cnt + 1);
--aPtr; sum -= contribute;
contribute = 0;
for (int i = 1; i <= bPtr; ++i) contribute -= v[x][b[i]];
b[++bPtr] = x; sum += contribute;
dfs(x + 1, cnt);
--bPtr; sum -= contribute;
}
int main() {
scanf("%d", &n);
tot = (n << 1);
for (int i = 1; i <= tot; ++i) {
for (int j = 1; j <= tot; ++j) {
scanf("%d", &v[i][j]);
sum += (i < j) * v[i][j];
}
}
dfs(1, 0);
printf("%lld\n", maxAns);
return 0;
}

题目链接:2019 牛客多校第二场 H

动态维护第\(i\)行的每一个元素\(s_{i,j}\),向上最大能到达的高度\(h_j\),向左能到达的最远点\(l_j\),向右能到达的最远点\(r_j\),满足\(l_j\)\(r_j\)\(h\)值都是大于等于\(h_j\)的。那么我们知道最大值是

\[ \max_{i = 1}^{n} \max_{j = 1}^{m} h_j \times (r_j - l_j + 1) \]

那么我们只要在\(h_j \times (r_j - l_j + 1)\)的基础上减去一行或者一列,以及\(h_j \times (r_j - l_j + 1)\)本身都存起来,排序去重后取第二个就是全局第二大。渐进时间复杂度\(O(n^2 \log n)\)

这道题去重会卡 set。

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 1000 + 5;
int n, m;
char s[MAX_N][MAX_N];
int l[MAX_N], r[MAX_N], h[MAX_N];
struct Rectangle {
int lx, ly, rx, ry, s;
Rectangle(int lx_ = 0, int ly_ = 0, int rx_ = 0, int ry_ = 0, int s_ = 0)
: lx(lx_), ly(ly_), rx(rx_), ry(ry_), s(s_) {}
friend bool operator < (const Rectangle &u, const Rectangle &v) {
if (u.s != v.s) return u.s > v.s;
else {
if (u.lx != v.lx) return u.lx < v.lx;
else if (u.ly != v.ly) return u.ly < v.ly;
else if (u.rx != v.rx) return u.rx < v.rx;
else return u.ry < v.ry;
}
}
friend bool operator == (const Rectangle &u, const Rectangle &v) {
return u.lx == v.lx && u.ly == v.ly && u.rx == v.rx && u.ry == v.ry && u.s == v.s;
}
inline void debug() {
printf("DEBUG LOG: lx = %d, ly = %d, rx = %d, ry = %d, s = %d\n", lx, ly, rx, ry, s);
}
};
// set <Rectangle> uni;
int tot = 0;
Rectangle v[MAX_N * MAX_N * 5];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%s", &s[i][1]);
}
int ans = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (s[i][j] == '1') ++h[j];
else h[j] = 0;
}
h[0] = h[m + 1] = -1;
int k;
for (int j = 1; j <= m; ++j) {
k = j;
while (h[j] <= h[k - 1]) k = l[k - 1];
l[j] = k;
}
for (int j = m; j >= 1; --j) {
k = j;
while (h[j] <= h[k + 1]) k = r[k + 1];
r[j] = k;
}
// for (int j = 1; j <= m; ++j) printf("l = %d, r = %d, h = %d\n", l[j], r[j], h[j]);
for (int j = 1; j <= m; ++j) {
if (h[j] == 0) continue;
ans = max(ans, h[j] * (r[j] - l[j] + 1));
// uni.insert(Rectangle(i - h[j] + 1, l[j], i, r[j], h[j] * (r[j] - l[j] + 1)));
v[++tot] = Rectangle(i - h[j] + 1, l[j], i, r[j], h[j] * (r[j] - l[j] + 1));
if (r[j] - l[j] + 1 > 1) {
int th = h[j], tl = r[j] - l[j]; int ts = th * tl;
// uni.insert(Rectangle(i - h[j] + 1, l[j], i, r[j] - 1, ts));
v[++tot] = Rectangle(i - h[j] + 1, l[j], i, r[j] - 1, ts);
// uni.insert(Rectangle(i - h[j] + 1, l[j] + 1, i, r[j], ts));
v[++tot] = Rectangle(i - h[j] + 1, l[j] + 1, i, r[j], ts);
}
if (h[j] > 1) {
int th = h[j] - 1, tl = r[j] - l[j] + 1; int ts = th * tl;
// uni.insert(Rectangle(i - h[j] + 2, l[j], i, r[j], ts));
v[++tot] = Rectangle(i - h[j] + 2, l[j], i, r[j], ts);
// uni.insert(Rectangle(i - h[j] + 1, l[j], i - 1, r[j], ts));
v[++tot] = Rectangle(i - h[j] + 1, l[j], i - 1, r[j], ts);
}
}
}
// for (auto e: uni) e.debug();
sort(v + 1, v + tot + 1);
int uniTot = unique(v + 1, v + tot + 1) - v;
// for (int i = 1; i < uniTot; ++i) v[i].debug();
if (uniTot == 0 || uniTot == 1) puts("0");
else {
// uni.erase(uni.begin());
printf("%d\n", v[2].s);
}
return 0;
}

题目链接:[Codeforces 1195E]

典型二维RMQ,具体解法见我的上一篇博文BZOJ 1047 - 理想的正方形(题解)。[TODO: add links]

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
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX_N = 3000 + 5;
int n, m, a, b;
LL g[MAX_N * MAX_N], x, y, z;
LL t[MAX_N][MAX_N];
LL getG(int u) {
if (g[u] != -1) return g[u];
else return g[u] = (getG(u - 1) * x % z + y % z) % z;
}
struct Node {
LL v, id;
friend bool operator == (const Node &nx, const Node &ny) {
return nx.id == ny.id && nx.v == ny.v;
}
};
// maintain MIN
struct GreaterQueue {
deque <Node> ori, q;
void push(LL x, int pos) {
ori.push_back({x, pos});
while (!q.empty() && q.back().v >= x) q.pop_back();
q.push_back({x, pos});
}
void pop() {
Node f = ori.front();
ori.pop_front();
if (f == q.front()) q.pop_front();
}
int front() {
return q.front().v;
}
void init() {
while (!q.empty()) q.pop_back();
while (!ori.empty()) ori.pop_back();
}
} qMin;
LL sum = 0;
int rMin[MAX_N][MAX_N], cMin[MAX_N][MAX_N];
int main() {
memset(g, -1, sizeof g);
scanf("%d%d%d%d", &n, &m, &a, &b);
scanf("%lld%lld%lld%lld", &g[0], &x, &y, &z);
g[0] %= z;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
t[i][j] = getG((i - 1) * m + j - 1);
// cout << t[i][j] << " ";
}
// cout << endl;
}
for (int i = 1; i <= n; ++i) {
qMin.init();
for (int j = 1; j <= m; ++j) {
if (j > b) qMin.pop();
qMin.push(t[i][j], j);
rMin[i][j] = qMin.front();
}
}
for (int j = 1; j <= m; ++j) {
qMin.init();
for (int i = 1; i <= n; ++i) {
if (i > a) qMin.pop();
qMin.push(rMin[i][j], i);
cMin[i][j] = qMin.front();
}
}
for (int i = a; i <= n; ++i) {
for (int j = b; j <= m; ++j) {
sum += cMin[i][j];
}
}
printf("%lld\n", sum);
return 0;
}