题解:P3549 [POI2013] MUL-Multidrink
posted on 2024-12-29 11:48:40 | under | source
机房大佬说这题思路上没有黑题的压迫感。
不妨拎起 \(1\to n\) 的链,链上每个点代表一棵子树。直觉上应该依次将子树遍历完再进行下去,这确实是对的。证明考虑调整法即可,假如我多次遍历该子树,则存在方案一次性遍历完,然后其它的对该子树的遍历就跳过即可。
于是考虑对一棵子树的遍历,同理进入儿子子树就应该遍历完,显然树形 dp,记 \(f_{u,0/1/2/3}\) 表示根进根出、根进儿子出、儿子进根出、儿子进儿子出地遍历完该子树是否可行。注意一个性质:\(f_{u,1/2}\) 实际上是相反的,也就是说一个成立另一个肯定成立,不妨一起记在 \(f_{u,1}\)。
转移大力分讨即可:
- \(f_{u,0}\):等价于 \(u\) 无儿子。
- \(f_{u,1}\):以儿子进根出为例,发现要么从一个儿子 \(v\) 的 \(f_{v,1}\) 开始、然后将叶子跳完、最后跳到 \(u\);要么全是叶子跳完后再跳到 \(u\)。也就是等价于 \(\sum\limits_{v\in son(u)} f_{v,0} \ge |son(u)|-1\),且剩下的那个必须满足 \(f_{v,1}\) 成立。
- \(f_{u,2}\):不难发现,优先跳完叶子不劣,所以先将所有叶子跳完。接着,假如一棵儿子子树不剩,就在跳叶子过程中跳到 \(u\) 即可;假如只剩 \(1\) 棵 \(v\) 子树,先跳 \(u\) 再跳 \(f_{v,1}\)(儿子进根出)的方案即可;假如剩 \(2\) 棵子树 \(v_1,v_2\),则对 \(v_1\) 进行根进儿子出,再跳到 \(u\),对 \(v_2\) 进行儿子进根出即可。假如剩 \(\ge 3\) 棵子树无解。也就是等价于去掉所有叶子后,儿子子树必须满足 \(f_{v,1}\) 成立,且儿子子树个数 \(\le 2\)。
最后,将链上的子树方案合并起来,肯定从左到右一个个地遍历,记 \(g_{i,0/1}\) 表示遍历完链上前 \(i\) 棵子树、且最终停留在 \(i\) 子树根或儿子上是否可行。转移是容易的,留给读者思考。
输出方案按转移进行即可。复杂度 \(O(n)\)。
代码
依托答辩,难以评价。
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5 + 5;
int n, u, v, d[N], dist[N], dn;
bool vis[N], f[N][3], g[N][2];
vector<int> to[N], son[N], ans;
inline void init(int u, int from){
for(auto v : to[u])
if(v ^ from) init(v, u), vis[u] |= vis[v];
if(u == n) vis[u] = true;
if(vis[u]) dist[++dn] = u;
}
inline void dfs(int u){
vis[u] = true;
for(auto v : to[u])
if(!vis[v]) dfs(v), son[u].push_back(v);
f[u][0] = son[u].empty();
if(!son[u].empty()){
f[u][1] = true;
int cnt = 0;
for(auto v : son[u])
if(!f[v][0]){
if(!f[v][1]) f[u][1] = false;
else if(++cnt > 1) f[u][1] = false;
}
}
if(son[u].size() >= 2){
f[u][2] = true;
int cnt = 0;
for(auto v : son[u])
if(!f[v][0]){
if(!f[v][1]) f[u][2] = false;
else if(++cnt > 2) f[u][2] = false;
}
}
}
inline void print(int u, int id){
//0:叶
//1:u进v出
//2: v进u出
//3:v进v出
if(!id) ans.push_back(u);
if(id == 1){
ans.push_back(u);
for(auto v : son[u]) if(!f[v][0]) print(v, 2);
for(auto v : son[u]) if(f[v][0]) print(v, 0);
}
if(id == 2){
for(auto v : son[u]) if(f[v][0]) print(v, 0);
for(auto v : son[u]) if(!f[v][0]) print(v, 1);
ans.push_back(u);
}
if(id == 3){
int fir = 0, sec = 0;
for(auto v : son[u])
if(!f[v][0]){
if(!fir) fir = v;
else sec = v;
}
if(!fir){
for(auto v : son[u])
if(f[v][0]){
print(v, 0);
if(!fir) ans.push_back(u), fir = 1;
}
}
else{
for(auto v : son[u]) if(f[v][0]) print(v, 0);
bool fl = false;
for(auto v : son[u])
if(!f[v][0]){
if(!fl) print(v, 1), ans.push_back(u), fl = true;
else print(v, 2);
}
}
}
}
inline void print2(int i, int id){
if(i == 1){
if(!id) print(dist[i], 0);
else print(dist[i], 1);
return ;
}
if(!id){
if(g[i - 1][0] && f[dist[i]][0]) print2(i - 1, 0), print(dist[i], 0);
else if(g[i - 1][0] && f[dist[i]][1]) print2(i - 1, 0), print(dist[i], 2);
else if(g[i - 1][1] && f[dist[i]][0]) print2(i - 1, 1), print(dist[i], 0);
}
else
if(g[i - 1][0] && f[dist[i]][1]) print2(i - 1, 0), print(dist[i], 1);
else if(g[i - 1][0] && f[dist[i]][2]) print2(i - 1, 0), print(dist[i], 3);
else if(g[i - 1][1] && f[dist[i]][1]) print2(i - 1, 1), print(dist[i], 1);
}
signed main(){
cin >> n;
for(int i = 1; i < n; ++i) scanf("%d%d", &u, &v), to[u].push_back(v), to[v].push_back(u);
init(1, 0);
reverse(dist + 1, dist + 1 + dn);
for(int i = 1; i <= dn; ++i) dfs(dist[i]);
g[1][0] = f[dist[1]][0], g[1][1] = f[dist[1]][1];
for(int i = 2; i <= dn; ++i){
g[i][0] = (g[i - 1][0] & (f[dist[i]][0] | f[dist[i]][1])) | (g[i - 1][1] & f[dist[i]][0]);
g[i][1] = (g[i - 1][0] & (f[dist[i]][1] | f[dist[i]][2])) | (g[i - 1][1] & f[dist[i]][1]);
}
if(!g[dn][0]) puts("BRAK");
else{
print2(dn, 0);
for(auto i : ans) printf("%d\n", i);
}
return 0;
}

浙公网安备 33010602011771号