Codeforces Round #614 (Div. 1)
Codeforces Round #614 (Div. 1)
AB
略
C
先观察规律确定dp方程式,然后记搜优化(略)
D
建出整棵树空间不太够( \(3\times10^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 << '\n';
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\),直接枚举加入点。