2019 Multi-University Training Contest 2
Contest Info
[Practice Link](https://cn.vjudge.net/contest/313503)
Solved | A | B | C | D | E | F | G | H | I | J | K | L |
---|---|---|---|---|---|---|---|---|---|---|---|---|
7/12 | - | O | - | - | O | - | - | Ø | Ø | O | O | O |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
B. Beauty Of Unimodal Sequence
题意:
定义一个序列\(a_i\)的下标序列是漂亮的:
- \(1 \leq p_1 < p_2 < \cdots < p_k \leq n\)
- 存在一个\(t \in [1, k]\)满足\(a_{p_1} < a_{p_2} < \cdots < a_{p_t}\) 并且\(a_{p_t} > a_{p_{t + 1}} > \cdots a_{p_k}\)
现在要找一个字典序最大的漂亮下标序列以及一个字典序最小的漂亮下标序列。
思路:
- 令\(f[i][0]\)表示前\(i\)个数以\(i\)结尾,并且满足处于上升状态的最大长度
- 令\(f[i][1]\)表示前\(i\)个数以\(i\)结尾,并且满足处于下降状态的最大长度
- 令\(g[i][0]\)表示后\(i\)个数以\(i\)结尾,并且满足处于下降状态的最大长度
- 令\(g[i][1]\)表示后\(i\)个数以\(i\)结尾,并且满足处于上升状态的最大长度
可以先求出\(f, g\)的值,然后考虑从左往右按位确定。
显然我们要确定第\(i\)位的数是什么,我们就要去找\(g[i][0]/g[i][1]\)的值为\(Max - i + 1\)中满足条件的最大/最小的数,直接按\(g\)的值分类放好,暴力查找即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 300010
#define INF 0x3f3f3f3f
#define pii pair <int, int>
#define fi first
#define se second
int n, m, a[N], b[N], f[N][2], g[N][2];
vector <vector<vector<int>>> vec;
struct SEG {
int t[N << 2];
void build(int id, int l, int r) {
t[id] = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
}
void update(int id, int l, int r, int pos, int x) {
if (l == r) {
t[id] = max(t[id], x);
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) update(id << 1, l, mid, pos, x);
else update(id << 1 | 1, mid + 1, r, pos, x);
t[id] = max(t[id << 1], t[id << 1 | 1]);
}
int query(int id, int l, int r, int ql, int qr) {
if (ql > qr) return 0;
if (l >= ql && r <= qr) return t[id];
int mid = (l + r) >> 1;
int res = 0;
if (ql <= mid) res = max(res, query(id << 1, l, mid, ql, qr));
if (qr > mid) res = max(res, query(id << 1 | 1, mid + 1, r, ql, qr));
return res;
}
}seg[2];
int main() {
while (scanf("%d", &n) != EOF) {
m = 0;
for (int i = 1; i <= n; ++i) scanf("%d", a + i), b[++m] = a[i];
sort(b + 1, b + 1 + m);
m = unique(b + 1, b + 1 + m) - b - 1;
for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + 1 + m, a[i]) - b;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < 2; ++j) {
f[i][j] = 0;
g[i][j] = 0;
}
}
//0表示处于上升状态
//1表示处于下降状态
seg[0].build(1, 1, m);
seg[1].build(1, 1, m);
for (int i = 1; i <= n; ++i) {
f[i][0] = 1;
f[i][1] = 1;
//0的转移
f[i][0] = max(f[i][0], seg[0].query(1, 1, m, 1, a[i] - 1) + 1);
//1的转移
f[i][1] = max(f[i][1], seg[0].query(1, 1, m, a[i] + 1, m) + 1);
f[i][1] = max(f[i][1], seg[1].query(1, 1, m, a[i] + 1, m) + 1);
f[i][1] = max(f[i][1], f[i][0]);
seg[0].update(1, 1, m, a[i], f[i][0]);
seg[1].update(1, 1 ,m, a[i], f[i][1]);
}
//0表示处于下降状态
//1表示处于上升状态
seg[0].build(1, 1, m);
seg[1].build(1, 1, m);
for (int i = n; i >= 1; --i) {
g[i][0] = 1;
g[i][1] = 1;
//0的转移
g[i][0] = max(g[i][0], seg[0].query(1, 1, m, a[i] + 1, m) + 1);
g[i][0] = max(g[i][0], seg[1].query(1, 1, m, a[i] + 1, m) + 1);
//1的转移
g[i][1] = max(g[i][1], seg[1].query(1, 1, m, 1, a[i] - 1) + 1);
g[i][0] = max(g[i][0], g[i][1]);
seg[0].update(1, 1, m, a[i], g[i][0]);
seg[1].update(1, 1, m, a[i], g[i][1]);
}
int Max = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < 2; ++j) {
Max = max(Max, f[i][j]);
}
}
vec.clear(); vec.resize(n + 1);
for (int i = 1; i <= n; ++i) vec[i].resize(2);
for (int i = n; i >= 1; --i) {
vec[g[i][0]][0].push_back(i);
vec[g[i][1]][1].push_back(i);
}
vector <int> small, big;
int now = 0, sta = 0, len = 0;
while (len < Max) {
int pos = -1, nsta = -1;
//当前处于上升状态
if (sta == 0) {
for (auto it : vec[Max - len][0]) {
if ((now == 0 || a[it] > a[now]) && it > pos && it > now) {
pos = it;
nsta = 0;
break;
}
}
for (auto it : vec[Max - len][1]) {
if ((now == 0 || a[it] != a[now]) && it > pos && it > now) {
pos = it;
nsta = 1;
break;
}
}
} else { //当前处于下降状态
nsta = 1;
for (auto it : vec[Max - len][1]) {
if ((now == 0 || a[it] < a[now]) && it > pos && it > now) {
pos = it;
break;
}
}
}
big.push_back(pos);
now = pos;
sta = nsta;
++len;
}
now = 0, sta = 0, len = 0;
while (len < Max) {
int pos = INF, nsta = -1;
//当前处于上升状态
if (sta == 0) {
for (auto it : vec[Max - len][0]) {
if ((now == 0 || a[it] > a[now]) && it < pos && it > now) {
pos = it;
nsta = 0;
}
}
for (auto it : vec[Max - len][1]) {
if ((now == 0 || a[it] != a[now]) && it < pos && it > now) {
pos = it;
nsta = 1;
}
}
} else { //当前处于下降状态
nsta = 1;
for (auto it : vec[Max - len][1]) {
if ((now == 0 || a[it] < a[now]) && it < pos && it > now) {
pos = it;
}
}
}
small.push_back(pos);
now = pos;
sta = nsta;
++len;
}
for (int i = 0; i < Max; ++i) printf("%d%c", small[i], " \n"[i == Max - 1]);
for (int i = 0; i < Max; ++i) printf("%d%c", big[i], " \n"[i == Max - 1]);
}
return 0;
}
E. Everything Is Generated In Equal Probability
题意:
给定一个数字\(N\),进行如下操作:
- 先从\([1, N]\)中等概率选出一个\(n\),然后等概率生成\(1-n\)的一个排列,接着执行下列程序:
- 如果当且序列长度$ > 0$,那么将逆序对的贡献加入到答案中,并等概率生成这个序列的一个子序列,递归操作
问最终答案的期望是多少?
思路:
考虑一个长度为\(n\)的排列(每个数各不相同,不一定要求连续),其逆序对的期望为\({n \choose 2} / 2\),因为每一对下标提供的期望都是\(\frac{1}{2}\),并且期望具有可加性。
那么令\(f(i)\)表示当\(n = i\)时的期望,那么有:
移项有:
那么最后答案就是:
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 3010
const ll p = 998244353;
ll f[N], C[N][N], pw[N];
inline void add(ll &x, ll y) {
x += y;
if (x >= p) x -= p;
}
ll qmod(ll base, ll n) {
ll res = 1;
while (n) {
if (n & 1) {
res = res * base % p;
}
base = base * base % p;
n >>= 1;
}
return res;
}
int main() {
for (int i = 0; i < N; ++i) C[i][0] = C[i][i] = 1;
for (int i = 1; i < N; ++i) {
for (int j = 1; j < i; ++j) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % p;
}
}
pw[0] = 1;
for (int i = 1; i < N; ++i) pw[i] = pw[i - 1] * 2 % p;
f[0] = 0;
for (int i = 1; i < N; ++i) {
f[i] = 1ll * i * (i - 1) % p * pw[i - 2] % p;
for (int j = 0; j < i; ++j)
add(f[i], C[i][j] * f[j] % p);
f[i] = f[i] * qmod(pw[i] - 1, p - 2) % p;
}
for (int i = 1; i < N; ++i) add(f[i], f[i - 1]);
int n;
while (scanf("%d", &n) != EOF) {
printf("%lld\n", f[n] * qmod(n, p - 2) % p);
}
return 0;
}
H - Harmonious Army
题意:
有\(n\)个人,可以给每个人分配两种职业,战士或者魔法师,有\(m\)种关系\((u, v, a, b, c)\),如果\(u, v\)都是战士,那么团队战斗力\(+a\),如果都是魔法师,那么团队战斗时\(+c\),否则团队战斗力\(+b\)。
现在要求给这\(n\)个人分配职业,使得团队战斗力最大。
思路:
我们可以让对于每一对关系这样建图,考虑一条路径表示的是损失关系,比如:
那么我们先将所有战斗力加起来,然后相当于跑一个图上的最小割。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 200010
#define db double
#define INFLL 0x3f3f3f3f3f3f3f3f
#define ll long long
int n, m;
struct Dicnic {
struct Edge {
int to, nxt;
db flow;
Edge() {}
Edge(int to, int nxt, db flow) : to(to), nxt(nxt), flow(flow) {}
} edge[N << 1];
int head[N], tot;
int dep[N];
void Init() {
memset(head, -1, sizeof head);
tot = 0;
}
void addedge(int u, int v, db w, int rw = 0) {
edge[tot] = Edge(v, head[u], w);
head[u] = tot++;
edge[tot] = Edge(u, head[v], rw);
head[v] = tot++;
}
bool BFS() {
memset(dep, -1, sizeof dep);
queue<int> q;
q.push(1);
dep[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edge[i].nxt) {
if (edge[i].flow && dep[edge[i].to] == -1) {
dep[edge[i].to] = dep[u] + 1;
q.push(edge[i].to);
}
}
}
return dep[n] >= 0;
}
db DFS(int u, db f) {
if (u == n || f == 0) return f;
db w, used = 0;
for (int i = head[u]; ~i; i = edge[i].nxt) {
if (edge[i].flow && dep[edge[i].to] == dep[u] + 1) {
w = DFS(edge[i].to, min(f - used, edge[i].flow));
edge[i].flow -= w;
edge[i ^ 1].flow += w;
used += w;
if (used == f) return f;
}
}
if (!used) dep[u] = -1;
return used;
}
db solve() {
db ans = 0;
while (BFS()) {
ans += DFS(1, INFLL);
}
return ans;
}
}dicnic;
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
dicnic.Init();
int S = 1, T = n + 2;
n += 2;
ll sum = 0;
for (int i = 1, u, v, A, B, C; i <= m; ++i) {
scanf("%d%d%d%d%d", &u, &v, &A, &B, &C);
++u, ++v;
db a = (A + B) * 1.0 / 2;
db c = (B + C) * 1.0 / 2;
db e = (A + C) * 1.0 / 2 - B;
dicnic.addedge(S, u, a);
dicnic.addedge(S, v, a);
dicnic.addedge(u, T, c);
dicnic.addedge(v, T, c);
dicnic.addedge(u, v, e);
dicnic.addedge(v, u, e);
sum += A + B + C;
}
printf("%lld\n", (ll)round(sum - dicnic.solve()));
}
return 0;
}
I. I Love Palindrome String
题意:
给出一个串\(S\),对于长度\(i \in [1, |S|]\),找出有多少个子串\([l, r]\)满足如下条件:
- 子串长度等于\(i\)
- 子串\(S_lS_{l + 1} \cdots s_r\)是个回文串
- \(s_ls_{l + 1} \cdots s_{\left\lfloor \frac{l + r}{2} \right\rfloor}\)也是个回文串
思路:
考虑一个串本质不同的回文串个数只有\(\mathcal{O}(n)\)个,那么可以用\(PAM\)处理出来,然后枚举每一个本质不同的回文串,
用\(Manacher\)判一下前半部分是否是一个回文串即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 310010
#define ALP 26
struct Manacher {
int len;
char Ma[N << 1];
int Mp[N << 1];
void init(int len) {
this->len = len;
}
void work(char *s) {
int l = 0;
Ma[l++] = '$';
Ma[l++] = '#';
for (int i = 0; i < len; ++i) {
Ma[l++] = s[i];
Ma[l++] = '#';
}
Ma[l] = 0;
int mx = 0, id = 0;
for (int i = 0; i < l; ++i) {
Mp[i] = mx > i ? min(Mp[2 * id - i], mx - i) : 1;
while (Ma[i + Mp[i]] == Ma[i - Mp[i]]) Mp[i]++;
if (i + Mp[i] > mx) {
mx = i + Mp[i];
id = i;
}
}
}
bool check(int l, int r) {
int il = (l + 1) * 2, ir = (r + 1) * 2;
int mid = (il + ir) / 2;
int len = (r - l + 2) / 2;
return (Mp[mid] / 2) >= len;
}
}man;
struct PAM{
int Next[N][ALP];
int fail[N];
int cnt[N];
int num[N];
int len[N];
int s[N];
int last;
int n;
int p;
int newnode(int w){ // 初始化节点,w=长度
for(int i=0;i<ALP;i++)
Next[p][i] = 0;
cnt[p] = 0;
num[p] = 0;
len[p] = w;
return p++;
}
void init(){
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
s[n] = -1;
fail[0] = 1;
}
int get_fail(int x){
while(s[n-len[x]-1] != s[n]) x = fail[x];
return x;
}
bool add(int c){
bool F = 0;
c -= 'a';
s[++n] = c;
int cur = get_fail(last);
if(!Next[cur][c]){
int now = newnode(len[cur]+2);
fail[now] = Next[get_fail(fail[cur])][c];
Next[cur][c] = now;
num[now] = num[fail[now]] + 1;
F = 1;
}
last = Next[cur][c];
cnt[last]++;
return F;
}
void count(){
for(int i=p-1;i>=0;i--)
cnt[fail[i]] += cnt[i];
}
}pam;
char s[N];
int f[N], g[N];
int res[N], len;
int main() {
while (scanf("%s", s) != EOF) {
len = strlen(s);
man.init(len); man.work(s);
pam.init();
for (int i = 0; i <= len; ++i) res[i] = 0;
for (int i = 0; i < len; ++i) {
if (pam.add(s[i])) {
g[i] = 1;
} else {
g[i] = 0;
}
f[i] = pam.last;
}
pam.count();
for (int i = 0; i < len; ++i) {
if (g[i] == 0) continue;
int Len = pam.len[f[i]];
int l = i - Len + 1;
int r = i;
int mid = (l + r) >> 1;
if (man.check(l, mid)) {
res[Len] += pam.cnt[f[i]];
}
}
for (int i = 1; i <= len; ++i) printf("%d%c", res[i], " \n"[i == len]);
}
return 0;
}
J. Just Skip The Problem
题意:
jury有一个数\(x \in [0, 2^n - 1]\),你需要给出一些数\(y_i\),使得jury在告诉你\(x \& y_i\)是否等于\(y_i\)的时候,你就能确定jury的\(x\)是多少。
问有多少种\(y_i\)的序列,使得其长度最小,并且保证能确定对方的\(x\)是多少。
思路:
显然\(y_i\)的最大长度是\(n\),那么要保证每个\(x\)都能知道,显然最小长度也是\(n\),即每个二进制位都放一个\(1\)去问。
那么答案就是\(n!\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 2000010
const ll p = 1e6 + 3;
int n;
ll fac[N];
int main() {
fac[0] = 1;
for (int i = 1; i <= 2000000; ++i) {
fac[i] = fac[i - 1] * i % p;
}
while (scanf("%d", &n) != EOF) {
if (n >= p) puts("0");
else {
printf("%lld\n", fac[n]);
}
}
return 0;
}
K. Keen On Everything But Triangle
题意:
有\(n\)根棍子,现在有\(q\)次询问,每次询问给出一个区间\((l_i, r_i)\),询问这个区间内的棍子能组成的三角形的最大周长是多少?
思路:
如果只有一次询问的话,那么我们考虑直接排序,每三个每三个枚举一下,取最大值。
现在是多次询问,但是我们注意到只要超过\(44\)根棍子就一定能组成三角形,所以线段树维护区间最大的\(44\)根棍子即可。
注意不要用vector,太慢。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
#define INF 0x3f3f3f3f
int n, q, a[N];
struct SEG {
struct node {
int a[55];
node() {
a[0] = 0;
}
void add(int x) {
a[++a[0]] = x;
}
node operator + (const node &other) const {
node res = node();
int it = 1, it2 = 1;
while (res.a[0] <= 50 && (it <= a[0] || it2 <= other.a[0])) {
if (it > a[0]) {
res.add(other.a[it2]);
++it2;
} else if (it2 > other.a[0]) {
res.add(a[it]);
++it;
} else {
if (a[it] > other.a[it2]) {
res.add(a[it]);
++it;
} else {
res.add(other.a[it2]);
++it2;
}
}
}
return res;
}
}t[N << 2], res;
void build(int id, int l, int r) {
if (l == r) {
t[id] = node();
t[id].add(a[l]);
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
t[id] = t[id << 1] + t[id << 1 | 1];
}
void query(int id, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) {
res = res + t[id];
return;
}
int mid = (l + r) >> 1;
if (ql <= mid) query(id << 1, l, mid, ql, qr);
if (qr > mid) query(id << 1 | 1, mid + 1, r, ql, qr);
}
}seg;
int main() {
while (scanf("%d%d", &n, &q) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
seg.build(1, 1, n);
int l, r;
while (q--) {
scanf("%d%d", &l, &r);
ll res = -1;
seg.res = SEG::node();
seg.query(1, 1, n, l, r);
int sze = seg.res.a[0];
for (int i = 1; i <= sze - 2; ++i) {
ll A = seg.res.a[i], B = seg.res.a[i + 1], C = seg.res.a[i + 2];
if (A < B + C) {
res = A + B + C;
break;
}
}
printf("%lld\n", res);
}
}
return 0;
}
L. Longest Subarray
题意:
有一个长度为\(n\)的序列\(a_i \in [1, C]\),现在要求找一个长度最长的子段,使得满足如下要求:
思路:
考虑在固定右端点的情况下,每个\(x \in [1, C]\)的一段不合法区间是连续的,那么考虑用线段树维护标记,我们每次要找的就是最左的并且没有被打标记的点,它可以作为左端点。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define INF 0x3f3f3f3f
int n, c, k, a[N], L[N], R[N], cnt[N];
vector <vector<int>> vec;
struct SEG {
struct node {
int Min, pos, lazy;
node(int pos = INF) {
Min = INF;
this->pos = pos;
lazy = 0;
}
void add(int x) {
Min += 1ll * x;
lazy += x;
}
node (int Min, int pos) : Min(Min), pos(pos) {}
node operator + (const node &other) const {
node res = node();
if (Min < other.Min) {
res.Min = Min;
res.pos = pos;
} else if (Min > other.Min) {
res.Min = other.Min;
res.pos = other.pos;
} else if (Min == other.Min) {
res.Min = Min;
if (pos < other.pos) {
res.pos = pos;
} else {
res.pos = other.pos;
}
}
return res;
}
}t[N << 2], res;
void build(int id, int l, int r) {
if (l == r) {
t[id] = node(l);
t[id].Min = 0;
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
t[id] = t[id << 1] + t[id << 1 | 1];
}
void pushdown(int id) {
int &lazy = t[id].lazy;
if (!lazy) return;
t[id << 1].add(lazy);
t[id << 1 | 1].add(lazy);
lazy = 0;
}
void update(int id, int l, int r, int ql, int qr, int x) {
if (ql > qr) return;
if (l >= ql && r <= qr) {
t[id].add(x);
return;
}
int mid = (l + r) >> 1;
pushdown(id);
if (ql <= mid) update(id << 1, l, mid, ql, qr, x);
if (qr > mid) update(id << 1 | 1, mid + 1, r, ql, qr, x);
t[id] = t[id << 1] + t[id << 1 | 1];
}
void query(int id, int l, int r, int ql, int qr) {
if (ql > qr) return;
if (l >= ql && r <= qr) {
res = res + t[id];
return;
}
int mid = (l + r) >> 1;
pushdown(id);
if (ql <= mid) query(id << 1, l, mid, ql, qr);
if (qr > mid) query(id << 1 | 1, mid + 1, r, ql, qr);
}
}seg;
int main() {
while (scanf("%d%d%d", &n, &c, &k) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
if (k == 1) {
printf("%d\n", n);
continue;
}
vec.clear(); vec.resize(c + 1);
for (int i = n; i >= 1; --i) {
vec[a[i]].push_back(i);
}
for (int i = 1; i <= c; ++i) {
cnt[i] = 0;
L[i] = 1;
R[i] = 0;
}
seg.build(1, 1, n);
int res = 0;
for (int i = 1; i <= n; ++i) {
int C = a[i];
seg.update(1, 1, n, L[C], R[C], -1);
if (cnt[C] < k - 1) {
if (cnt[C] == 0) L[C] = 1;
++cnt[C];
R[C] = i;
} else if (cnt[C] == k - 1){
L[C] = vec[C].back() + 1;
++cnt[C];
R[C] = i;
} else {
vec[C].pop_back();
L[C] = vec[C].back() + 1;
R[C] = i;
}
seg.update(1, 1, n, L[C], R[C], 1);
seg.res = SEG::node();
seg.query(1, 1, n, 1, L[C] - 1);
if (seg.res.Min == 0) {
res = max(res, i - seg.res.pos + 1);
}
}
printf("%d\n", res);
}
return 0;
}