Codeforces Round #620 (Div. 2)
题目链接:https://codeforces.com/contest/1304
A - Two Rabbits
签到
B - Longest Palindrome
题意:有n个长度都是m的字符串,选其中一些字符串出来,组成最长的回文串。
题解:由于长度都是m,所以“能够组合之后连接成为回文串的一部分”的串,本身可以分成几次添加到回文串的两端。(意思是"ab"+"c"="cb"+"a"这种情况不会出现),那么尝试先选一个出现次数为奇数的回文串放在中间,其他的假如反串也出现了,那么就和反串一起加在两边。
实现的时候有好多蠢东西啊,实际上直接插进map里面统计,然后遍历map找本身是回文串的,再看看second是不是奇数,是的话取出一个并break,然后再遍历一次map构造。
反思:???看评论区才发现原来所有的字符串是不同的?
C - Air Conditioner
题意:餐厅有n个客人预定,初始温度是m。空调有三种档位:每分钟+1、保持不变,每分钟-1。某个客人会满意,当且仅当在ti时刻他到达的瞬间,温度范围在[li,ri]内。求是否存在一种调温方法,使得所有客人都满意。
读题不太仔细。一开始看见n<=100,没看到输入,以为是要使得满意的客人尽可能多,以为是什么奇奇怪怪的dp,但是温度范围又太大,设什么dp[i][j]为前i个客人中使得j个满意,结束时的温度范围,感觉会是在数轴上一些线段的并,还要考虑随着时间流逝每段线段都会扩张。没看见输入是非降序的,还给它排序。总之可能是写完B题去吃饭了导致正常掉蓝。
题解:考虑当前的最后一个客人到来的时候,要使得他满意,最后的范围受[li,ri]限制,同时也受前一个客人结束时的温度的合法取值范围限制,注意到由于要使得所有客人的满意,所以合法取值范围是数轴上这些线段的交,而且每个线段会每分钟往左右延伸1,所以他们的交也是每分钟往左右延伸1,观察到这点之后就会知道上一个客人结束时温度的合法取值范围也是一条线段。注意还受时间变化的限制。
int n, m;
int t[105], l[105], h[105];
void test_case() {
scanf("%d%d", &n, &m);
int L = m, H = m;
for(int i = 1; i <= n; ++i)
scanf("%d%d%d", &t[i], &l[i], &h[i]);
for(int i = 1; i <= n; ++i) {
int dt = t[i] - t[i - 1];
if(L - dt > h[i] || H + dt < l[i]) {
puts("NO");
return;
}
L = max(l[i], L - dt);
H = min(h[i], H + dt);
}
puts("YES");
return;
}
*D - Shortest and Longest LIS
题意:给一个大于小于号的序列,先填一个[1,n]的排列,使得这个排列符合这个序列,并且LIS(最长上升子序列)最短;再填一个[1,n]的排列,使得这个排列符合这个序列,并且LIS(最长上升子序列)最长。
题解:首先,最短的LIS的长度不可能小于这个序列本身存在的连续上升区间。要得到最短的LIS,先把原本的大于小于号序列分解为若干个连续上升区间,然后从最右侧的连续上升区间开始逐个向左构造,这样左边的每个连续上升区间的每个值都会比右边的连续上升区间的每个值要大,比如:
789456123
这样构造下,LIS的长度恰好就是最长的那个连续上升区间的长度。
然后,把这个序列划分为若干个连续下降区间,非常显然属于同一个区间的元素不能出现在同一个上升子序列中,所以最长的LIS的长度不能超过划分出的连续下降区间的数量。要得到最长的LIS,一种直观的想法是把小的元素尽可能放在左边,把大的元素尽可能放在右边,这样左边的每个连续下降区间的每个值都会比右边的连续上升区间的每个值要小,比如:
321654987
这样构造下,LIS的长度恰好就是连续下降区间的数量。
昨天想的一种诡异的构造,是每个连续下降区间轮流取最小的数,当时用了很多奇怪的数据结构写,其实维护“轮流”这个性质的,还是队列最简单,把每个区间都放进队列里面,然后每次取出队首放一个元素,假如区间还没放完,就插入队尾。
int n;
char s[200005];
int top;
int L[200005], R[200005];
int cur;
int ans1[200005], ans2[200005];
void test_case() {
scanf("%d%s", &n, s + 1);
top = 1;
L[top] = 1;
R[top] = 1;
for(int i = 1; i <= n - 1; ++i) {
if(s[i] == '>') {
++top;
L[top] = i + 1;
R[top] = i + 1;
continue;
} else {
R[top] = i + 1;
continue;
}
}
/*for(int t = 1; t <= top; ++t)
printf("[%d,%d]\n", L[t], R[t]);*/
cur = 1;
for(int t = top; t >= 1; --t) {
for(int i = L[t]; i <= R[t]; ++i) {
ans1[i] = cur;
++cur;
}
}
top = 1;
L[top] = 1;
R[top] = 1;
for(int i = 1; i <= n - 1; ++i) {
if(s[i] == '>') {
R[top] = i + 1;
continue;
} else {
++top;
L[top] = i + 1;
R[top] = i + 1;
continue;
}
}
/*for(int t = 1; t <= top; ++t)
printf("[%d,%d]\n", L[t], R[t]);*/
cur = n;
for(int t = top; t >= 1; --t) {
for(int i = L[t]; i <= R[t]; ++i) {
ans2[i] = cur;
--cur;
}
}
for(int i = 1; i <= n; ++i)
printf("%d%c", ans1[i], " \n"[i == n]);
for(int i = 1; i <= n; ++i)
printf("%d%c", ans2[i], " \n"[i == n]);
}
Dilworth定理(的对偶定理)说的是最长上升子序列的长度=尽可能长的不上升子序列的划分数量(最长链长度=最少的反链划分),好像没什么关系。上面的解法既给出了上下界,同时给出了能到达上下界的一种构造。
*E - 1-Trees and Queries
题意:给一棵无权树,若干次询问,每次询问一组(x,y,a,b,k),问是否存在一条a到b的(可以往回走的)路径,长度恰好为k,在x与y之间连一条边的条件下。
题解:可以往回走的路径,所以是k,k-2,k-4,...这些长度都是可以的,在a直接走到b然后来回走就可以了。首先,假如在原本的树上,dis(a,b)就是不超过k的一个与k奇偶性相同的值,那么直接YES,否则考虑走x与y之间的新边,那么dis(a,x)+1+dis(y,b)或者dis(a,y)+1+dis(x,b)其中至少一个是不超过k的一个与k奇偶性相同的值,也是直接YES。前面的情况是直接走的,想尽可能快到达终点。最后一种情况应该是通过绕一个环来尝试改变奇偶性,假如x与y连接之后是一个偶数长度的环,那么这种情况就没有任何意义了,否则是奇数长度的环,那么可以相当于站在x或者站在y直接浪费掉这个环的长度。显然环的长度就是dis(x,y)+1,所以这种情况也就是dis(a,x)+dis(x,y)+1+dis(x,b)和dis(a,y)+dis(y,x)+1+dis(y,b)其中至少一个是不超过k的一个与k奇偶性相同的值,继续输出YES。
否则输出NO。
const int MAXN = 100000;
vector <int> G[MAXN + 5];
int dep[MAXN + 5], fa[MAXN + 5][20 + 1];
void dfs(int u, int p) {
dep[u] = dep[p] + 1;
fa[u][0] = p;
for (int i = 1; i <= 20; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(auto &v : G[u]) {
if(v == p)
continue;
dfs(v, u);
}
}
int LCA(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int i = 20; i >= 0; i--)
if (dep[x] - (1 << i) >= dep[y])
x = fa[x][i];
if (x == y)
return x;
for (int i = 20; i >= 0; i--)
if (fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int dis(int x, int y) {
return dep[x] + dep[y] - 2 * dep[LCA(x, y)];
}
int k;
bool check(int x) {
if(x <= k && (k - x) % 2 == 0)
return true;
return false;
}
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
int q;
scanf("%d", &q);
while(q--) {
int x, y, a, b;
scanf("%d%d%d%d%d", &x, &y, &a, &b, &k);
if(check(dis(a, b))) {
puts("YES");
continue;
}
int dxa = dis(x, a);
int dyb = dis(y, b);
if(check(dxa + 1 + dyb)) {
puts("YES");
continue;
}
int dxb = dis(x, b);
int dya = dis(y, a);
if(check(dxb + 1 + dya)) {
puts("YES");
continue;
}
int dxy = dis(x, y);
if(check(dxa + dxy + 1 + dxb) || check(dya + dxy + 1 + dyb)) {
puts("YES");
continue;
}
puts("NO");
continue;
}
}
反思:其实最后一种情况“通过绕一个环来改变奇偶性”,被“使用新增的xy边”这种情况包含。