Codeforces Round #614 (Div. 1)
AB
略
C
先观察规律确定dp方程式,然后记搜优化(略)
D
建出整棵树空间不太够( (3 imes10^7) 个节点 ),只能考虑类似虚树的思想。其实只需要知道每个子树里有多少节点,然后不断移动来确定重心(略)
E
如果能确定两种字母,剩下的都是第三种。那么询问 (CO,CH,CC,HO,OO),花费 (1.25),此时已经得到了除了最后一个位置的所有 (C) 和除了第一个位置的所有 (O),也就是说,([2,n-1]) 的所有字符已经确定。如果 (p_1) 还未确定,(p_1) 只能是 (O,H),假设答案为某一个,询问一次 ([1,n-1]) 即可得出 (p_1),用同样的方法询问一次 ([1,n]) 可以得出 (p_n),总花费 (1.25+frac{1}{(n-1)^2}+frac{1}{n^2})。
但 (n=4) 时需要特殊处理:先依次问 (CO,CH,CC,HO),如果某一个存在,就用类似上面确定 (p_1,p_n) 的方法确定剩余两位。否则询问 (OO),如果 (OO) 存在,(O) 一定占满了一段长度 (ge2) 的前缀(因为不存在 (CO,HO)),最坏的情况是只确定前面两个为 (O),那么一定有 (p_3=H),然后再问一次 ([1,n]) 确定 (p_4)。如果 (OO) 不存在,一定有 (p_2=p_3=H),(p_1=O/H,p_4=C/H),问一次 (HHH) 即可全部确定
#include <bits/stdc++.h>
using namespace std;
const int N = 52;
int n, m, k[N], w[N], tag[N]; char res[N];
#define flush fflush (stdout)
#define pc putchar
void query (string s) {
cout << "? " << s << '
';
flush; memset (tag, 0, sizeof (tag)); cin >> m;
for (int i = 1; i <= m; ++i) cin >> w[i], tag[w[i]] = 1;
}
void get (string s) {
query (s);
for (int i = 1, x; i <= m; ++i) {
k[x = w[i]] = k[x + 1] = 1;
res[x] = s[0], res[x + 1] = s[1];
}
}
void solvea () {
get ("CH"), get ("CO"), get ("CC"), get ("OO"), get ("HO");
for (int i = 2; i < n; ++i) if (!k[i]) res[i] = 'H';
if (!k[1]) {
string s = "H";
for (int i = 2; i < n; ++i) s += res[i];
query (s); res[1] = tag[1] ? 'H' : 'O';
}
if (!k[n]) {
string s = "";
for (int i = 1; i < n; ++i) s += res[i];
query (s + 'H'); res[n] = tag[1] ? 'H' : 'C';
}
}
void work () {
int l = w[1], r = w[1] + 1; string s;
for (int i = l - 1; i >= 1; --i) {
s = ""; for (int j = i + 1; j <= r; ++j) s += res[j];
query ('H' + s); if (tag[i]) { res[i] = 'H'; continue; }
query ('O' + s); res[i] = tag[i] ? 'O' : 'C';
}
for (int i = r + 1; i <= n; ++i) {
s = ""; for (int j = 1; j < i; ++j) s += res[j];
query (s + 'H'); if (tag[1]) { res[i] = 'H'; continue; }
query (s + 'O'); res[i] = tag[1] ? 'O' : 'C';
}
}
void solveb () {
get ("CH"); if (m) { work (); return; }
get ("CO"); if (m) { work (); return; }
get ("CC"); if (m) { work (); return; }
get ("HO"); if (m) { work (); return; }
get ("OO");
if (m) {
int pos = 0;
for (int i = 1; i <= n; ++i)
if (!k[i]) { pos = i; break; }
if (!pos) return;
if (pos == 3) res[3] = 'H';
string s = "";
for (int i = 1; i <= 3; ++i) s += res[i];
query (s + 'H');
res[n] = tag[1] ? 'H' : 'C';
} else {
query ("HHH"); res[2] = res[3] = 'H';
res[1] = tag[1] ? 'H' : 'O';
res[n] = tag[2] ? 'H' : 'C';
}
}
signed main() {
int T, qwq; cin >> T;
while (T--) {
cin >> n; memset (k, 0, sizeof (k));
n > 4 ? solvea () : solveb ();
pc ('!'), pc (' ');
for (int i = 1; i <= n; ++i) pc (res[i]);
puts (""); flush; cin >> qwq;
}
}
F
若 (a_x|a_y),连边 ((x,y)),形成一张 (DAG),对每个连通块单独处理。用计算加点方案代替删点方案。若存在 ((x,y),(y,z)),必存在 ((x,z)),所以一个点能不能加入只需记录“已激活”的无入边的点的集合(最大 (15)),然后 (dp) 一下,设 (f_{i,j}) 为激活点集为 (i),已经加入了 (j) 个点,转移分两种:一种是不改变 (i),选择的方案数与 (i) 可达点数有关;另一种是改变 (i),直接枚举加入点。