2019牛客暑期多校训练第六场
A.Garbage Classification
垃圾分类题,模拟即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
int t, n, cnt[3], m, mp[27];
char s[MAXN], id[27];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> t;
for (int kase = 1; kase <= t; kase++) {
memset(cnt, 0, sizeof(cnt));
cin >> (s + 1);
n = strlen(s + 1);
cin >> (id);
m = strlen(id);
for (int i = 0; i < m; i++) {
if (id[i] == 'd')mp[i] = 0;
else if (id[i] == 'w')mp[i] = 1;
else mp[i] = 2;
}
for (int i = 1; i <= n; i++) {
cnt[mp[s[i] - 'a']]++;
}
cout << "Case #" << kase << ": ";
if (4 * cnt[2] >= n)cout << "Harmful
";
else if (10 * cnt[2] <= n)cout << "Recyclable
";
else if (cnt[0] >= 2 * cnt[1])cout << "Dry
";
else cout << "Wet
";
}
return 0;
}
B.Shorten IPv6 Address
按照题目条件处理即可:
- 处理前导零
- 处理出连续含零区间,注意处理端点在左右边界的情况的长度。
- 贪心将最右且最长的区间变为 ::
可利用(string)处理较为方便。
具体细节见代码:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e3 + 5;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
int t, n, v[5] = { 0,1,2,4,8 };
string ans[8], s, tmp;
string res;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> t;
for (int kase = 1; kase <= t; kase++) {
cin >> tmp;
s.clear();
for (int i = 0; i < 32; i++) {
int sum = 0;
for (int j = 0; j < 4; j++) {
if (tmp[i * 4 + j] == '1')sum |= v[4 - j];
}
if (sum <= 9)s.push_back('0' + sum);
else s.push_back('a' + sum - 10);
}
for (int i = 0; i < 8; i++) {
ans[i].clear();
int cnt = 0;
bool flag = false;
for (int j = i * 4; j < i * 4 + 4; j++) {
if (flag || s[j] != '0') {
flag = true;
ans[i].push_back(s[j]);
}
}
if (!flag) {
ans[i].push_back('0');
}
}
int l = 0, r = 0, len = 0;
for (int i = 7; i >= 1; i--) {
if (ans[i].size() == 1 && ans[i - 1].size() == 1 && ans[i][0] == '0' && ans[i - 1][0] == '0') {
int left = i, right = i;
while (left - 1 >= 0 && ans[left - 1].size() == 1 && ans[left - 1][0] == '0')left--;
if (2 * (right - left) + (right != 7 && left != 0) > len) {
len = 2 * (right - left) + (right != 7 && left != 0);
r = right;
l = left;
}
}
}
res.clear();
if (len == 0) {
for (int i = 0; i < 8; i++) {
res.append(ans[i]);
if (i < 7)res.push_back(':');
}
}
else {
for (int i = 0; i < l; i++) {
res.append(ans[i]);
if (i < 7)res.push_back(':');
}
if (l > 0)res.append(":");
else res.append("::");
for (int i = r + 1; i < 8; i++) {
res.append(ans[i]);
if (i < 7)res.push_back(':');
}
}
cout << "Case #" << kase << ": " << res << '
';
}
return 0;
}
C.Palindrome Mouse
对于一个本质不同的回文串,其含回文子串的个数即为其在回文树上的祖先以及相关(fail)结点。
考虑直接在回文树上(dfs),暴力跳(fail)结点统计答案。
可以证明,复杂度为(O(n))。因为每个结点的(fail)指针只会跳一次,再跳一次就相当于目前串后缀的一个前缀,而前缀在之前肯定已经被标记过了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
int T, n;
namespace PAM{
int ch[N][26], fail[N], len[N], st[N], cnt[N], vis[N];
int sz, n, last;
ll res, ans;
int New(int l, int f) {
memset(ch[++sz], 0, sizeof(ch[sz]));
len[sz] = l, fail[sz] = f;
return sz;
}
void init() {
sz = -1;
New(0, 1); last = New(-1, 0);
st[n = 0] = -1;
memset(cnt, 0, sizeof(cnt));
memset(vis, 0, sizeof(vis));
res = ans = 0;
}
int getf(int x) {
while(st[n - len[x] - 1] != st[n]) x = fail[x];
return x;
}
bool Insert(int c) { //int
st[++n] = c;
int x = getf(last);
bool F = 0;
if(!ch[x][c]) {
F = 1;
int f = getf(fail[x]);
ch[x][c] = New(len[x] + 2, ch[f][c]);
}
last = ch[x][c];
cnt[last]++;
return F;
}
void count() {
for(int i = sz; i >= 1; i--) cnt[fail[i]] += cnt[i];
}
void add(int x) {
if(++vis[x] == 1) ans++;
}
void del(int x) {
if(--vis[x] == 0) ans--;
}
void dfs(int u) {
if(fail[u] > 1) add(fail[u]);
res += ans;
if(u > 1) add(u);
for(int i = 0; i < 26; i++) {
if(ch[u][i]) dfs(ch[u][i]);
}
if(fail[u] > 1) del(fail[u]);
if(u > 1) del(u);
}
};
char s[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
for(int Case = 1; Case <= T; Case++) {
cin >> s + 1;
n = strlen(s + 1);
PAM::init();
for(int i = 1; i <= n; i++) PAM::Insert(s[i] - 'a');
PAM::dfs(0); PAM::dfs(1);
cout << "Case #" << Case << ": ";
cout << PAM::res << '
';
}
return 0;
}
D.Move
第一反应是二分,但是这个题不具有单调性质。
因为我们肯定会确定一个界来进行检验,但又不能二分,所以考虑枚举。直接枚举显然不行,但发现这个题的答案有边界。
易知下边界为(frac{sum}{k}),上边界为(frac{sum+maxv}{k})。所以直接暴力枚举并且判断就行了。
判断可以利用(set)判断,那么时间复杂度就是(O(maxv*nlogn))。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T;
const int N = 1005;
int a[N];
int n, k;
bool vis[N];
bool check(int x) {
int now = x, cnt = 0;
for(int i = 1; i <= n; i++) vis[i] = 0;
for(int j = 1; j <= k; j++) {
now = x;
for(int i = n; i >= 1; i--) {
if(a[i] <= now && !vis[i]) {
vis[i] = 1; now -= a[i];
cnt++;
}
}
}
bool flag= true;
for(int i=1;i<=n;i++){
if(!vis[i]){
flag = false;
break;
}
}
return flag;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
int Case = 0;
while(T--) {
Case++;
cin >> n >> k;
int mx =0,sum=0,ans=0;
for(int i = 1; i <= n; i++) cin >> a[i],mx = max(mx,a[i]),sum += a[i];
sort(a + 1, a + n + 1);
for(int i = max(mx,(sum + k - 1)/k);;i++){
if(check(i)){
ans = i;
break;
}
}
cout << "Case #" << Case << ": ";
cout << ans << '
';
}
return 0;
}
E.Androgynos
两个图同构的条件即为点数相同、边数相同并且连通性一致。
在这个题中因为要求边数相同,所以点数必须满足(4k,4k+1)这条件才行。然后对于(nleq 4)的情况,就手玩一下;当(n=4k,k>1)时,会发现可以将点数缩成4块,那么连边就类似于(n=4)的情况。
题目中还要求输出映射关系,先分析(n=4k)的情况,这种情况块与块之间的连边就类似于“同”没用中间的那些东西(....),然后选择上面两块连成团就行。可以证明这样连刚好满足边数等于(frac{n*(n-1)}{4})。并且因为团和独立集比较特殊,其补图也很好找,所以就构造出来了。
对于(n=4k+1)的情况,将多出来的那个点连向两个团即可。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
int T;
int n;
int mp[N][N], ans[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
int Case = 0;
while(T--) {
Case++;
memset(mp, 0, sizeof(mp));
memset(ans, 0, sizeof(ans));
cin >> n;
cout << "Case #" << Case << ": ";
if(n % 4 > 1) {
cout << "No" << '
';
continue;
}
cout << "Yes" << '
';
int k = n / 4;
for(int i = 1; i <= k * 2; i += k) {
for(int j = i; j <= i + k - 1; j++) {
for(int t = j + 1; t <= i + k - 1; t++) {
mp[j][t] = mp[t][j] = 1;
}
}
}
for(int i = 1; i <= k; i++) {
for(int j = k + 1; j <= 2 * k ; j++) {
mp[i][j] = mp[j][i] = 1;
}
for(int j = 2 * k + 1; j <= 3 * k; j++) {
mp[i][j] = mp[j][i] = 1;
}
}
for(int i = k + 1; i <= 2 * k; i++) {
for(int j = 3 * k + 1; j <= 4 * k; j++) {
mp[i][j] = mp[j][i] = 1;
}
}
if(n % 4 == 1) {
for(int i = 1; i <= 2 * k; i++) mp[n][i] = mp[i][n] = 1;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
cout << mp[i][j];
}
cout << '
';
}
for(int i = 1; i <= k; i++) ans[i] = i + 2 * k;
for(int i = k + 1; i <= 2 * k; i++) ans[i] = i + 2 * k;
for(int i = 2 * k + 1; i <= 3 * k; i++) ans[i] = i - k;
for(int i = 3 * k + 1; i <= 4 * k; i++) ans[i] = i - 3 * k;
if(n % 4 == 1) ans[n] = n;
for(int i = 1; i < n; i++) cout << ans[i] << ' ';
cout << ans[n] << '
';
}
return 0;
}
G.Is Today Friday?
蔡勒公式判断星期,然后枚举所有(0)~(9)的全排列,将输入串去重后拿出来一一判断即可。
表面看复杂度是(O(10!n))的,但实际上跑不满。稍微分析(口胡)一下,如果给出的串杂乱无章,大概率会判不合法,那么每次check就会很快,不可能把(n)跑满;我们会(T)的情况主要就是发生在多个字符串同时满足多个排列的情况,感性分析一下这种情况只会出现在字符串都相同中,所以去重就行了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
string s[N];
int T, n;
int a[10];
int days[15] = {0, 31, 2, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool check(int k) {
int y = 0, m = 0, d = 0;
for(int i = 0; i < 4; i++) y = y * 10 + (a[s[k][i] - 'A']);
for(int i = 5; i < 7; i++) m = m * 10 + (a[s[k][i] - 'A']);
for(int i = 8; i < 10; i++) d = d * 10 + (a[s[k][i] - 'A']);
if(y < 1600 || y > 9999 || m > 12 || d > 31) return false;
if((m == 2 && d > 28 + (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)))) return false;
if(m != 2 && d > days[m]) return false;
if(m < 3) y--, m += 12;
return (y + y / 4 - y / 100 + y / 400 + d + 1 + 2 * m + 3 * (m + 1) / 5) % 7 == 5;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
int c = 0;
while(T--) {
cin >> n;
for(int i = 1; i <= n; i++) cin >> s[i];
for(int i = 0; i < 10; i++) a[i] = i;
sort(s + 1, s + n + 1);
int D = unique(s + 1, s + n + 1) - s - 1;
random_shuffle(s + 1, s + D + 1);
cout << "Case #" << (++c) << ": ";
int flag = 1;
do{
int ok = 1;
for(int i = 1; i <= D; i++) {
if(!check(i)) {
ok = 0; break;
}
}
if(ok) {
for(int i = 0; i < 10; i++) cout << a[i];
cout << '
';
flag = 0; break ;
}
} while(next_permutation(a, a + 10));
if(flag) cout << "Impossible" << '
';
}
return 0;
}
H.Train Driver
如果三个点都不动那很好处理,从每个点出发(bfs)即可。
但现在三个点都是随机出现的,其中两个点出现的范围较小,首先想到从这两个点出现的集合出发跑(bfs)。
接下来就考虑如何得到第三个点随机分布时的答案。
不论第三个点出现在哪个位置,这三个点都会交于一点,满足距离最短。那么可以看作从一点出发到第三点的距离比从其它点出发到第三个点的距离都短。这也是最短路。
所以考虑新建源点,每个点的初始距离都为(d[0][i]+d[1][i]),然后跑最短路即可。因为时间限制,所以考虑(O(n))的求法,可以拿两个普通队列模拟优先队列,也可以直接用(vector),类似于代码中的写法。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
int T;
int n, m;
int head[N], tot;
struct Edge{
int v, next, w;
}e[N << 1];
void adde(int u, int v, int w = 1) {
e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; head[u] = tot++;
}
int d[2][22][N];
int s[2][22];
void SSSP(int S, int *d) {
fill(d + 1, d + n + 1, -1); d[S] = 0;
queue <int> q; q.push(S);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(d[v] == -1 || d[v] > d[u] + 1) {
d[v] = d[u] + 1;
q.push(v);
}
}
}
}
int w[N];
vector <int> v[N];
bool vis[N];
ll work(int u1, int u2) {
for(int i = 1; i <= n; i++) w[i] = d[0][u1][i] + d[1][u2][i], vis[i] = 0;
for(int i = 0; i <= m; i++) v[i].clear();
for(int i = 1; i <= n; i++) v[w[i]].push_back(i);
for(int i = 0; i <= m; i++)
while(v[i].size()) {
int u = v[i].back(); v[i].pop_back();
if(vis[u]) continue ;
vis[u] = 1;
for(int j = head[u]; j != -1; j = e[j].next) {
int to = e[j].v;
if(w[to] > w[u] + 1) {
w[to] = w[u] + 1;
v[w[to]].push_back(to);
}
}
}
return accumulate(w + 1, w + n + 1, 0ll);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
int t = 0;
while(T--) {
cin >> n >> m; t++;
memset(head, -1, sizeof(head)); tot = 0;
for(int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
adde(u, v); adde(v, u);
}
for(int i = 0; i < 2; i++) {
cin >> s[i][0];
for(int j = 1; j <= s[i][0]; j++) {
cin >> s[i][j];
SSSP(s[i][j], d[i][j]);
}
}
cout << "Case #" << t << ": ";
ll dis = 0;
for(int i = 1; i <= s[0][0]; i++) {
for(int j = 1; j <= s[1][0]; j++) {
dis += work(i, j);
}
}
ll fm = 1ll * s[0][0] * s[1][0] * n;
ll g = __gcd(dis, fm);
dis /= g; fm /= g;
if(fm == 1) cout << dis << '
';
else cout << dis << '/' << fm << '
';
}
return 0;
}
/*
4 4
1 2
2 3
1 3
1 4
2 1 2
2 3 4
3 3
1 2
2 3
1 3
3 1 2 3
3 1 2 3
*/
J.Upgrading Technology
枚举最低位置,那么其它位置高度都可以任意取,维护前后缀即可。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e3 + 5;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
int t, n, m, c[MAXN][MAXN], d[MAXN];
ll sum[MAXN][MAXN], maxv[MAXN][MAXN], maxv2[MAXN];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> t;
for (int kase = 1; kase <= t; kase++) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> c[i][j];
sum[i][j] = sum[i][j - 1] - c[i][j];
}
}
for (int i = 1; i <= n; i++) {
maxv[i][m + 1] = -INFL;
for (int j = m; j >= 0; j--) {
maxv[i][j] = max(sum[i][j], maxv[i][j + 1]);
}
}
for (int i = 0; i <= m; i++) {
maxv2[i] = 0;
for (int j = 1; j <= n; j++) {
maxv2[i] += maxv[j][i];
}
}
for (int i = 1; i <= m; i++)cin >> d[i];
ll ans = 0, pre = 0;
for (int i = 0; i <= m; i++) {
pre += d[i];
for (int j = 1; j <= n; j++) {
ans = max(ans, pre + maxv2[i] - maxv[j][i] + sum[j][i]);
}
}
cout << "Case #" << kase << ": " << ans << '
';
}
return 0;
}