树上及图论做题记录

\(CF85E\)

求二分图,使得每组内两个点之间的曼哈顿距离的最大值最小,并求方案数。
\(solution:\) 二分 \(len\)\(dis>len\) 的点对连边,等价于判断新图是否为二分图(染色法)。方案数为 \(2\) 的连通块数次方。

\(CF1100E\)

给定有向图,改变某些边的方向,成为有向无环图。求改变边方向的方案,使得所选边边权的最大值最小。
\(solution:\) 二分答案,对大于 \(mid\) 的边,拓扑排序判断是否有负环。具体方法:进队的点的个数 \(sum\),判断是否等于 \(n\)
\(sum < n\) 有环,\(sum = n\) 无环。输出方案将构成环的边全部反向,相当于拓扑图中从后向前的边。所以在合法的 \(mid\) 下遍历一遍所有的边,判一下拓扑序即可,注意边权要小于等于 \(mid\)

const int N = 1e5 + 10;
int n, m, tim; bool vis[N], t[N];
int h[N], e[N << 1], f[N << 1], w[N << 1], ne[N << 1], idx;
int in[N], dfn[N], ans[N], cnt, l, r, maxs;

inline void add(int a, int b, int c){ f[++ idx] = a, e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ; }

inline int topo(int now){
    int sum = 0; queue<int> q;
    for(int i = 1; i <= n; i ++) if(!in[i]) q.push(i);
    while(!q.empty()){
        int x = q.front(); q.pop(); sum ++, dfn[x] = ++ tim;
        for(int i = h[x]; i; i = ne[i]){
            int j = e[i]; if(w[i] <= now) continue;
            if(!(-- in[j])) q.push(j);
        }
    } return sum >= n;
}

inline bool check(int now){
    memset(in, 0, sizeof(in)); memset(dfn, 0, sizeof(dfn)); tim = 0;
    for(int i = 1; i <= m; i ++) if(w[i] > now) in[e[i]] ++;
    if(!topo(now)) return 0; cnt = 0;
    for(int i = 1; i <= n; i ++) if(!dfn[i]) dfn[i] = ++ tim;
    for(int i = 1; i <= m; i ++){
        int u = f[i], v = e[i];
        if(w[i] <= now && dfn[u] > dfn[v]) ans[++ cnt] = i;
    }return 1;
}

signed main(){
    read(n), read(m);// memset(h, - 1, sizeof h);
    for(int i = 1, u, v, w; i <= m; i ++){ read(u), read(v), read(w); add(u, v, w); r = max(r, w); }
    while(l <= r){ int mid = l + r >> 1; if(check(mid)) maxs = mid, r = mid - 1; else l = mid + 1;}
    print(maxs), putchar(' '), print(cnt), puts(""); sort(ans + 1, ans + cnt + 1);
    for(int i = 1; i <= cnt; i ++) print(ans[i]), putchar(' ');
    return 0;
}

\(P3629 [APIO2010]\) 巡逻

给你一棵树,边权都为 \(1\),新建 \(k\) 条道路,要求从源点出发,每条道路必须经过至少一次,新建的道路必须仅经过一次,求可能的新建道路的方案,使遍历路径最短。
\(solotion:\) \(k=1\)\(ans = 2(n - 1) - d + 1\) 自证不难(\(d\) 为直径)。当 \(k=2\) 时,对 \(k=1\) 时的直径边权赋值为 \(-1\),这时再求直径,构成第二个环,\(ans = 2(n - 1) - d1 - d2 + 2\)。本题主要考查了两种求树的直径的方法 \(DP\) 可以处理负权,\(dfs\) 可以记录路径。

#include<set>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long 
using namespace std;

template<class T> inline void read(T &x){
    x = 0; register char c = getchar(); register bool f = 0;
    while(!isdigit(c)) f ^= c == '-', c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
    if(f) x = -x;
}

