我解决的:D、I、E。
没看的:H、K。
旁观的:B、G。
看了但没做出来的:A、C、F、J。
E Life Transfer
简单题,略。
D Cycle String?
题意:给定一个长为 的串,问能否将其重新排列使得新串的每一个长度为 的循环子串(即这个子串可以由原串的某个后缀和前缀拼接而成)互不相同,如果能要给出一个构造。
统计每一个字符出现的数目,然后分类讨论。
- 只有一种字符,那必然无解。
- 只有两种字符,且某种字符出现了不少于 次,那么就可能无解,要看 是否为 或者 ,或者大于 。这里讨论一下即可。
- 除上面两种情况外都是有解的。设出现次数最多的字符为 ,如果 出现了不超过 次,那么只要对原字符串排序即可;否则在排序完原字符串后,要用一个除 之外的字符把那一段 分割为左右两部分,使得左半部分长度恰为 。可以证明这一构造是合法的。
K Stranded Robot
大模拟,略。应该不是很难写…
G Projection
题意:有一个 的三维网格,每一个网点上可能有一个方块。给定这个网格沿着两个方向投影得到的画面,大小分别为 和 ,问为了达成这样的投影,这个网格中最多要有几个方块、最少要有几个方块,并给出两种情况下方块的坐标列表。多解时输出字典序最小的那个。
按 坐标遍历,每一层进行构造。显然,每一层的答案互不影响。
无解当且仅当这一层在 的投影中有影子,但在 的投影中没有影子,或者反过来。
如果这一层在 的投影中有 个方块的影子,在 的投影中有 个方块的影子,那么这一层最多可以有 个方块,最少可以有 个方块。
对于后者要求出字典序最小的方案,只需要按照 坐标或者 坐标递增方向构造方块即可。
#include <bits/stdc++.h>
#define MAXN 105
using namespace std;
int n, m, h, a[MAXN][MAXN], b[MAXN][MAXN];
int cntm[MAXN], cnth[MAXN];
int lstm[MAXN], lsth[MAXN];
char s[MAXN];
int main(){
scanf("%d%d%d", &n, &m, &h);
for (int i = 1; i <= n; ++i){
scanf("%s", s + 1);
cntm[i] = 0;
for (int j = 1; j <= m; ++j)
a[i][j] = (s[j] == '1' ? 1: 0), cntm[i] += a[i][j];
}
for (int i = 1; i <= n; ++i){
scanf("%s", s + 1);
cnth[i] = 0;
for (int j = 1; j <= h; ++j)
b[i][j] = (s[j] == '1' ? 1: 0), cnth[i] += b[i][j];
}
// -1
int maxi = 0, mini = 0;
for (int i = 1; i <= n; ++i){
if ((cntm[i] > 0 && cnth[i] == 0) || (cnth[i] > 0 && cntm[i] == 0)){
printf("-1\n");
return 0;
}
maxi += cntm[i] * cnth[i];
mini += max(cntm[i], cnth[i]);
}
// construct
printf("%d\n", maxi);
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= m; ++j)
for (int k = 1; k <= h; ++k)
if (a[i][j] && b[i][k]) printf("%d %d %d\n", i - 1, j - 1, k - 1);
}
printf("%d\n", mini);
for (int i = 1; i <= n; ++i){
if (!cntm[i]) continue;
int totm = 0, toth = 0;
for (int j = 1; j <= m; ++j)
if (a[i][j]) lstm[++totm] = j;
for (int j = 1; j <= h; ++j)
if (b[i][j]) lsth[++toth] = j;
for (int j = 1, k = 1; j <= totm; ){
printf("%d %d %d\n", i - 1, lstm[j] - 1, lsth[k] - 1);
if (totm - j == toth - k){
++j, ++k;
} else if (totm - j > toth - k){
++j;
} else {
++k;
}
}
}
return 0;
}
F Game on a Tree
题意:给定一个以 为根的树,一开始所有节点都是白色。A 先手将一个棋子放在某个节点,棋子会把所在节点涂黑。随后 B 和 A 轮流移动棋子,棋子不能跨过黑色节点,且只能移动到当时所在节点的祖先,或者子树中的某个节点。不能移动者输。问双方均采取最优策略时,谁赢。
这貌似是一个很经典的博弈论问题。如果将所有互相可达的点之间连一个边,得到一个新的无向图,那么结论就是:如果这个无向图的最大匹配是完美匹配,那么后手必胜;否则先手必胜。
为了求出新图的最大匹配,我们设 表示 号点的子树中,在最大匹配的情况下,还有多少个点没有被匹配。
设 的所有儿子的 值加起来为 。若其不为 0,那么 就与 个未匹配点中的一个匹配, 。否则 不被匹配, 。
所以按上述说明 DP 一下,检查 即可。
B Level Up
题意:有 个任务,现在要尽快通过两个关卡,一开始处于第一关,通过第一关就到达第二关。通过第一、二关各要 经验。到第二关时,原有的 经验会清零。如果通过第一关时经验超过 ,溢出的部分会继承到第二关。给定每一个任务在第一、二关时完成可获得的经验和所需时间,求最短通关时间。
我们同时考虑两个关卡,设 为用前 个任务,第一关达到 经验,第二关达到 经验时的最短时间。则转移方程容易写出。
一个坑点在于我们必须按照“在第一关时完成可获得的经验”从小到大来遍历任务,否则无法正确处理经验溢出的情况:考虑 ,只有两个任务 , 在第一关通过获得经验 , 为 ,两者在第二关通过时均不获得经验。如果 在 前被枚举,那么会被判无解,反之有解。
DP 的第一维可以用滚动数组优化。
I Absolute Game
题意:A 和 B 两个人各有 个数,有 轮游戏,每一轮两个人要轮流删掉自己的一个数。A 先手。假设最后 A,B 手上的数分别为 ,A 希望最大化两者差的绝对值,B 希望最小化两者差的绝对值。问两人均采取最优策略时,最后这个差的绝对值。
设 A 有数 ,B 有数 。问题等价于 A 选一个 ,然后最大化 。
于是就很简单了。但是最难的在于这个等价性是怎么来的呢?我不会证,不如参考官方题解。
J Graph and Cycles
题意:给定一个 个点的带权无向完全图,保证 是奇数。定义环 的价格为 ,其中 。现在要将图分割成若干个环,使得每一个环的价格之和最小。求这个价格。
由于 是奇数,因此每一个点的度为偶数。可以看出,如果一个环经过点 ,那么一定有一条边进入 ,另一条走出 。
对每一个点 ,其连着的边可以分成 组,每组属于某个环,一条进入 ,另一条走出 。每组中权值较大的边就是这组边对价格的贡献。因此所有环的价格和就等于若干组边的价值和。
到这一步,只要恰当的分组使得价格尽量小即可。最好的分组方式就是排序后按顺序两两分组。这样就求出了答案。
但怎么说明这样转化后,一定有相对应的环分割呢?我也不会证,不如参考官方题解。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
vector<int> G[1005];
int main(){
scanf("%d", &n);
int u, v, w;
while (~scanf("%d%d%d", &u, &v, &w)){
G[u].push_back(w);
G[v].push_back(w);
}
ll ans = 0;
for (int i = 1; i <= n; ++i){
sort(G[i].begin(), G[i].end());
for (int j = 1; j < n - 1; j += 2)
ans += G[i][j];
}
printf("%lld\n", ans);
return 0;
}
C Find the Array
题意:交互题。有一个长为 的数组,里面的数互不相同,且开始时未知。有两种操作:操作 1 直接获取一个下标上的值,操作 2 接受若干下标,设为 个,获得这些下标上的数两两做差的绝对值,共 个(无序)。在 30 次操作内找出这个数组。
本题有一个很妙的做法。先用操作 2 输入所有下标,找出极差 ,其必然是两个最值的差。然后我们希望找到一个最小的 ,使得 的最值均在 内。这个可以二分处理。
然后针对 ,我们希望对于所有 ,找出 。这可以用二进制分组实现:枚举从低位到高位每一个 bit,设该 bit 为 ,设与该 bit 有交(即按位与不为 0)、且不等于 的下标集合为 ,把 输入给操作 2,然后再把 输入给操作 2,把前者的结果除掉后者的结果,就得到了对任意 的 构成的集合,记为 。
我们已经知道了 数组的所有值(共 个),现在要把值和下标关联起来。这是二进制分组的常规操作:对于每一个值,检查其对于每一个 bit ,是否在 中出现。把所有出现了的 bit 或起来就是下标。
得到了 数组就可以知道另一个极值在哪,设为 。用两次操作 1 获得 和 ,然后就可以方便地利用 数组求出 数组了。
总操作数不超过 。
注意特判一些特殊情况,如操作 2 必须接受至少 2 个数。
#include <bits/stdc++.h>
using namespace std;
int n, a[255], b[255], lst[255], tot;
unordered_multiset<int> st[13];
unordered_set<int> sttot;
int getmaxi(int l){
int maxi = 0;
l = l * (l - 1) >> 1;
for (int i = 1, t; i <= l; ++i){
scanf("%d", &t);
maxi = max(maxi, t);
}
return maxi;
}
int main(){
scanf("%d", &n);
// find the maximum differece
if (n == 1){
printf("1 1\n");
fflush(stdout);
scanf("%d", &a[1]);
printf("3 %d\n", a[1]);
return 0;
}
printf("2 %d", n);
for (int i = 1; i <= n; ++i)
printf(" %d", i);
putchar('\n');
fflush(stdout);
int maxi = getmaxi(n);
// find the base position
int l = 2, r = n;
while (r > l){
int mid = (l + r) >> 1;
printf("2 %d", mid);
for (int i = 1; i <= mid; ++i)
printf(" %d", i);
putchar('\n');
fflush(stdout);
if (getmaxi(mid) == maxi) r = mid;
else l = mid + 1;
}
// here, pos is l
// group the number by bits
for (int i = 1, logg = 0; i <= n; i <<= 1, ++logg){
tot = 0;
for (int j = 1; j <= n; ++j){
if (!(i & j) || j == l) continue;
lst[++tot] = j;
}
if (tot == 0) continue;
printf("2 %d %d", tot + 1, l);
for (int j = 1; j <= tot; ++j)
printf(" %d", lst[j]);
putchar('\n');
fflush(stdout);
for (int j = 1, t; j <= (tot + 1) * tot / 2; ++j){
scanf("%d", &t);
st[logg].insert(t);
}
if (tot > 1){
printf("2 %d", tot);
for (int j = 1; j <= tot; ++j)
printf(" %d", lst[j]);
putchar('\n');
fflush(stdout);
for (int j = 1, t; j <= (tot - 1) * tot / 2; ++j){
scanf("%d", &t);
st[logg].erase(st[logg].find(t));
}
}
for (int x: st[logg])
sttot.insert(x);
}
// find array b
int logg = 0, p = -1;
while ((1 << logg) <= n) ++logg;
for (int x: sttot){
int pos = 0;
for (int i = 0; i < logg; ++i){
if (st[i].count(x)) pos |= (1 << i);
}
b[pos] = x;
if (x == maxi) p = pos;
}
// here, pos' is p
printf("1 %d\n", l);
fflush(stdout);
scanf("%d", &a[l]);
printf("1 %d\n", p);
fflush(stdout);
scanf("%d", &a[p]);
// calc the answer
int sgn = (a[l] < a[p] ? 1: -1);
printf("3");
for (int i = 1; i <= n; ++i){
if (i != l) a[i] = a[l] + sgn * b[i];
printf(" %d", a[i]);
}
putchar('\n');
fflush(stdout);
return 0;
}
A Max or Min
待补。。。
H Tree Permutations
待补。。。