Codeforces Round #605 (Div. 3)
https://codeforces.com/contest/1272/
A - Three Friends
题意:给三个点,每个点至多移动一格,求三个点两两之间的距离之和的最小值。
题解:排序之后中间那个点动不动无所谓,左右的点向中间靠近可以减少线段的程度至多2,长度最短减少到0。显然答案就是线段长度的两倍。
void test_case() {
int a[4];
scanf("%d%d%d", &a[1], &a[2], &a[3]);
sort(a + 1, a + 1 + 3);
int maxd = a[3] - a[1];
if(maxd <= 2)
maxd = 0;
else {
maxd -= 2;
maxd *= 2;
}
printf("%d\n", maxd);
}
B - Snow Walking Robot
题意:给一个图,从原点开始上下左右走,走的轨迹除了原点之外不能有任何一个交点,且原点只能经过两次。给一个序列,你可以重排其中的走法,假如不行则先删除尽可能少的再重排。
题解:假如有上下左右至少各一个,那么走一个矩形。否则若只有上下或者只有左右那么就走一步就回来。
注意字符串的开始是从1开始,还调半天,真是演。
char s[100005];
void test_case() {
scanf("%s", s + 1);
int cntu = 0, cntl = 0, cntd = 0, cntr = 0;
int n = strlen(s + 1);
for(int i = 1; i <= n; ++i) {
if(s[i] == 'U')
++cntu;
else if(s[i] == 'D')
++cntd;
else if(s[i] == 'L')
++cntl;
else
++cntr;
}
cntl = min(cntl, cntr);
cntr = cntl;
cntu = min(cntu, cntd);
cntd = cntu;
if(cntr == 0) {
if(cntd) {
puts("2");
puts("UD");
return;
} else {
puts("0");
puts("");
return;
}
} else if(cntd == 0) {
puts("2");
puts("LR");
return;
}
int n2 = cntl + cntr + cntu + cntd;
printf("%d\n", n2);
while(cntu--)
putchar('U');
while(cntr--)
putchar('R');
while(cntd--)
putchar('D');
while(cntl--)
putchar('L');
putchar('\n');
return;
}
C - Yet Another Broken Keyboard
题意:给一个串,和一堆字符,求只由这些字符构成的子串的数量。
题解:随便组合一下。
char s[200005];
bool vis[128];
void test_case() {
int n, k;
scanf("%d%d%s", &n, &k, s + 1);
for(int i = 1; i <= k; ++i) {
char c[2];
scanf("%s", c);
vis[c[0]] = 1;
}
ll ans = 0, tmp = 0;
for(int i = 1; i <= n; ++i) {
if(vis[s[i]])
++tmp;
else {
ans += tmp * (tmp + 1) / 2;
tmp = 0;
}
}
ans += tmp * (tmp + 1) / 2;
tmp = 0;
printf("%lld\n", ans);
}
D - Remove One Element
题意:从一个序列中删除至多一个元素,求生成的新序列中的最长严格上升子串的长度。
题解:比较恶心的一个题,一开始考虑dp,但其实有更简单的做法:划分出每段严格上升的段,然后只有当段的开头或者段的结尾被删除时有可能会连接前后两个不同的段。注意假如一个段只有一个元素时的情况。
int a[200005], fi[200005], se[200005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int top = 1;
fi[1] = 1, se[1] = 1;
for(int i = 1; i <= n; ++i) {
if(a[i] > a[i - 1])
se[top] = i;
else {
fi[++top] = i;
se[top] = i;
}
}
int ans = se[1] - fi[1] + 1;
for(int i = 2; i <= top; ++i) {
ans = max(ans, se[i] - fi[i] + 1);
if(se[i] == fi[i] && i + 1 <= top) {
if(a[fi[i + 1]] > a[se[i - 1]])
ans = max(ans, se[i - 1] - fi[i - 1] + 1 + se[i + 1] - fi[i + 1] + 1);
} else {
if(a[se[i - 1] - 1] < a[fi[i]])
ans = max(ans, se[i - 1] - fi[i - 1] + 1 + se[i] - fi[i] + 1 - 1);
else if(a[se[i - 1]] < a[fi[i] + 1])
ans = max(ans, se[i - 1] - fi[i - 1] + 1 + se[i] - fi[i] + 1 - 1);
}
}
printf("%d\n", ans);
}
假如是dp的话,需要考虑的是前面有没有元素被删除过。但是同时也受末尾的状态影响。设dp[0][i]表示不进行删除时的以i为结尾的最长连续上升子串的长度,dp[1][i]为删除了i的,dp[2][i]为已经使用了删除机会,且删除的不是i的。
int a[200005], dp[3][200005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int ans = 0;
for(int i = 1; i <= n; ++i) {
dp[0][i] = a[i] > a[i - 1] ? dp[0][i - 1] + 1 : 1;
dp[1][i] = dp[0][i - 1];
dp[2][i] = max(a[i] > a[i - 1] ? dp[2][i - 1] + 1 : 1, i >= 2 ? (a[i] > a[i - 2] ? dp[1][i - 1] + 1 : 1) : 1);
ans = max(ans, dp[0][i]);
ans = max(ans, dp[2][i]);
}
printf("%d\n", ans);
}
E - Nearest Opposite Parity
题意:给一个数组,ai表示你在i位置时可以向左或向右跳ai个格子,越界非法。对于每个i位置求出它走到最近的不同奇偶性的格子的步数。
题解:的确先反向建图,然后就可以从一个格子搜索哪些格子可以到它了。把可以从不同奇偶性的1步转移过来的格子作为初始格,显然初始格的步数是1,且后面不会再被更新。把所有的初始格标记好了之后,剩下的能够做到初始格的点则是距离为2的点,证明:初始格A->初始格的终点B距离为1,且A与B的奇偶性不同。而C可以一步到A,但是C却不是初始格,则C与奇偶性相同。推论:除去初始格之外,剩下的格子一步走到的点都是和自己奇偶性相同的。所以直接多源bfs求解。
int n, a[MAXN + 5];
int dis[MAXN + 5];
vector<int> G[MAXN + 5];
queue<int> Q;
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
int x = i + a[i];
if(x <= n)
G[x].push_back(i);
x = i - a[i];
if(x >= 1)
G[x].push_back(i);
dis[i] = -1;
}
for(int i = 1; i <= n; ++i) {
int x = i + a[i];
if(x <= n && (a[i] ^ a[x]) & 1)
dis[i] = 1;
x = i - a[i];
if(x >= 1 && (a[i] ^ a[x]) & 1)
dis[i] = 1;
if(dis[i] == 1)
Q.push(i);
}
while(!Q.empty()) {
int u = Q.front();
Q.pop();
for(auto &v : G[u]) {
if(dis[v] == -1) {
dis[v] = dis[u] + 1;
Q.push(v);
}
}
}
for(int i = 1; i <= n; ++i)
printf("%d%c", dis[i], " \n"[i == n]);
}