2017-2018 ACM-ICPC, NEERC, Southern Subregional Contest A E F G H I K M
// 深夜补水题,清早(雾)写水文
A. Automatic Door
题意
\(n(n\leq 1e9)\)个\(employee\)和\(m(m\leq 1e5)\)个\(client\)要进门,\(employee\)进门的时刻为\(a,2a,...,.na\),\(client\)进门的时间则由输入数据给定。
这个门很厉害,是个自动门。如果第\(k\)时刻有人要进门,那么它会在第\(k\)时刻打开,在第\(k+d\)时刻再关闭,在\([k,k+d]\)时刻要进门的人都能在这段期间进门。
问门会打开多少次。
思路
因为\(n\)太大了,所以显然不可能将\(n+m\)个数存下来再扫描,况且\(employee\)进门的时间还是有规律的。
所以可以考虑根据每个\(client\)进门的时间去算前一段时间内为了满足\(employee\)以及当前这个\(client\)进门的需求开了多少次门,以及这个门一直开到了什么时候。
维护当前门可以持续开到的时间\(last\)搞一搞即可。
假设上次处理到的时间为\(last\),当前\(client\)要进门的时间为\(t\).
如果\(last\geq t\),那么直接continue;
若存在\(k\)满足\(last\lt k_{min}a\lt ...\lt k_{max}a\leq t\),说明中间有\([min,max]个employee\)要进门。又根据\(a\)与\(d\)的关系知门每开一次能让\(k=d/a+1\)个\(employee\)进门,再讨论一下为最后一个\(employee\)开的门能否让当前的\(client\)也进门即可。这样就求出了这一整段的开门次数以及维护了当前的达到的\(last\)时间。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
LL n, m, a, d;
scanf("%I64d%I64d%I64d%I64d", &n, &m, &a, &d);
LL k = d/a+1, ans = 0, last = 0;
for (int i = 0; i < m; ++i) {
LL t;
scanf("%I64d", &t);
if (t <= last) continue;
if (t < a) { last = t + d, ++ans; continue; }
LL kmin = last/a+1, kmax = t/a;
if (kmin <= kmax && kmin <= n) {
kmax = min(kmax, n);
LL delk = kmax-kmin+1;
LL cnt = ceil(1.0*delk / k)-1;
ans += cnt;
kmin += cnt*k;
if (kmin*a+d >= t) last = kmin*a+d, ++ans;
else last = t+d, ans += 2;
}
else last = t + d, ++ans;
}
LL b = last / a + 1;
if (b <= n) ans += ceil(1.0*(n-b+1) / k);
printf("%I64d\n", ans);
return 0;
}
E. Field of Wonders
题意
规定游戏规则为:有一个未知字符串,玩家操作若干次,每次操作猜一个字母,如果这个字符串里面有这个字母,那么所有位置的该字符都会显示出来。
现在给定一个游戏进行到中途某一阶段时该字符串变成的样子(数据保证至少还有一个位置的字符没有被猜出来),再给定一个字符串的集合。问下一步玩家有多少种猜的选择,使得保证能使至少一个位置的字符再多显示出来。
思路
题意即为,问有多少个 还没猜过的 字符 在 所有与当前局面匹配的字符串中 全部出现过。
直接统计即可。
注意
判断与当前局面匹配的字符串时,需要进行两个判断:
首先,很显然的,要求猜出来的显示出来的(即非*
处对应的)字符是一样的。
其次,因为游戏规定,一旦猜中某个字符那么所有位置的该字符都会显示出来,所以未猜出来的(*
处对应的)字符绝不可能与之前猜出来的(非*
处对应的)字符相同。
// 感叹一句样例真心良心
Code
#include <bits/stdc++.h>
#define maxn 1010
using namespace std;
typedef long long LL;
char s[maxn], rec[maxn];
int blank[maxn], n, cnt[maxn];
bool exist[maxn], exist2[maxn];
bool match(const char* s1, const char* s2) {
memset(exist2, 0, sizeof(exist2));
for (int i = 0; i < n; ++i) {
if (s2[i] != '*') exist2[s2[i]] = true;
}
for (int i = 0; i < n; ++i) {
if (s2[i] == '*' && exist2[s1[i]]) return false;
}
for (int i = 0; i < n; ++i) {
if (s2[i] == '*') continue;
if (s1[i] != s2[i]) return false;
}
return true;
}
int main() {
scanf("%d", &n);
scanf("%s", s);
int m, tot = 0;
for (int i = 0; s[i]; ++i) if (s[i] == '*') blank[tot++] = i;
scanf("%d", &m);
int total = 0;
for (int i = 0; i < m; ++i) {
scanf("%s", rec);
memset(exist, 0, sizeof(exist));
if (match(rec, s)) {
++total;
for (int j = 0; j < tot; ++j) {
exist[rec[blank[j]]] = true;
}
for (int j = 'a'; j <= 'z'; ++j) {
if (exist[j]) ++cnt[j];
}
}
}
int ans = 0;
for (int i = 'a'; i <= 'z'; ++i) {
if (cnt[i] >= total) ++ans;
}
printf("%d\n", ans);
return 0;
}
F. Lost in Transliteration
题意
用拉丁字母书写波兰姓名可能会有歧义,体现在两点上:
- \(kh\leftrightarrow h\)
- \(oo\leftrightarrow u\)
现给出一串名字,问有多少不同的名字。
注意,变化是有后续影响的,即可以接连变化的。
思路
最方便的做法是:
将所有的\(kkkk……kkh\)替换成\(h\),将所有的\(u\)替换成\(oo\).
前者显而易见,为什么后者是将所有的\(u\)替换成\(oo\),而不是反之呢?
如果反之,将\(oo\)都化成\(u\). 考虑奇数个\(o\)的情况,它事实上对应了\(n/2+1\)种串,每两个\(o\)一合并,最终多出来的那个\(u\)可能出现在第一个到最后一个之间的任意一个位置上。就不方便比较了。
// 再感叹一句样例真心良心
采用将所有的\(kkkk……kkh\)替换成\(h\),将所有的\(u\)替换成\(oo\),就保证了结果的唯一性。
// 一个小注意点,\(u\)替换成\(oo\)后字符串的长度可能翻倍,数组要开两倍。
最后排个序数一数即可。
// 还学到了字符串数组特殊的排序技巧...。
Code
#include <bits/stdc++.h>
#define maxl 110
#define maxn 410
using namespace std;
typedef long long LL;
char s[maxl];
struct Array {
char data[maxl];
char& operator [] (const int idx) { return data[idx]; }
}ss[maxn];
bool cmp(Array a, Array b) {
return strcmp(a.data, b.data) < 0;
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%s", s);
int len = strlen(s), p = 0;
while (p < len) {
if (s[p] == 'k') {
int j = p;
while (j < len && s[j] == 'k') ++j;
if (j == len || s[j] != 'h') {
for (; p < j; ++p) ss[i][++ss[i][0]] = 'k';
}
else if (s[j] == 'h') ss[i][++ss[i][0]] = 'h', ++j;
p = j;
}
else if (s[p] == 'u') {
ss[i][++ss[i][0]] = 'o';
ss[i][++ss[i][0]] = 'o';
++p;
}
else ss[i][++ss[i][0]] = s[p++];
}
ss[i][ss[i][0]+1] = '\0';
}
sort(ss, ss+n, cmp);
int ans = 1;
for (int i = 1; i < n; ++i) {
if (cmp(ss[i-1], ss[i]) == 1) ++ans;
}
printf("%d\n", ans);
return 0;
}
G. Orientation of Edges
题意
给定一张图,\(n\)个点,\(m\)条边,若干条有向边,若干条无向边,且有重边。现要求给所有的无向边定向,使得从某个给定点\(s\)出发能够访问到的点的总数最大/最小。输出个数及方案。
思路
直接\(dfs\).
坑点在重边。有向边和无向边不能算作重边。并且最后输出边定向的时候要注意将所有的预处理时删去的重边定成和留下的那条同向。
Code
#include <bits/stdc++.h>
#define maxm 600010
#define maxn 600010
using namespace std;
typedef long long LL;
int n, m, s, tot, ne[maxn], id[maxn], ans[maxn], cnt;
bool vis[maxn];
struct node {
int u, v, t;
}a[maxm];
bool cmp(int i, int j) {
return a[i].u < a[j].u || (a[i].u==a[j].u && a[i].v<a[j].v) || (a[i].u==a[j].u && a[i].v==a[j].v && a[i].t<a[j].t);
}
struct Edge {
int to, ne, type, dir, id;
Edge(int _to=0, int _ne=0, int _type=0, int _id=0, int _dir=-1):
to(_to), ne(_ne), type(_type), dir(_dir), id(_id) {}
}edge[maxm];
void add(int type, int u, int v, int id) {
edge[tot] = Edge(v, ne[u], type, id);
ne[u] = tot++;
if (type==2) edge[tot] = Edge(u, ne[v], type, id), ne[v] = tot++;
}
void dfs1(int u) {
++cnt;
vis[u] = true;
for (int i = ne[u]; ~i; i = edge[i].ne) {
int v = edge[i].to;
if (vis[v]) continue;
edge[i].dir = 1;
dfs1(v);
}
}
void dfs2(int u) {
++cnt;
vis[u] = true;
for (int i = ne[u]; ~i; i = edge[i].ne) {
int v = edge[i].to;
if (vis[v]) continue;
if (edge[i].type == 1) dfs2(v);
else edge[i].dir = 0;
}
}
void out() {
printf("%d\n", cnt);
for (int i = 0; i < tot; ++i) {
if (edge[i].dir!=-1) ans[edge[i].id] = edge[i].to == a[edge[i].id].v ? edge[i].dir : !edge[i].dir;
}
for (int i = 1; i < m; ++i) {
if (a[id[i]].u == a[id[i-1]].u && a[id[i]].v==a[id[i-1]].v && a[id[i]].t==a[id[i-1]].t) ans[id[i]] = ans[id[i-1]];
}
for (int i = 0; i < m; ++i) {
if (a[i].t == 2) putchar(ans[i]==1 ? '+' : '-');
}
puts("");
}
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 0; i < m; ++i) scanf("%d%d%d", &a[i].t, &a[i].u, &a[i].v), id[i] = i;
sort(id, id+m, cmp);
memset(ne, -1, sizeof(ne));
add(a[id[0]].t, a[id[0]].u, a[id[0]].v, id[0]);
for (int i = 1; i < m; ++i) {
if (a[id[i]].u!=a[id[i-1]].u || a[id[i]].v!=a[id[i-1]].v || a[id[i]].t!=a[id[i-1]].t) add(a[id[i]].t, a[id[i]].u, a[id[i]].v, id[i]);
}
cnt = 0;
dfs1(s);
out();
cnt = 0;
for (int i = 1; i <= n; ++i) edge[i].dir = -1;
memset(vis, 0, sizeof(vis));
dfs2(s);
out();
return 0;
}
H. Palindromic Cut
题意
给定一个长度为\(n(n\leq 4e5)\)的字符串,现要将其中的字符重新安排位置,并且切成等长的若干段,使得每段都是一个回文串。问最少切成多少段。并输出一种方案。
思路
切
统计出现了奇数次的字符个数,假设为\(k\)个,则显然:
- 要分成的段数\(\geq k\)
- 每一段长度都须为奇数长
k==0
一段,不用切。
k==1
一段,不用切。
为什么?万一n为偶数呢?那不是不好放置吗?
这种情况是不可能发生的,因为 1个奇数+若干个偶数=奇数 ,即总长度必然为奇数长,直接将它放在中间即可。
其他情况
上面说到,要分成的段数\(\geq k\),每段长度为奇数,但又不仅局限于这两个条件。
注意到,如果分成的段数\(\gt k\),那么大的部分一定得是偶数。即假设分成了\(num\)段,则\(num-k\)一定得为偶数。
为什么呢?
因为将那出现奇数次的k种字符放在k段的中间位置后,多出来的num-k段,其中间的字符都是从出现了偶数次的字符中拆出来的,而要拆就得一对一对地拆,否则剩下奇数个又没法往两边分了。
于是预处理出\(n\)的所有因子,枚举分成的段数,即从\(\geq k\)的第一个开始向后枚举,\(check\)是否满足条件即可。时间复杂度\(O(\sqrt n)\).
万一不存在一种满足要求的情况呢?
不存在的。至少有一种分法满足要求,那就是全部切开。
分
输出的考虑和上面提到的拆和放的思路一致。
-
先是将所有出现奇数次的\(k\)种字符放到前\(k\)段的中间位置,每种放一个;
-
再将偶数个的一对对拆出来填到后\(n-k\)段中,这里放多少个就无所谓了,可直接对每种字符放完为止;
-
最后将剩下的字符从两边往中间放,满了就下一段。
注意特判分成一段的情况,直接输出即可。
Code
#include <bits/stdc++.h>
#define N 62
#define maxn 400010
char s[maxn];
int cnt[70], fac[700], n;
char ans[maxn];
using namespace std;
typedef long long LL;
int idx(char c) {
if (islower(c)) return c-'a';
else if (isupper(c)) return c-'A'+26;
else return c-'0'+52;
}
char charat(int i) {
if (i < 26) return 'a'+i;
else if (i < 52) return 'A'+i-26;
else return '0'+i-52;
}
void print1() {
puts("1");
int p = 0;
for (int i = 0; i < N; ++i) {
if (cnt[i] & 1) --cnt[i], ans[n/2] = charat(i);
while (cnt[i]) {
ans[p] = ans[n-1-p] = charat(i);
++p, cnt[i] -= 2;
}
}
ans[n] = '\0';
puts(ans);
exit(0);
}
void printn() {
printf("%d\n", n);
putchar(s[0]);
for (int i = 1; i < n; ++i) printf(" %c", s[i]); puts("");
exit(0);
}
void print(int num, int sz) {
printf("%d\n", num);
for (int i = 0; i < num-1; ++i) {
char temp = ans[i*sz+sz];
ans[i*sz+sz] = '\0';
printf("%s ", ans+i*sz);
ans[i*sz+sz] = temp;
}
printf("%s\n", ans+sz*(num-1));
}
int main() {
scanf("%d%s", &n, s);
for (int i = 0; i < n; ++i) ++cnt[idx(s[i])];
int k = 0;
for (int i = 0; i < N; ++i) if (cnt[i]&1) ++k;
if (!k || k==1) print1();
int tot = 0;
for (int i = 1; i*i <= n; ++i) {
if (i*i == n) fac[tot++] = i;
else {
if (n % i == 0) fac[tot++] = i, fac[tot++] = n/i;
}
}
sort(fac, fac+tot);
int p = lower_bound(fac, fac+tot, k) - fac;
for (; p < tot; ++p) {
if (!((fac[p]-k)&1) && ((n/fac[p])&1)) break;
}
int sz = n/fac[p], curm = 0, cur = 0;
if (sz == 1) printn();
for (int i = 0; i < N; ++i) {
if (cnt[i] & 1) ans[curm*sz+sz/2] = charat(i), ++curm, --cnt[i];
}
for (int i = 0; i < N; ++i) {
if (curm == fac[p]) break;
while (cnt[i] && curm < fac[p]) ans[curm*sz+sz/2] = charat(i), ++curm, --cnt[i];
}
int l = 0, r = sz-1;
for (int i = 0; i < N; ++i) {
if (cur == fac[p]) break;
if (l == sz/2) ++cur, l = 0, r = sz-1;
while (cnt[i]) {
if (l == sz/2) ++cur, l = 0, r = sz-1;
ans[cur*sz+l++] = ans[cur*sz+r--] = charat(i);
cnt[i] -= 2;
}
}
print(fac[p], sz);
return 0;
}
I. Photo Processing
2017-2018 ACM-ICPC, NEERC, Southern Subregional Contest I. Photo Processing
K. Road Widening
题意
给定一个数列a
,每个元素初始值为s[i]
,可以加上g[i]
的变化量,要求最终得到的数列中相邻两数的绝对值之差\(\leq 1\). 问总共最多能加的值是多少。输出总和及各点变化后的量。
思路
正着扫描一遍,限定下来一个范围。再拿这个范围的最大值去倒着扫描一遍,确定每个点的值。
Code
#include <bits/stdc++.h>
#define maxn 200010
using namespace std;
typedef long long LL;
int s[maxn], g[maxn], e[maxn], ans[maxn], st[maxn], ed[maxn];
LL res;
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) scanf("%d%d", &s[i], &g[i]), e[i] = s[i] + g[i];
st[0] = s[0], ed[0] = e[0];
for (int i = 1; i < n; ++i) {
st[i] = max(st[i-1]-1, s[i]);
ed[i] = min(ed[i-1]+1, e[i]);
if (st[i] > ed[i]) { puts("-1"); return 0; }
}
LL res=ed[n-1]-s[n-1];
ans[n-1] = ed[n-1];
for (int i = n-2; i >= 0; --i) {
if (ans[i+1] + 1 <= ed[i]) ans[i] = ans[i+1] + 1;
else if (ans[i+1] <= ed[i]) ans[i] = ans[i+1];
else ans[i] = ans[i+1] - 1;
res += ans[i]-s[i];
}
printf("%I64d\n%d", res, ans[0]);
for (int i = 1; i < n; ++i) printf(" %d", ans[i]); printf("\n");
return 0;
}
M. Quadcopter Competition
题意
在方格纸上走,从\((x1,y1)\)出发绕一圈回来,要求将\((x2,y2)\)包围在路径里(不能在路径上),问最小距离。
思路
就是问最小的矩形周长。
注意横或纵坐标相同的情况。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
int x1, x2,y1, y2, ans;
scanf("%d%d", &x1, &y1);
scanf("%d%d", &x2, &y2);
if (y1 == y2) ans = 2 * (1+abs(x1-x2)) + 4;
else if (x1 == x2) ans = 2 * (1+abs(y1-y2)) + 4;
else {
if (x2 > x1) ++x2;
else --x2;
if (y2 > y1) ++y2;
else --y2;
ans = 2 * (abs(y1-y2) + abs(x1-x2));
}
printf("%d\n", ans);
return 0;
}