树上及图论做题记录
\(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;
}