版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/82503040
【比赛链接】
【题解链接】
**【A】**Single Wildcard Pattern Matching
【思路要点】
- 判断 的星号前后是否为 中不相交的前缀和后缀。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN], t[MAXN];
int main() {
int n, m; read(n), read(m);
scanf("\n%s", s + 1);
scanf("\n%s", t + 1);
int pos = 0;
for (int i = 1; i <= n; i++)
if (s[i] == '*') pos = i;
if (pos == 0) {
bool flg = n == m;
for (int i = 1; i <= n; i++)
flg &= s[i] == t[i];
if (flg) printf("YES\n");
else printf("NO\n");
} else {
bool flg = n - 1 <= m;
for (int i = 1; i <= pos - 1; i++)
flg &= s[i] == t[i];
for (int i = pos + 1; i <= n; i++)
flg &= s[i] == t[m - (n - i)];
if (flg) printf("YES\n");
else printf("NO\n");
}
return 0;
}
**【B】**Pair of Toys
【思路要点】
- 答案即不等式组 的整数解的个数。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int main() {
long long n, k;
read(n), read(k);
long long l = 1, r = n;
chkmin(r, k - 1);
chkmax(l, k - n);
chkmin(r, (k - 1) / 2);
if (l > r) printf("0\n");
else writeln(r - l + 1);
return 0;
}
**【C】**Bracket Subsequence
【思路要点】
- 用栈处理括号序列,去掉 对括号。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN], ans[MAXN];
int main() {
int n, k;
read(n), read(k);
scanf("\n%s", s + 1);
int cnt = (n - k) / 2, len = 0;
for (int i = 1; i <= n; i++) {
if (s[i] == '(') ans[++len] = '(';
else if (cnt) cnt--, len--;
else ans[++len] = ')';
}
ans[len + 1] = 0;
printf("%s\n", ans + 1);
return 0;
}
**【D】**Array Restoration
【思路要点】
- 若数组中不存在 ,则将一个 替换为 。若数组中也不存在 ,那么问题无解。
- 将剩余的 替换为任意的与其相邻的数字。
- 从大到小考虑每一种数字,最大的数字 必须形成一个连续的区间, 必须形成一个连续的区间, 必须形成一个连续的区间,以此类推。
- 用并查集模拟判断即可,若上面任意一步不满足,则问题无解,否则输出上述方案即可。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, q, a[MAXN], f[MAXN];
vector <int> b[MAXN];
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
void merge(int x, int y) {
f[F(x)] = F(y);
}
int main() {
read(n), read(q);
bool big = false, zero = false;
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] == q) big = true;
if (a[i] == 0) zero = true;
}
if (!big && !zero) {
printf("NO\n");
return 0;
}
if (!big) {
for (int i = 1; i <= n; i++)
if (a[i] == 0) {
a[i] = q;
break;
}
}
for (int i = 1; i <= n; i++) {
if (a[i] != 0) continue;
int pos = i;
while (pos <= n && a[pos] == 0) pos++;
int val = max(a[i - 1], a[pos]);
for (int j = i; j < pos; j++)
a[j] = val;
}
for (int i = 1; i <= n; i++)
b[a[i]].push_back(i);
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = q; i >= 1; i--) {
for (unsigned j = 0; j < b[i].size(); j++) {
int tmp = b[i][j];
if (a[tmp - 1] >= i) merge(tmp - 1, tmp);
if (a[tmp + 1] >= i) merge(tmp + 1, tmp);
}
if (b[i].size()) {
int root = F(b[i][0]);
for (unsigned j = 1; j < b[i].size(); j++) {
int tmp = b[i][j];
if (root != F(tmp)) {
printf("NO\n");
return 0;
}
}
}
}
printf("YES\n");
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
return 0;
}
**【E】**Down or Right
【思路要点】
- 首先,如果没有询问的两点之间距离的限制,我们显然可以询问当前点右侧的一个点是否能够到达终点,如果能,向右侧走,如果不能,向下方走。
- 存在询问的两点之间距离的限制时,我们可以用这个方式走到迷宫的副对角线上。
- 我们也可以从终点倒推回对角线:询问起点是否能够到终点上方的一个点,如果能,将终点上移,否则将终点左移。
- 如果我们从起点走向对角线时优先考虑向右走,从终点走向对角线时优先考虑向上走,那么必然会在副对角线上最靠右上的一个既能够到达终点,起点又能到达的点汇合,拼接两边的路径即可。
- 时间复杂度 ,使用询问次数不超过 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n;
char ans[MAXN];
string res;
int main() {
cin >> n;
int x = 1, y = 1;
for (int i = 1; i <= n - 1; i++) {
cout << '?' << ' ' << x << ' ' << y + 1 << ' ' << n << ' ' << n << endl;
cin >> res;
if (res == "YES") ans[i] = 'R', y++;
else ans[i] = 'D', x++;
}
x = n, y = n;
for (int i = 1; i <= n - 1; i++) {
cout << '?' << ' ' << 1 << ' ' << 1 << ' ' << x - 1 << ' ' << y << endl;
cin >> res;
if (res == "YES") ans[2 * n - 1 - i] = 'D', x--;
else ans[2 * n - 1 - i] = 'R', y--;
}
cout << '!' << ' ' << ans + 1 << endl;
return 0;
}
**【F】**Mobile Phone Network
【思路要点】
- 将所有关键边价格设为 ,作最小生成树,由于题目保证关键边不形成,所有关键边都会出现在生成树上。
- 那么显然一条关键边的价格应当设为跨越它的非树边的价格的最小值,以保证其不被非树边替换,若有任何一条关键边未被非树边跨越,答案为 。
- 实现时将非树边按权值小到大排序,依次将路径上尚未标记的边标记为自身的价格即可,找到下一条尚未标记的边可以使用并查集。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 500005;
const int MAXLOG = 20;
const int MAXM = 1e6 + 5;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct info {int x, y, val; } e[MAXM];
struct edge {int dest, val; };
vector <edge> a[MAXN];
int n, m, k, f[MAXN], depth[MAXN], father[MAXN][MAXLOG], val[MAXN];
bool sel[MAXM], key[MAXN];
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
void col(int pos, int lca, int v) {
pos = F(pos);
while (depth[pos] > depth[lca]) {
val[pos] = v;
f[pos] = F(father[pos][0]);
pos = F(pos);
}
}
void dfs(int pos, int fa) {
father[pos][0] = fa;
depth[pos] = depth[fa] + 1;
for (int i = 1; i < MAXLOG; i++)
father[pos][i] = father[father[pos][i - 1]][i - 1];
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i].dest != fa) {
if (a[pos][i].val == 0) key[a[pos][i].dest] = true;
dfs(a[pos][i].dest, pos);
}
}
int lca(int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
for (int i = MAXLOG - 1; i >= 0; i--)
if (depth[father[x][i]] >= depth[y]) x = father[x][i];
if (x == y) return x;
for (int i = MAXLOG - 1; i >= 0; i--)
if (father[x][i] != father[y][i]) {
x = father[x][i];
y = father[y][i];
}
return father[x][0];
}
bool cmp(info a, info b) {
return a.val < b.val;
}
int main() {
read(n), read(k), read(m);
for (int i = 1; i <= n; i++)
f[i] = i;
int tot = 0;
for (int i = 1; i <= k; i++) {
int x, y; read(x), read(y);
e[++tot] = (info) {x, y, 0};
}
for (int i = 1; i <= m; i++) {
int x, y, z; read(x), read(y), read(z);
e[++tot] = (info) {x, y, z};
}
for (int i = 1; i <= n; i++)
f[i] = i;
sort(e + 1, e + tot + 1, cmp);
for (int i = 1; i <= tot; i++) {
if (F(e[i].x) == F(e[i].y)) continue;
sel[i] = true;
f[F(e[i].x)] = F(e[i].y);
a[e[i].x].push_back((edge) {e[i].y, e[i].val});
a[e[i].y].push_back((edge) {e[i].x, e[i].val});
}
for (int i = 1; i <= n; i++)
f[i] = i;
dfs(1, 0);
for (int i = 1; i <= tot; i++) {
if (sel[i]) continue;
int tmp = lca(e[i].x, e[i].y);
col(e[i].x, tmp, e[i].val);
col(e[i].y, tmp, e[i].val);
}
long long ans = 0;
for (int i = 1; i <= n; i++)
if (key[i]) {
if (val[i] == 0) {
printf("-1\n");
return 0;
}
ans += val[i];
}
writeln(ans);
return 0;
}
**【G】**The Tree
【思路要点】
- 如果两组观察 满足 且 ,那么我们称 。由定义, 符号满足自反性 、反对称性 和传递性 ,因此所有观察以及 符号形成了一个偏序集,由 Dilworth’s theorem ,问题的答案在数值上等于以下问题的答案:至多能够选择的总大小为多少的观察,使得观察间两两不可比(即最大反链的大小)?
- 接下来我们要证明一个判断一组观察是否形成反链的方法:
- 引理:一组观察 形成一个反链,当且仅当以下两点同时成立:
、对于每一个 的子树, 在该子树中的子集是一个反链。
、存在一个(不一定是整数的)时刻 ,使得从任何一个观察 的时间和地点出发都无法在 时刻到达 ,并且也不能在 时刻从 出发到达任何一个观察 的时间和地点(以下认为这是从 出发在时间轴上向后行动)。- 证明:引理的充分性很显然,我们来证明其必要性。从任何一个观察 的时间和地点出发不可以到达 的时刻的全集是一个开区间 ,两个观察 可比的充分条件为 ,即若 形成一个反链,所有 的开区间 的并非空,因此引理的必要性得证。
- 注意 不一定是整数,但若 存在, 一定可以是 的整数倍,因此我们可以将输入的边长和日期乘以 ,并假设 是整数。
- 接下来就可以得出一个多项式做法了:
- 我们通过树形 来解决这个问题:记 表示在 的子树中,所选的所有观察无法在 时刻到达 的情况下 最大的大小。
- 若点 处没有观察,那么我们将点 处的 值转移至其父亲处较为容易,假设 的父边长为 ,有 ,我们称这个转移为 。
- 若点 处有观察 ,转移时我们不能直接令 ,因为此时观察 能够在 时刻到达点 ,正确的将点 处的 值转移至其父亲处的过程如下:
、
、
、- 最后,在父亲处把所有子树转移上来的答案按位相加即可。
- 由此,我们有了一个 的做法。
- 我们需要用数据结构来优化这个做法。
- 注意到 中不同的数值不会超过 子树中不同的观察的个数,我们不妨令 ,将 的 用一个数据结构维护起来。
- 那么,对于子树的按位加和操作,实际上就是将对应的数据结构合并,用启发式合并的方式可以使得插入次数在 级别。
对于 操作,所有 会变成 ,所有 会变成 ,并且若两个位置发生交叉,即 ,若 ,则会留下 ,否则会留下 。- 用 按照 的大小顺序维护 ,并再用一个 记录相邻的两个位置发生交叉的时间,进行 操作时,用一个全局的懒标记的增加来避免对整个集合进行更改,在发生交叉时再对集合进行更改。
- 还有一个小问题就是在进行 时我们实际上只需要求出 进行操作 即可,并不需要将 和 的具体值计算出来。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct Pair {int x, y; };
int n, m, curr, depth[MAXN];
vector <Pair> a[MAXN], b[MAXN];
struct Info {int pos, type, val; };
bool operator < (Info a, Info b) {
int apos = a.pos + a.type * curr;
int bpos = b.pos + b.type * curr;
if (apos == bpos) return a.type < b.type;
else return apos < bpos;
}
struct Crash {
Info a, b;
int when;
void calc() {
int apos = a.pos + a.type * curr;
int bpos = b.pos + b.type * curr;
when = curr - (bpos - apos + 1) / 2;
}
};
Crash make(Info a, Info b) {
Crash ans = (Crash) {a, b};
ans.calc();
return ans;
}
bool operator < (Crash a, Crash b) {
if (a.when == b.when) return make_pair(a.a, a.b) < make_pair(b.a, b.b);
else return a.when > b.when;
}
struct Container {
set <Info> Val;
set <Crash> Del;
void EraseCrash(Info a, Info b) {
if (a.type == -1 && b.type == 1)
Del.erase(make(a, b));
}
void InsertCrash(Info a, Info b) {
if (a.type == -1 && b.type == 1)
Del.insert(make(a, b));
}
void erase(Info a) {
set <Info> :: iterator pos = Val.find(a), suf = pos; suf++;
if (pos != Val.begin()) {
set <Info> :: iterator pre = pos; pre--;
EraseCrash(*pre, *pos);
if (suf != Val.end()) InsertCrash(*pre, *suf);
}
if (suf != Val.end()) EraseCrash(*pos, *suf);
Val.erase(pos);
}
void insert(Info a) {
if (a.val == 0) return;
if (Val.find(a) != Val.end()) {
a.val += (*Val.find(a)).val;
erase(*Val.find(a));
}
if (a.val == 0) return;
set <Info> :: iterator pos = Val.insert(a).first, suf = pos; suf++;
if (pos != Val.begin()) {
set <Info> :: iterator pre = pos; pre--;
InsertCrash(*pre, *pos);
if (suf != Val.end()) EraseCrash(*pre, *suf);
}
if (suf != Val.end()) InsertCrash(*pos, *suf);
}
void process(Crash a) {
erase(a.a), erase(a.b);
int tmp = a.a.val + a.b.val;
if (tmp == 0) return;
if (tmp > 0) a.b.val = tmp, insert(a.b);
else a.a.val = tmp, insert(a.a);
}
void maintain(int timer) {
while (!Del.empty() && timer <= (*Del.begin()).when)
process(*Del.begin());
curr = timer;
}
void debug(int pos, int timer) {
maintain(timer);
cerr << "Debugging In Progress. At Point " << pos << " Current Time: " << timer << endl;
set <Info> :: iterator i;
cerr << "Set Val: " << endl;
for (i = Val.begin(); i != Val.end(); i++)
cerr << (*i).pos << '(' << (*i).pos + curr * (*i).type << ')' << ' ' << (*i).type << ' ' << (*i).val << endl;
set <Crash> :: iterator j;
cerr << "Del Val: " << endl;
for (j = Del.begin(); j != Del.end(); j++) {
cerr << (*j).a.pos << '(' << (*j).a.pos + curr * (*j).a.type << ')' << ' ' << (*j).a.type << ' ' << (*j).a.val << ' ';
cerr << (*j).b.pos << '(' << (*j).b.pos + curr * (*j).b.type << ')' << ' ' << (*j).b.type << ' ' << (*j).b.val << ' ';
cerr << "Collide Time: " << (*j).when << endl;
}
}
void join(Container &a, int timer) {
maintain(timer);
if (a.Val.size() > Val.size()) {
swap(Val, a.Val);
swap(Del, a.Del);
}
set <Info> :: iterator i;
for (i = a.Val.begin(); i != a.Val.end(); i++)
insert(*i);
}
int delta(int pos) {
Info tmp = (Info) {pos + curr, -1, 0};
Info tnp = (Info) {pos + 1 - curr, 1, 0};
int vtmp = 0, vtnp = 0;
if (Val.find(tmp) != Val.end()) vtmp = -(*Val.find(tmp)).val;
if (Val.find(tnp) != Val.end()) vtnp = (*Val.find(tnp)).val;
return max(vtmp, vtnp);
}
void modify(vector <Pair> &a, int from, int to) {
maintain(from);
//debug(0, curr);
for (unsigned i = 0; i < a.size(); i++) {
int tmp = delta(a[i].x);
a[i].y = max(0, a[i].y - tmp);
}
maintain(from - 1);
//debug(0, curr);
for (unsigned i = 0; i < a.size(); i++) {
insert((Info) {a[i].x - curr, 1, a[i].y});
insert((Info) {a[i].x + 1 + curr, -1, -a[i].y});
}
//debug(0, curr);
maintain(to);
//debug(0, curr);
}
int calcans() {
int ans = 0, now = 0;
set <Info> :: iterator i;
for (i = Val.begin(); i != Val.end(); i++) {
now += (*i).val;
chkmax(ans, now);
}
return ans;
}
} dp[MAXN];
void dfs(int pos, int fa) {
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i].x != fa) {
depth[a[pos][i].x] = depth[pos] + a[pos][i].y;
dfs(a[pos][i].x, pos);
}
}
void work(int pos, int fa, int last) {
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i].x != fa) {
int tmp = a[pos][i].x;
work(tmp, pos, depth[pos]);
dp[pos].join(dp[a[pos][i].x], depth[pos]);
}
dp[pos].modify(b[pos], depth[pos], last);
//dp[pos].debug(pos, last);
}
int main() {
read(n);
for (int i = 1; i <= n - 1; i++) {
int x, y, z;
read(x), read(y), read(z);
a[x].push_back((Pair) {y, z * 2});
a[y].push_back((Pair) {x, z * 2});
}
read(m);
for (int i = 1; i <= m; i++) {
int d, f, p;
read(d), read(f), read(p);
b[p].push_back((Pair) {d * 2, f});
}
dfs(1, 0);
work(1, 0, -2);
writeln(dp[1].calcans());
return 0;
}