基环树问题 解题报告

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表示不选
设环上两个相邻的点为rt1rt2,那么答案就是max(dp[rt1][0],dp[rt2][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

题意

求多颗基环树的直径之和。

分析

对于一颗基环树而言,其直径可以考虑两种情况,一种是经过环上的点,一种不经过环上的点。
那么对于每一颗基环树,找出环,对于环上的每一点向外遍历(不走环上),求出向外扩展的最大长度,同时把子树里面的直径也找出来
然后的想法就是在环上找两个点,使得他们在环上的距离 + 他们各自扩展的最大长度之和最大既是答案。
暴力的找肯定是n2超时,考虑前缀和优化,环上的距离可以用前缀和的差表示,环上第i个点到第j个点的距离为sumjsumi
那么答案就是fj+fi+sumjsumi, 不难发现ij可以分开考虑,即可以用单调队列优化。

代码

#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;
}
posted @   ancer  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示