基环树问题 解题报告
luogu P5022 旅行
题意
对于60%的数据,给一棵树,求一条字典序最小的Hamilton路径;
对于40%的数据,给一颗基环树,求一条字典序最小的Hamilton路径。
分析
前向星存图,对于每个点的出边排序,从1开始dfs一遍即可过60%数据;
对于基环树,由于Hamilton路径在树上必然有一条边不经过,而这条边必然在环上,
可以考虑枚举删除环上的边,遍历图找出并更新答案。
然而题目数据m就5000,枚举每条边都删一下也可行。
代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#define rg register
#define IOS ios::sync_with_stdio(false);cin.tie(NULL),cout.tie(NULL)
/*inline int read(){
rg int x = 0, f = 1;
rg char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x * f;
}*/
const int N = 5e3 + 7;
int n, m;
vector<vector<int> > G(N);
struct edge{
int u, v;
}e[N];
void dfs1(int u, int fa){
cout << u << " ";
for (auto v : G[u]){
if (v == fa) continue;
dfs1(v, u);
}
}
int ans[N], res[N];
int cnt, delnum;
bool vis[N];
inline bool check(int u, int v){
if (e[delnum].u == u && e[delnum].v == v || e[delnum].u == v && e[delnum].v == u) return true;
return false;
}
void dfs2(int u, int fa){
res[++cnt] = u;
vis[u] = true;
for (auto v : G[u]){
if (vis[v] || check(u, v)) continue;
dfs2(v, u);
}
}
inline bool cmp(){
for (int i(1); i <= n; ++i)
if (ans[i] != res[i])
return ans[i] > res[i];
return false;
}
int main(){
IOS;
//freopen("in.txt", "r", stdin);
cin >> n >> m;
for (int i(1); i <= m; ++i){
int u, v; cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
e[i].u = u, e[i].v = v;
}
for (int i(1); i <= n; ++i)
sort(G[i].begin(), G[i].end());
if (m == n - 1)
dfs1(1, 0);
else {
memset(ans, 0x3f, sizeof(ans));
for (int i(1); i <= m; ++i){
memset(res, 0, sizeof(res));
memset(vis, 0, sizeof(vis));
cnt = 0;
delnum = i;
dfs2(1, 0);
if (cmp() && cnt == n)
for (int i(1); i <= n; ++i)
ans[i] = res[i];
}
for (int i(1); i <= n; ++i) cout << ans[i] << " ";
}
return 0;
}
luogu P1453 城市环路
题意
给定一颗基环树,每个点给定一个权值,求一个点的集合,满足相邻的点不同时存在,使得该集合内权值和最大。
分析
对于在环上某两个相邻的点,答案有三种情况,两个点都不在集合里,或者两个点只有一个在集合里
不妨考虑dp,这两个点分别向外dp一次,dp的第二维存一下该点是否被选,1表示选,0表示不选
设环上两个相邻的点为,那么答案就是
直接囊括了三种情况的最大值。
代码
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
#define rg register
#define IOS ios::sync_with_stdio(false);cin.tie(NULL),cout.tie(NULL)
/*inline int read(){
rg int x = 0, f = 1;
rg char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x * f;
}*/
const int N = 1e5 + 7;
struct Edge{
int next, to;
}e[N << 1];
int head[N], cnt;
inline void add(int u, int v){
e[++cnt].to = v, e[cnt].next = head[u];
head[u] = cnt;
}
int fa[N];
int find(int x){
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int n, p[N];
double k;
int rt1, rt2;
int dp[N][2];
void treedp(int u, int fath){
dp[u][0] = 0, dp[u][1] = p[u];
for (int i(head[u]); i; i = e[i].next){
int v = e[i].to;
if (v == fath) continue;
treedp(v, u);
dp[u][1] += dp[v][0];
dp[u][0] += max(dp[v][0], dp[v][1]);
}
}
int main(){
IOS;
//freopen("in.txt", "r", stdin);
cin >> n;
for (int i(0); i < n; ++i) cin >> p[i], fa[i] = i;
for (int i(1); i <= n; ++i){
int u, v; cin >> u >> v;
if (find(u) == find(v)){
rt1 = u, rt2 = v;
continue;
}
add(u, v), add(v, u);
fa[find(u)] = find(v);
}
cin >> k;
treedp(rt1, -1);
int ans = dp[rt1][0];
treedp(rt2, -1);
cout << fixed << setprecision(1) << max(ans, dp[rt2][0]) * k;
return 0;
}
luogu P4381 Island
题意
求多颗基环树的直径之和。
分析
对于一颗基环树而言,其直径可以考虑两种情况,一种是经过环上的点,一种不经过环上的点。
那么对于每一颗基环树,找出环,对于环上的每一点向外遍历(不走环上),求出向外扩展的最大长度,同时把子树里面的直径也找出来
然后的想法就是在环上找两个点,使得他们在环上的距离 + 他们各自扩展的最大长度之和最大既是答案。
暴力的找肯定是超时,考虑前缀和优化,环上的距离可以用前缀和的差表示,环上第i个点到第j个点的距离为
那么答案就是, 不难发现可以分开考虑,即可以用单调队列优化。
代码
#include <iostream>
#include <cstdio>
using namespace std;
#define rg register
#define IOS ios::sync_with_stdio(false);cin.tie(NULL),cout.tie(NULL)
/*inline int read(){
rg int x = 0, f = 1;
rg char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x * f;
}*/
#define ll long long
const int N = 1e6 + 7;
int head[N], tot = 1;
struct Edge{
int next, to, w;
}e[N << 1];
inline void add(int u, int v, int w){
e[++tot].to = v, e[tot].w = w, e[tot].next = head[u];
head[u] = tot;
}
bool on_circle[N];
ll fa[N], loop[N], vis[N], sum[N << 1], pointc, firste, cnt;
bool dfs(int u){
vis[u] = 1;
bool res = false;
for (int i(head[u]); i; i = e[i].next){
int v = e[i].to;
if (i == fa[u]) continue;
if (!vis[v])
fa[v] = i ^ 1, res |= dfs(v);
else if (vis[v] == 1){
pointc = v;
res = true;
loop[++cnt] = v; on_circle[v] = true;
firste = i;
}
}
vis[u] = 2;
if (u == pointc) res = false;
if (res) loop[++cnt] = u, on_circle[u] = true;
return res;
}
ll temp, f[N];
void treedp(int u, int fath){
for (int i(head[u]); i; i = e[i].next){
int v = e[i].to;
if (v == fath || on_circle[v]) continue;
treedp(v, u);
temp = max(temp, f[u] + f[v] + e[i].w);
f[u] = max(f[u], f[v] + e[i].w);
}
}
ll g[N], q[N];
ll cal(){
for (int i(1); i <= cnt + 1; ++i) sum[i] = sum[i - 1] + e[fa[loop[i - 1]]].w;
for (int i(cnt + 2); i <= 2 * cnt; ++i) sum[i] = sum[i - 1] + sum[i - cnt] - sum[i - cnt - 1], loop[i] = loop[i - cnt];
loop[cnt + 1] = loop[1];
for (int i(1); i <= cnt * 2; ++i) g[i] = f[loop[i]] - sum[i];
int l = 1, r = 1;
ll res = 0;
for (int i(1); i <= 2 * cnt; ++i) q[i] = 0; q[1] = 1;
for (int i(2); i <= 2 * cnt; ++i){
while (l < r && i - q[l] >= cnt) ++l;
res = max(res, g[q[l]] + f[loop[i]] + sum[i]);
while (l < r && g[i] >= g[q[r]]) --r;
q[++r] = i;
}
return res;
}
int main(){
IOS;
//freopen("in.txt", "r", stdin);
int n; cin >> n;
for (int i(1); i <= n; ++i){
int v, l; cin >> v >> l;
add(i, v, l), add(v, i, l);
}
ll finalans = 0;
for (int i(1); i <= n; ++i){
if (vis[i]) continue;
cnt = 0, temp = 0;
dfs(i);
fa[loop[1]] = firste;
for (int j(1); j <= cnt; ++j)
treedp (loop[j], 0);
ll ans = max(temp, cal());
finalans += ans;
}
cout << finalans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通