Codeforces Round #442 Div.2 A B C D E
A. Alex and broken contest
题意
判断一个字符串内出现五个给定的子串多少次。
Code
#include <bits/stdc++.h>
char s[110];
using namespace std;
typedef long long LL;
char* rec[5] = {"Danil", "Olya", "Slava", "Ann", "Nikita"};
int len[5] = {5,4,5,3,6};
bool cmp(char* s1, char* s2, int len) {
for (int i = 0; i < len; ++i) {
if (s1[i] != s2[i]) return false;
}
return true;
}
int main() {
int cnt=0;
scanf("%s", s);
for (int i = 0; s[i]; ++i) {
for (int j = 0; j < 5; ++j) {
if (cmp(rec[j], s+i, len[j])) ++cnt;
}
if (cnt > 2) {printf("NO\n"); return 0;}
}
if (cnt==1) printf("YES\n");
else printf("NO\n");
return 0;
}
B. Nikita and string
题意
给定一个长为\(n(n\leq 5000)\)的只含\('a',b'\)的字符串,先要求删去最少数目的字符,使得留下的字符串可以被分为三截,最左边全\('a'\),中间一段全\('b'\),最右边全\('a'\).
思路
\(O(n^2)\)
预处理前缀和。
枚举两个分界点。
时间复杂度\(O(n^2)\).
\(O(n)\)
\(dp\)做法
参见http://codeforces.com/contest/877/submission/31663633
Code
#include <bits/stdc++.h>
#define maxn 5010
char s[maxn];
int a[maxn], b[maxn];
using namespace std;
typedef long long LL;
int main() {
scanf("%s", s+1);
int len = strlen(s+1);
for (int i = 1; i <= len; ++i) {
a[i] = a[i-1], b[i] = b[i-1];
if (s[i]=='a') ++a[i];
else ++b[i];
}
int ans = len+1;
for (int i = 1; i <= len+1; ++i) {
for (int j = i; j <= len+1; ++j) {
int x1=0, x2=0, x3=0;
if (i > 1) x1 = b[i-1]-b[0];
if (j > i) x2 = a[j-1]-a[i-1];
if (j <= len) x3 = b[len]-b[j-1];
ans = min(ans,x1+x2+x3);
}
}
printf("%d\n", len-ans);
return 0;
}
C. Slava and tanks
题意
有\(1\times n\)的格子,里面不知道哪些位置会有坦克。每架坦克的血量为\(2\). 在位置\(i\)处扔炸弹,该位置的坦克受到一点伤害,并且跑到相邻的格子中去;再受到一次伤害,就死了。问最少扔多少次炸弹能保证消灭所有的坦克,并且输出扔炸弹的序列。
思路
仆の思路
左右两块:两格两格一组地扔;中间:根据%4的值分类讨论。
有点难描述,直接看代码吧Orz
题解
先往所有偶数格子中扔,再往所有奇数格子中扔,最后往所有偶数格子中扔。
证明
1. 可行性:
-
先往偶数格子中扔炸弹时,偶数格子中的所有坦克都跑到了奇数格子中;
此时,只有奇数格子中有坦克,且其中的坦克分为两类:血量为\(1\),血量为\(2\) -
再往奇数格子中扔炸弹,奇数格子中 1) 血量为\(1\)的坦克全都死了; 2) 血量为\(2\)的坦克都跑到了偶数格子中;
此时,只有偶数格子中有坦克,且其中的坦克只有一类:血量为\(1\) -
最后往偶数格子中扔坦克,全都死了
至于为什么是\(偶\rightarrow 奇\rightarrow 偶\)的顺序而不是\(奇\rightarrow 偶\rightarrow 奇\)的顺序,是因为奇数格子数目\(\geq\)偶数格子数目,所以前者是\(n+n/2\)而后者在\(n\)为奇数时是\(n+n/2+1\).
2. 最优性:
参考题解页面\(ludo\)用户的评论
"
The problem is equivalent to finding a string S such that for all 1 < i ≤ n, i - 1 precedes i somewhere in the string and i precedes i - 1 somewhere in the string.
Now observe that for some i, it occurs either 1 or 2 times in the optimal string. If it is more than 2 times, we can erase the middle ones and obtain a better string.
If for some i, the number of times is 1, then i + 1 must occur (at least) 2 times: to the left of this location and to the right. If i occurs 2 times, then i + 1 must occur (at least) once (in between the two occurences).
From this, you can build the string 2,4,6,... 1,3,5,..., 2,4,6, ... which is therefore optimal.
At every step we get some partial string with all the numbers from 1 to i. Inductively you can show that this is optimal at every transition to i + 1.
"
意思就是说,这个问题等价于找一个串,对于\(1\lt i\leq n\),都有\(i-1\)既在\(i\)之前出现过,也在\(i\)之后出现过。然后在这个转化成的新问题上进行构造。具体见上,就不再翻译了。
转化的依据:当我们轰炸\(i\)位置时,最糟糕的情况是第\(i-1\)个位置和第\(i+1\)个位置上都有坦克。因此,构造出的串中既需要存在\(i,i-1\)这个子串,也需要存在\(i,i+1\)这个子串。换句话说,就是\(i-1\)既在\(i\)之前出现过,也在\(i\)之后出现过。
Code
Ver. 1
#include <bits/stdc++.h>
#define maxn 200010
int ans[maxn];
using namespace std;
typedef long long LL;
int main() {
int n;
scanf("%d", &n);
int tot = 0;
printf("%d\n", n+n/2);
for (int i = 1; i <= n/4; ++i) {
ans[tot++] = i*2;
ans[tot++] = i*2-1;
}
for (int i = 1; i <= n/4; ++i) {
ans[tot++] = n-(i*2)+1;
ans[tot++] = n-(i*2)+2;
}
if (n%4==1) {
ans[tot++] = n/2+1;
for (int i = 2; i < n; ++i) if (!(i&1)) ans[tot++] = i;
}
else if (n%4==3) {
ans[tot++] = n/2+1, ans[tot++] = n/2, ans[tot++] = n/2+2;
for (int i = 2; i < n; ++i) if (!(i&1)) ans[tot++] = i;
}
else if (n%4==2) {
ans[tot++] = n/2; ans[tot++] = n/2+1, ans[tot++] = n/2;
for (int i = 1; i <= n/4; ++i) ans[tot++] = 2*i, ans[tot++] = n+1-2*i;
}
else {
for (int i = 1; i <= n/4; ++i) ans[tot++] = 2 * i, ans[tot++] = n+1-2*i;
}
printf("%d", ans[0]);
for (int i = 1; i < tot; ++i) printf(" %d", ans[i]); printf("\n");
return 0;
}
Ver. 2
#include <bits/stdc++.h>
#define maxn 200010
int ans[maxn];
using namespace std;
typedef long long LL;
int main() {
int n;
scanf("%d", &n);
printf("%d\n", n+n/2);
int tot = 0;
for (int i = 1; i <= n; ++i) if (!(i&1)) ans[tot++]= i;
for (int i = 1; i <= n; ++i) if (i&1) ans[tot++] = i;
for (int i = 1; i <= n; ++i) if (!(i&1)) ans[tot++]= i;
printf("%d", ans[0]);
for (int i = 1; i < tot; ++i) printf(" %d", ans[i]); printf("\n");
return 0;
}
D. Olya and Energy Drinks
题意
给定一张地图,有些地方有障碍物。给定起始点与终止点,可以向上下左右四个方向走。每秒可至多走\(k\)个单位。问最少花多少时间可以从起点到终点。
思路
裸的\(bfs\)
Code
#include <bits/stdc++.h>
#define maxn 1010
struct node { int x, y; };
int dr[4][2] = {{0,1}, {0,-1}, {1,0}, {-1,0}};
char s[maxn];
using namespace std;
typedef long long LL;
bool mp[maxn][maxn];
int v[maxn][maxn];
int main() {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i) {
scanf("%s", s+1);
for (int j = 1; j <= m; ++j) {
if (s[j] == '.') mp[i][j] = 0;
else mp[i][j] = 1;
}
}
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
if (x1==x2&&y1==y2) { puts("0"); exit(0); }
queue<node> q;
while (!q.empty()) q.pop();
q.push({x1,y1});
while (!q.empty()) {
node nd = q.front(); q.pop();
for (int i = 0; i < 4; ++i) {
node temp = nd;
int step = 0;
while (step < k) {
++step;
temp.x += dr[i][0], temp.y += dr[i][1];
if (temp.x <= 0 || temp.x > n || temp.y <= 0 || temp.y > m || mp[temp.x][temp.y]) break;
if (temp.x == x2 && temp.y == y2) { printf("%d\n", v[nd.x][nd.y]+1); exit(0); }
if (!(temp.x==x1&&temp.y==y1) && !v[temp.x][temp.y]) v[temp.x][temp.y] = v[nd.x][nd.y] + 1, q.push({temp.x, temp.y});
}
}
}
printf("-1\n");
return 0;
}
E. Danil and a Part-time Job
题意
给定一棵有根树,根为\(1\),每个点的值是\(0\)或\(1\). 有两种操作,
- \(get\ v\):询问以\(v\)为根的子树中有多少个点的值为\(1\);
- \(pow\ v\):将以\(v\)为根的子树中所有点的值反转;
思路
\(dfs序+线段树\)
可以说是很裸的题了...
不要脸地来为我原博客里dfs序的文章打下广告_(:з」∠)_ http://blog.csdn.net/kkkkahlua/article/category/7026724
Code
#include <bits/stdc++.h>
#define maxn 200010
#define lson (rt<<1)
#define rson (rt<<1|1)
struct node { int l, r, c, tag; }tr[maxn * 4];
struct Edge { int to, ne; }edge[maxn];
int c[maxn], l[maxn], r[maxn], cnt, tot, ne[maxn];
using namespace std;
typedef long long LL;
void add(int u, int v) {
edge[tot] = {v, ne[u]};
ne[u] = tot++;
}
void dfs(int u) {
l[u] = ++cnt;
for (int i = ne[u]; ~i; i = edge[i].ne) dfs(edge[i].to);
r[u] = cnt;
}
void push_up(int rt) { tr[rt].c = tr[lson].c + tr[rson].c; }
void build(int rt, int l, int r) {
tr[rt].l = l, tr[rt].r = r, tr[rt].tag = 0;
if (l==r) { tr[rt].c = c[l]; return; }
int mid = l+r >> 1;
build(lson, l, mid), build(rson, mid+1, r);
push_up(rt);
}
void push_down(int rt) {
if (tr[rt].tag) {
tr[lson].c = tr[lson].r-tr[lson].l+1-tr[lson].c;
tr[rson].c = tr[rson].r-tr[rson].l+1-tr[rson].c;
tr[lson].tag ^= 1, tr[rson].tag ^= 1;
tr[rt].tag = 0;
}
}
int query(int rt, int l, int r) {
if (tr[rt].l==l && tr[rt].r==r) return tr[rt].c;
push_down(rt);
int mid = tr[rt].l + tr[rt].r >> 1;
if (r <= mid) return query(lson, l, r);
else if (l > mid) return query(rson, l, r);
else return query(lson, l, mid) + query(rson, mid+1, r);
}
void modify(int rt, int l, int r) {
if (tr[rt].l==l && tr[rt].r==r) {
tr[rt].c = tr[rt].r-tr[rt].l+1-tr[rt].c, tr[rt].tag ^= 1;
return;
}
push_down(rt);
int mid = tr[rt].l+tr[rt].r>>1;
if (r<=mid) modify(lson, l, r);
else if (l > mid) modify(rson, l, r);
else modify(lson, l, mid), modify(rson, mid+1, r);
push_up(rt);
}
int main() {
int n, x;
scanf("%d", &n);
memset(ne, -1, sizeof(ne));
for (int i = 2; i <= n; ++i) {
scanf("%d", &x);
add(x, i);
}
dfs(1);
for (int i = 1; i <= n; ++i) {
scanf("%d", &x);
c[l[i]] = x;
}
build(1, 1, n);
int m;
scanf("%d", &m);
char s[5];
while (m--) {
scanf("%s%d", s, &x);
if (s[0]=='g') printf("%d\n", query(1,l[x], r[x]));
else modify(1,l[x], r[x]);
}
return 0;
}
小结
\(D\)和\(E\)这么水...是不是应该以后先看题啊Orz
昨天大半夜构造\(C\)构造了一个多小时...中间还睡着了...还是构造题做得太少。