2019牛客暑期多校训练营(第六场)
Contest Info
[Practice Link](https://ac.nowcoder.com/acm/contest/886#question)
Solved | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
7/10 | O | O | O | O | O | - | Ø | - | - | O |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. Garbage Classification
按题意模拟即可。
#include <bits/stdc++.h>
using namespace std;
#define N 2010
char s[N], t[N], mp[N];
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case #%d: ", kase);
scanf("%s%s", s + 1, t);
for (int i = 0; i < 26; ++i) {
mp['a' + i] = t[i];
}
int d = 0, w = 0, h = 0;
int len = strlen(s + 1);
for (int i = 1; i <= len; ++i) {
int c = mp[s[i]];
if (c == 'w') ++w;
else if (c == 'd') ++d;
else ++h;
}
if (h * 4 >= len) {
puts("Harmful");
} else if (h * 10 <= len) {
puts("Recyclable");
} else if (d >= w * 2) {
puts("Dry");
} else {
puts("Wet");
}
}
return 0;
}
B. Shorten IPv6 Address
题意:
给出\(Ipv6\)地址的缩写规则,要求将\(128\)位的地址缩写成长度最小,多解输出字典序最小的解。
思路:
- 先去掉所有前导\(0\)
- 然后枚举一段连续的\(0\),变成\('::'\),然后枚举所有可行解,取最优解即可
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 1010
char s[N];
int a[8];
vector <string> res;
string f(int x) {
string res = "";
if (!x) return "0";
while (x) {
int y = x % 16;
if (y < 10) res += y + '0';
else if (y == 10) res += 'a';
else if (y == 11) res += 'b';
else if (y == 12) res += 'c';
else if (y == 13) res += 'd';
else if (y == 14) res += 'e';
else res += 'f';
x /= 16;
}
reverse(res.begin(), res.end());
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T; cin >> T;
for (int kase = 1; kase <= T; ++kase) {
cout << "Case #" << kase << ": ";
cin >> (s + 1);
int len = 128;
for (int i = 1; i <= len; i += 16) {
int num = 0;
for (int j = i; j < i + 16; ++j) {
num = num * 2 + s[j] - '0';
}
// cout << num << endl;
a[i / 16] = num;
}
res.clear();
string str = f(a[0]);
for (int i = 1; i < 8; ++i) {
str += ":";
str += f(a[i]);
}
res.push_back(str);
string tmp = "";
for (int i = 0; i < 8; ++i) {
string now = tmp;
now += ":";
int j = i;
for (; j < 8; ++j) {
if (a[j])
break;
}
for (int k = j; k < 8; ++k) {
now += ":";
now += f(a[k]);
}
if (j >= 8) now += ":";
if (j > i + 1) res.push_back(now);
if (i) tmp += ":";
tmp += f(a[i]);
}
sort(res.begin(), res.end(), [](string x, string y){
if (x.length() != y.length())
return x.length() < y.length();
return x < y;
});
// cout << "####\n";
// for (auto it : res) cout << it << endl;
cout << res[0] << "\n";
}
return 0;
}
C. Palindrome Mouse
题意:
给出一个字符串,将所有回文子串加入一个集合,然后询问集合中存在多少对\((a, b)\)使得\(a\)是\(b\)的子串
思路:
考虑本质不同的回文子串个数只有\(O(n)\)个,那么枚举每个本质不同的回文子串的末端点\(r\),根据其长度可以找到左端点\(l\)。
那么这个子串的贡献就是\([l, r]\)区间本质不同的回文子串个数\(-1\)。
那么就变成了这个题我做过.jpg
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
#define ALP 26
struct PAM{
int Next[N][ALP];
int fail[N];
int cnt[N];
int sum[N];
int len[N];
int s[N];
int last;
int n;
int p;
int d[N], up[N];
int pos[N], sze[N];
vector<int> G[N];
int newnode(int w){
for(int i=0;i<ALP;i++)
Next[p][i] = 0;
cnt[p] = 0;
len[p] = w;
return p++;
}
void init(){
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
s[n] = -1;
fail[0] = 1;
}
int get_fail(int x){
while(s[n-len[x]-1] != s[n]) x = fail[x];
return x;
}
bool add(int c){
bool F = 0;
c -= 'a';
s[++n] = c;
int cur = get_fail(last);
if(!Next[cur][c]){
int now = newnode(len[cur]+2);
fail[now] = Next[get_fail(fail[cur])][c];
Next[cur][c] = now;
d[now] = len[now] - len[fail[now]];
up[now] = (d[fail[now]] == d[now] ? up[fail[now]] : now);
F = 1;
}
last = Next[cur][c];
cnt[last]++;
return F;
}
void build() {
pos[0] = 0;
for (int i = 0; i <= p; ++i) G[i].clear();
for (int i = 0; i <= p; ++i) {
if (i != 1) {
G[fail[i]].push_back(i);
}
}
}
void DFS(int x) {
pos[x] = ++pos[0], sze[x] = 1;
for (auto y : G[x]) {
DFS(y);
sze[x] += sze[y];
}
}
}pam;
struct BIT {
ll a[N];
void init() {
memset(a, 0, sizeof a);
}
void update(int x, ll v) {
for (; x < N; x += x & -x) {
a[x] += v;
}
}
ll query(int x) {
ll res = 0;
for (; x > 0; x -= x & -x) {
res += a[x];
}
return res;
}
}bit;
struct SEG {
int t[N << 2];
void init() {
memset(t, 0, sizeof t);
}
void update(int id, int l, int r, int x, int v) {
if (l == r) {
t[id] = max(t[id], v);
return;
}
int mid = (l + r) >> 1;
if (x <= mid) update(id << 1, l, mid, x, v);
else update(id << 1 | 1, mid + 1, r, x, v);
t[id] = max(t[id << 1], t[id << 1 | 1]);
}
int query(int id, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) {
return t[id];
}
int mid = (l + r) >> 1;
int res = 0;
if (ql <= mid) res = max(res, query(id << 1, l, mid, ql, qr));
if (qr > mid) res = max(res, query(id << 1 | 1, mid + 1, r, ql, qr));
return res;
}
}seg;
int n, p[N];
char s[N];
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case #%d: ", kase);
scanf("%s", s + 1);
n = strlen(s + 1);
pam.init();
for (int i = 1; i <= n; ++i) {
if (pam.add(s[i])) {
p[i] = pam.p - 1;
} else {
p[i] = -1;
}
}
pam.build(); pam.DFS(1);
ll res = -pam.p + 2;
bit.init(); seg.init();
int now = 1;
for (int i = 1; i <= n; ++i) {
while (s[i] != s[i - pam.len[now] - 1]) now = pam.fail[now];
now = pam.Next[now][s[i] - 'a'];
for (int x = now; x; x = pam.fail[pam.up[x]]) {
int l = max(1, seg.query(1, 1, pam.pos[0], pam.pos[x], pam.pos[x] + pam.sze[x] - 1) - pam.len[x] + 2);
int r = i - pam.len[pam.up[x]] + 2;
bit.update(l, 1);
bit.update(r, -1);
}
seg.update(1, 1, pam.pos[0], pam.pos[now], i);
if (p[i] != -1) {
int l = i - pam.len[p[i]] + 1;
res += bit.query(l);
}
}
printf("%lld\n", res);
}
return 0;
}
D. Move
题意:
有\(k\)个箱子,体积一样,有\(n\)件物品,体积分别为\(v_i\)。
要求按照以下策略装箱:
- 先尽可能装满当前箱子,才考虑下一个箱子
- 对于每个箱子,每次选择剩下的最大的能装的装进去,直到没有一个物品能装进去
问箱子们的体积至少需要多少?
思路:
首先箱子体积跟答案没有单调性,如:
15 5
39 39 39 39 39 60 60 60 60 60 100 100 100 100 100
199 为一个合法的答案,但 200 不是,201 也不是。
那么咱们就考虑枚举,我们当时考虑的是递增的时候增量不是\(1\),而是\(\text{用剩余的未装物品的体积 - 箱子剩余体积} / k\)。
但实际上这样做只是会很快到达下界\(\left\lceil \frac{sum}{k} \right\rceil\),并且上界是\(\left\lceil \frac{sum}{k} \right\rceil + maxV\)
- 假设某个界不能装下所有物品,那么每个箱子的剩余空间都$ < maxV$
- 那么有\(k \cdot (ans - maxV + 1) \leq sum\)
- \(ans \leq \frac{sum}{k} + maxV - 1\)
所以直接枚举即可,时间复杂度\(\mathcal{O}(maxV \cdot nlogn)\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 1010
#define INF 0x3f3f3f3f
int n, k, a[N];
int check(int x) {
int sum = 0;
int box = 0;
multiset <int> se;
for (int i = 1; i <= n; ++i) {
se.insert(a[i]);
sum += a[i];
}
for (int i = 1; i <= k; ++i) {
int remind = x;
while (!se.empty()) {
auto pos = se.upper_bound(remind);
if (pos != se.begin()) {
--pos;
remind -= (*pos);
sum -= (*pos);
se.erase(pos);
} else {
break;
}
}
box += remind;
if (se.empty()) return -INF;
}
return sum - box;
}
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case #%d: ", kase);
scanf("%d%d", &n, &k);
int sum = 0;
for (int i = 1; i <= n; ++i) scanf("%d", a + i), sum += a[i];
for (int i = sum / k; ; ) {
int x = check(i);
if (x == -INF) {
printf("%d\n", i);
break;
} else {
++i;
// i += max(1, x / k + (x % k != 0));
}
}
}
return 0;
}
E. Androgynos
题意:
给出一个\(n\)个点的图,要求构造一个无向自补图。
思路:
- 一个图和其补图同构的必要条件是边数相同,即\(n\)阶完全图的边数是偶数。
- 注意到\(n\)阶无向完全图的边数是\(\frac{n(n - 1)}{2}\),那么\(n = 4k\)或者\(n = 4k + 1\)时成立。
- \(n = 4k\):
- \(n = 4\)时,连成一条链
- \(n = 4k(k > 1)\)时,可以先把顶点分成\(4\)块,\(2\)块内部连成团,\(2\)块内部不连组成独立集,然后按\(n = 4\)的情况连块之间的边
- \(n = 4k + 1\):
- 多出来的一个点向所有团连边
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 2010
int n;
int G[N][N];
int ans[N];
int main() {
// freopen("input.txt", "r", stdin);
int T;
scanf("%d", &T);
for (int cas = 1; cas <= T; ++cas) {
printf("Case #%d: ", cas);
scanf("%d", &n);
if (n % 4 == 2 || n % 4 == 3) {
puts("No");
continue;
}
memset(G, 0, sizeof G);
puts("Yes");
if (n % 4 == 0) {
for (int i = 1; i <= n; i += 4) {
int x = i, y = i + 1, z = i + 2, w = i + 3;
ans[x] = y;
ans[y] = w;
ans[z] = x;
ans[w] = z;
G[x][y] = G[y][x] = 1;
G[y][z] = G[z][y] = 1;
G[z][w] = G[w][z] = 1;
for (int j = 1; j < i; ++j) {
G[x][j] = G[j][x] = 1;
G[w][j] = G[j][w] = 1;
}
}
} else if (n % 4 == 1) {
ans[1] = 1;
for (int i = 2; i <= n; i += 4) {
int x = i, y = i + 1, z = i + 2, w = i + 3;
ans[x] = y;
ans[y] = w;
ans[z] = x;
ans[w] = z;
G[x][y] = G[y][x] = 1;
G[y][z] = G[z][y] = 1;
G[z][w] = G[w][z] = 1;
for (int j = 1; j < i; ++j) {
G[x][j] = G[j][x] = 1;
G[w][j] = G[j][w] = 1;
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
printf("%d", G[i][j]);
}
puts("");
}
for (int i = 1; i <= n; ++i) {
printf("%d%c", ans[i], " \n"[i == n]);
}
}
return 0;
}
G. Is Today Friday?
题意:
给出若干个加密后的日期,问是否存在一种解密序列使得所有日期合法并且都是星期五。
思路:
考虑限制条件非常紧,那么刚开始将全排列加入答案,然后一个一个\(check\),如果不行直接去掉,不用\(check\)两三次,可行解空间就会变得很小。
代码:
#include <bits/stdc++.h>
using namespace std;
int n;
vector <string> vec, res;
void input(vector <string> &vec) {
vec.clear();
string s;
for (int i = 1; i <= n; ++i) {
cin >> s;
vec.push_back(s);
}
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
}
void get_permutation(vector <string> &res) {
res.clear();
string str = "";
for (int i = 0; i < 10; ++i)
str += i + '0';
do {
res.push_back(str);
} while (next_permutation(str.begin(), str.end()));
}
int getwee2(int y, int m, int d) {
int ans;
if (m == 1 || m == 2) m += 12, y--;
if ((y < 1752) || (y == 1752 && m < 9) || (y == 1752 && m == 9 && d < 3)) {
ans = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 + 5) % 7;
} else {
ans = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
}
ans = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
return ans + 1;
}
int getweek(int y, int m, int d) {
if (m < 3) {
y -= 1;
m += 12;
}
int c = y / 100;
y = y - 100 * c;
return ((y + y / 4 + c / 4 - 2 * c + (13 * (m + 1)) / 5 + d - 1) % 7 + 7) % 7;
}
bool isLeap(int y) {
return ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0);
}
int mon[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
bool ok(string s, string mp) {
int y = (mp[s[0] - 'A'] - '0') * 1000 + (mp[s[1] - 'A'] - '0') * 100 + (mp[s[2] - 'A'] - '0') * 10 + (mp[s[3] - 'A'] - '0');
int m = (mp[s[5] - 'A'] - '0') * 10 + (mp[s[6] - 'A'] - '0');
int d = (mp[s[8] - 'A'] - '0') * 10 + (mp[s[9] - 'A'] - '0');
if (y < 1600) return 0;
if (m <= 0 || m > 12) return 0;
if (d <= 0 || d > mon[isLeap(y)][m]) return 0;
if (getweek(y, m, d) != 5) return 0;
return 1;
}
void check(string s, vector <string> &vec) {
vector <string> tmp;
for (auto it : vec) {
if (ok(s, it)) {
tmp.push_back(it);
}
}
vec = tmp;
}
void solve() {
for (auto it : vec) {
check(it, res);
if (res.empty()) {
cout << "Impossible\n";
return;
}
}
sort(res.begin(), res.end());
cout << res[0] << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T; cin >> T;
for (int kase = 1; kase <= T; ++kase) {
cout << "Case #" << kase << ": ";
cin >> n;
input(vec);
get_permutation(res);
solve();
}
return 0;
}
J. Upgrading Technology
题意:
有\(n\)个武器,第\(i\)个武器从\(j - 1\)升级到\(j\)级需要代价\(c_{i, j}\),如果所有武器都大于等于\(k\)级,那么会获得\(d_k\)的收益。
问如果升级武器使得收益最高。
思路:
- 如果要获得\(d_k\)的收益,那么需要保证至少有一件武器等级为\(k\),其他武器的等级大于等于\(k\)
- 不要忘记\(0\)级的收益
然后前缀搞一搞即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 1010
#define INFLL 0x3f3f3f3f3f3f3f3f
int n, m;
ll a[N][N], b[N];
ll f[N][N], g[N], f2[N][N];
int main() {
// freopen("input.txt", "r", stdin);
int T;
scanf("%d", &T);
for (int cas = 1; cas <= T; ++cas) {
printf("Case #%d: ", cas);
scanf("%d %d", &n, &m);
memset(f, 0, sizeof f);
memset(f2, 0, sizeof f2);
memset(g, 0, sizeof g);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%lld", &a[i][j]);
}
}
for (int i = 1; i <= m; ++i) {
scanf("%lld", b + i);
g[i] = g[i - 1] + b[i];
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
f[i][j] = f[i][j - 1] - a[i][j];
f2[i][j] = f2[i][j - 1] - a[i][j];
}
for (int j = m; j >= 0; --j) {
if (j + 1 <= m) {
f[i][j] = max(f[i][j], f[i][j + 1]);
}
}
}
for (int j = m; j >= 0; --j) {
ll tmp = 0;
for (int i = 1; i <= n; ++i) {
tmp += f[i][j];
}
for (int i = 1; i <= n; ++i) {
ans = max(ans, tmp - f[i][j] + f2[i][j] + g[j]);
}
}
printf("%lld\n", ans);
}
return 0;
}