[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 second
vector < 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 long
const 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 long
const 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 long
const 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;
}