[ZJOI2007] 时态同步
分析:
不难发现,中断点就是叶子节点,
首先,所有叶子节点的高度肯定就等于最深的那个叶子节点的深度。
且不可能去调整最深的叶子结点的深度了。
这样经过一遍dfs之后我们可以计算出每个叶子需要增加的高度。
然后我们发现,如果能调整高度,肯定是要调整尽可能高的那条边,因为这条边调整之后,他所属的叶子节点的深度全部都会加1
于是我们发现,一条边所能调整的最大值,一定是他全部儿子节点所需调整的最小值。
在通过一遍从下往上dfs可以求出每条边调整的最小值
然后再跑一遍从上往下dfs累计答案即可。
Code
#include<bits/stdc++.h>
using namespace std;#define int long long const int N = 5e5+100;
typedef pair < int , int > pii;
#define fi first
#define se second
#define pb push_back
int n;
int rt;
vector < pii > a[N];
int f[N];
int s[N],maxs;
int mins[N];void Dfs(int x,int faa){for (int i = 0; i < a[x].size(); i++){int y = a[x][i].fi,v = a[x][i].se;if (y == faa) continue;s[y] = s[x]+v;Dfs(y,x);}maxs = max(maxs,s[x]);
}
void Dp1(int x,int faa){bool f = 1;mins[x] = 1e18;for (int i = 0; i < a[x].size(); i++){int y = a[x][i].fi , v = a[x][i].se;if (y == faa) continue;f = 0;Dp1(y,x);mins[x] = min(mins[x],mins[y]);}if (f) mins[x] = maxs-s[x];
}int ans = 0;
void Dp2(int x,int faa,int sum){for (int i = 0; i < a[x].size(); i++){int y = a[x][i].fi; if (y == faa) continue;ans+=mins[y]-sum;Dp2(y,x,mins[y]);}
}signed main(){cin.tie(0);ios::sync_with_stdio(false);cin>>n>>rt;for (int i = 1,x,y,z; i < n; i++)cin>>x>>y>>z,a[x].pb({y,z}),a[y].pb({x,z});Dfs(rt,0);Dp1(rt,0);Dp2(rt,0,0);cout<<ans<<endl;
}
有线电视网
分析:
比较明显是一个树上背包问题。
人就相当于是物品,钱就相当于是价值。
f [ i ] [ j ] f[i][j] f[i][j]表示以i为根的树,选j个观众所能获得的最大金币值
可以如下转移(类似分组背包):
f [ x ] [ j ] = m a x ( f [ x ] [ j ] , f [ x ] [ j − i ] + f [ y ] [ i ] − e d . v ) f[x][j] = max(f[x][j],f[x][j-i]+f[y][i]-ed.v) f[x][j]=max(f[x][j],f[x][j−i]+f[y][i]−ed.v)
答案就是 f [ 1 ] [ i ] > = 0 f[1][i]>=0 f[1][i]>=0的最大i值
Code
#include<bits/stdc++.h>
using namespace std;const int N = 3e3+100,inf = 3e5+100;
int n,m;
typedef pair < int , int > pii;
#define pb push_back
#define fi first
#define se secondvector < pii > a[N];
int f[N][N];
int v[N];int Dfs(int x,int faa){bool ff = 1;int sum = 0;f[x][0] = 0;for (int i = 0; i < a[x].size(); i++){int y = a[x][i].fi , v = a[x][i].se;if (y == faa) continue; ff = 0;int t = Dfs(y,x); sum+=t;for (int vv = sum; vv >= 1; vv--)for (int j = 1; j <= t; j++)if (vv >= j) f[x][vv] = max(f[x][vv],f[x][vv-j]+f[y][j]-v);}if (ff){f[x][0] = 0;f[x][1] = v[x]; return 1;}return sum;
}int main(){cin.tie(0);ios::sync_with_stdio(false);cin>>n>>m;for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++) f[i][j] = -inf;for (int i = 1; i <= n-m; i++){int k; cin>>k;for (int j = 1,y,z; j <= k; j++)cin>>y>>z,a[i].pb({y,z}),a[y].pb({i,z});}for (int i = n-m+1; i <= n; i++) cin>>v[i];Dfs(1,0);int Max = 0;for (int i = n; i >= 0; i--)if(f[1][i]>=0) {cout<<i<<endl;return 0;}return 0;
}
Nearby Cows G
分析:
题目要我们求离每一个点距离不超过k的点的点权和
其实是一个比较明显的换根dp的题目
这是一颗无根树,但是我们先假设树的根节点为1号点
f [ i ] [ j ] f[i][j] f[i][j]表示以i为根节点,他的子树里距离当前节点不超过j的点权和
于是就有下面很显然的转移方程:
f [ x ] [ j ] = f [ y ] [ j − 1 ] + v [ x ] f[x][j]=f[y][j-1]+v[x] f[x][j]=f[y][j−1]+v[x]
这样子跑完一遍dfs之后可以得到以1为根的答案
但是显然不是每个点都得到了答案
所以我们需要跑一遍换根dp,求出每一个点的答案
其实换根dp的关键,就是看当前节点根父亲节点之间的关系,如何通过父亲节点的答案转移到当前子节点的答案
我们这个时候如果 令f的定义中的子树去掉
那么 f [ x ] [ k ] f[x][k] f[x][k]显然就是每个点的答案。
那么我们如何转移,才能将上面的定义转化为这个定义呢?
关键就是找出非子树内的距离当前点不超过k的点的权值和是多少
我们发现这个可以通过父亲来转移
f [ y ] [ k ] + = f [ x ] [ k − 1 ] f[y][k]+=f[x][k-1] f[y][k]+=f[x][k−1]
但是我们发现这个时候会有一点重复,因为 f [ x ] [ k − 1 ] f[x][k-1] f[x][k−1]有一部分答案是包含当前子树的,而当前子树的答案我们已经有了
所以还需要减去 f [ y ] [ k − 2 ] f[y][k-2] f[y][k−2]
综上, f [ y ] [ k ] + = f [ x ] [ k − 1 ] − f [ y ] [ k − 2 ] f[y][k]+=f[x][k-1]-f[y][k-2] f[y][k]+=f[x][k−1]−f[y][k−2]
#include<bits/stdc++.h>
using namespace std;#define int long longconst int N = 1e5+100;
int f[N][30];
int n,k;
vector < int > a[N];
int ans[N];
#define pb push_back
#define fi first
#define se second
int v[N];void Insert(int x,int y){a[x].pb(y);
}void Dfs(int x,int faa){for (int i = 0; i < a[x].size(); i++){int y = a[x][i]; if (y == faa) continue;Dfs(y,x);for (int kk = 1; kk <= k; kk++)f[x][kk]+=f[y][kk-1];}for (int i = 0; i <= k; i++) f[x][i]+=v[x];
}void Dp(int x,int faa){ans[x] = f[x][k];for (int i = 0; i < a[x].size(); i++){int y = a[x][i]; if (y == faa) continue;for (int kk = k; kk >= 1; kk--){f[y][kk]+=f[x][kk-1];if (kk >= 2) f[y][kk]-=f[y][kk-2];}Dp(y,x);}
}signed main(){cin.tie(0);ios::sync_with_stdio(0);cin>>n>>k;for (int i = 1,x,y; i < n; i++)cin>>x>>y,Insert(x,y),Insert(y,x);for (int i = 1; i <= n; i++) cin>>v[i];Dfs(1,0);Dp(1,0);for (int i = 1; i <= n; i++) cout<<ans[i]<<endl;return 0;
}
发现环
分析:
基环树找环
用拓扑排序去找环
Code
#include<bits/stdc++.h>
using namespace std;const int N = 1e5+100;
int du[N];
vector < int > a[N];
#define pb push_back
int n;
bool vi[N];int main(){cin>>n;for (int i = 1,x,y; i <= n; i++)cin>>x>>y,a[x].pb(y),a[y].pb(x),du[x]++,du[y]++;queue < int > q;for (int i = 1; i <= n; i++)if (du[i] == 1) q.push(i);while (q.size()){int x = q.front(); q.pop();vi[x] = 1;for (int i = 0; i < a[x].size(); i++){int y = a[x][i];du[y]--;if (du[y] == 1) q.push(y);}}for (int i = 1; i <= n; i++)if (!vi[i]) cout<<i<<' ';return 0;
}
城市环路
分析:
这道题其实本来应该是一个基环树dp的题
但是这边没采用这个方法
其实我们发现,隔一个人选一个点,有点类似于奇偶性
只能选取奇偶性相同的点。
那么对于环上的点,其实我们只需要找出任意两个相连的点,将两个点分别作为树的根跑一遍树形dp即可
f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示选/不选当前点的最优答案
那么最终答案就是 m a x ( f [ x ] [ 0 ] , f [ y ] [ 0 ] ) max(f[x][0],f[y][0]) max(f[x][0],f[y][0])
限定两个点分别不选,是因为其实这两个点在环上是连着的,这样一定满足条件。
找出在环上的任意两个点可以采用并查集
最后一个加边使形成一个环,此时两个点一定处在同一集合
#include<bits/stdc++.h>
using namespace std;#define int long longconst int N = 1e6+100;
int fa[N];
vector < int > a[N];
#define pb push_back
int n;
int v[N];
int f[N][3];
double k;int getfa(int x){return fa[x] == x?x:fa[x] = getfa(fa[x]);
}void Dp(int x,int faa){bool ff = 1;for (int i = 0; i < a[x].size(); i++){int y = a[x][i]; if (y == faa) continue;ff = 0;Dp(y,x);f[x][1]+=f[y][0];f[x][0]+=max(f[y][1],f[y][0]);}f[x][1]+=v[x];
}signed main(){cin.tie(0);ios::sync_with_stdio(false);cin>>n;for (int i = 1; i <= n; i++) fa[i] = i;int st,ed;for (int i = 1; i <= n; i++){int x,y; cin>>v[i]>>y;int X = getfa(i) , Y = getfa(y);if (X == Y){st = i; ed = y; continue;}a[i].pb(y); a[y].pb(i);fa[X] = Y;}Dp(st,0);int ans = f[st][0];memset(f,0,sizeof f);Dp(ed,0);ans = max(ans,f[ed][0]);cout<<ans;return 0;
}
骑士
分析:
这道题跟上一题基本一样
但是需要注意的是这道题可能是基环树森林
对于森林里的每个数都累加一遍答案即可
Code
#include<bits/stdc++.h>
using namespace std;#define int long longconst int N = 1e6+100;
int fa[N];
vector < int > a[N];
#define pb push_back
int n;
int v[N];
int f[N][3];
bool vi[N];
int st[N],ed[N];
int cnt = 0;int getfa(int x){return fa[x] == x?x:fa[x] = getfa(fa[x]);
}vector < int > live;
void Dp(int x,int faa){live.pb(x);bool ff = 1;for (int i = 0; i < a[x].size(); i++){int y = a[x][i]; if (y == faa) continue;ff = 0;Dp(y,x);f[x][1]+=f[y][0];f[x][0]+=max(f[y][1],f[y][0]);}f[x][1]+=v[x];
}signed main(){cin.tie(0);ios::sync_with_stdio(false);cin>>n;for (int i = 1; i <= n; i++) fa[i] = i;
// int st,ed;for (int i = 1; i <= n; i++){int x,y; cin>>v[i]>>y;int X = getfa(i) , Y = getfa(y);if (X == Y){st[++cnt] = i; ed[cnt] = y; continue;
// st = i; ed = y; continue;}a[i].pb(y); a[y].pb(i);fa[X] = Y;}int ans = 0;for (int i = 1; i <= cnt; i++){Dp(st[i],0);int now = f[st[i]][0];for (int j = 0; j < live.size(); j++) f[live[j]][0] = f[live[j]][1] = 0;live.clear();Dp(ed[i],0); now = max(now,f[ed[i]][0]);for (int j = 0; j < live.size(); j++) f[live[j]][0] = f[live[j]][1] = 0;live.clear();ans+=now;}cout<<ans;return 0;
}