template<class T> inline void print(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) print(x / 10);
    putchar('0' + x % 10);
}
const int N = 1e5 + 10, inf = 1e9 + 10;
int n, k, id, D;
int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;
int dis[N], f[N]; struct To{ int x, id; }to[N];

inline void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++; }
inline void dfs(int u, int fa){
    for(int i = h[u]; ~ i; i = ne[i]){
        int j = e[i]; if(j == fa) continue;
        dis[j] = dis[u] + w[i]; dfs(j, u);
    }
}
inline void dfs2(int u, int fa){
    for(int i = h[u]; ~ i; i = ne[i]){
        int j = e[i]; if(j == fa) continue;
        dis[j] = dis[u] + w[i]; to[j] = {u, i}; dfs2(j, u);
    }
}
inline int k1(){
    memset(dis, inf, sizeof dis); dis[1] = 0; dfs(1, 0); int d = -1;
    for(int i = 1; i <= n; i ++) if(dis[i] > d) d = dis[i], id = i;
    memset(dis, inf, sizeof dis); dis[id] = 0; dfs2(id, 0);
    for(int i = 1; i <= n; i ++) if(dis[i] > d) d = dis[i], id = i; return (n - 1 << 1) - d + 1; 
}
inline void dp(int u, int fa){
    f[u] = 0; for(int i = h[u]; ~ i; i = ne[i]){
        int j = e[i]; if(j == fa) continue; dp(j, u);
        D = max(D, f[u] + f[j] + w[i]); f[u] = max(f[u], f[j] + w[i]);
    }
}

signed main(){
    read(n), read(k); memset(h, -1, sizeof h);
    for(int i = 1, u, v; i <= n - 1; i ++){ read(u), read(v); add(u, v, 1), add(v, u, 1); }
    if(k == 1) return print(k1()), 0; else if(k == 2){
        int tem = k1(); while(to[id].x != 0){ w[to[id].id] = -1; w[to[id].id ^ 1] = -1; id = to[id].x; } 
        dp(1, 0); return print(tem - D + 1), 0;
    }
}

\(CF711D\)

给一个 \(n\) 个点 \(n\) 条边的基环树。每条边指定一个方向,问有多少种方案不出现环。
咕了。\(ans = 2^{n-\sum w[i]} * \prod 2^{w[i]}-2\)

\(P2607 [ZJOI2008]\) 骑士

给定 \(n\) 个关系,\(a,b\) 不能同时选择,问最大权值和。
\(solution:\) 基环树,找环。每次断环上一条边变成树,强制断边的两个断点不同时选择,跑树形 \(dp\),变成了没有上司的舞会。

const int N = 2e6 + 10;
int n, root, val[N], h[N], e[N], ne[N], idx, f[N][2], ans, vis[N], fa[N];

inline void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++; }

inline void dfs(int u){
    vis[u] = 1; f[u][0] = 0, f[u][1] = val[u];
    for(int i = h[u]; ~ i; i = ne[i]){
        int j = e[i]; if(j != root){
            dfs(j); f[u][0] += max(f[j][1], f[j][0]); f[u][1] += f[j][0];
        }else f[j][1] = -N; 
    }  
}

inline void find(int x){
    vis[x] = 1; root = x; while(!vis[fa[root]]){
        root = fa[root]; vis[root] = 1;
    } dfs(root); int t = max(f[root][0], f[root][1]);
    vis[root] = 1; root = fa[root]; dfs(root); ans += max(t, max(f[root][0], f[root][1])); return;
}

signed main(){ memset(h, -1, sizeof h);
    read(n); for(int i = 1, x; i <= n; i ++){ read(val[i]), read(x); add(x, i); fa[i] = x; }
    for(int i = 1; i <= n; i ++) if(!vis[i]) find(i);
    print(ans); return 0;
}
posted @ 2022-07-28 11:55  Altwilio  阅读(8)  评论(0编辑  收藏  举报