树形 dp 与树上问题
NFLS 集训笔记 20220802 - 树形 dp 进阶与树上问题综合
\(\text{By DaiRuiChen007}\)
I. 洛谷[P2585] - 三色二叉树
思路分析
简单题,建出树后暴力枚举当前节点和其儿子的染色情况,\(dp_{u,c}\) 表示染完 \(u\) 为根的所有子树,且 \(u\) 的颜色是 \(c\) 时的答案,暴力转移即可
时间复杂度 \(\Theta(n)\)
总结:
本题较易,没有什么比较难的坑点,大暴力即可
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5e5+1,INF=1e18;
int dp[MAXN][3][2],son[MAXN],tot=1;
char tr[MAXN];
vector <int> edge[MAXN];
inline void build(int p) {
for(int i=1;i<=son[p];++i) {
edge[p].push_back(++tot);
build(tot);
}
}
inline void dfs(int p) {
for(int v:edge[p]) dfs(v);
switch(son[p]) {
case 0: {
for(int col:{0,1,2}) {
for(int op:{0,1}) {
dp[p][col][op]=(col?0ll:1ll);
}
}
break;
}
case 1: {
int u=edge[p][0];
for(int col1:{0,1,2}) {
for(int col2:{0,1,2}) {
if(col1==col2) continue;
dp[p][col1][0]=min(dp[p][col1][0],dp[u][col2][0]+(col1?0ll:1ll));
dp[p][col1][1]=max(dp[p][col1][1],dp[u][col2][1]+(col1?0ll:1ll));
}
}
break;
}
case 2: {
int u=edge[p][0],v=edge[p][1];
for(int col1:{0,1,2}) {
for(int col2:{0,1,2}) {
for(int col3:{0,1,2}) {
if(col1==col2||col2==col3||col3==col1) continue;
dp[p][col1][0]=min(dp[p][col1][0],dp[u][col2][0]+dp[v][col3][0]+(col1?0ll:1ll));
dp[p][col1][1]=max(dp[p][col1][1],dp[u][col2][1]+dp[v][col3][1]+(col1?0ll:1ll));
}
}
}
break;
}
}
}
signed main() {
scanf("%s",tr+1);
int n=strlen(tr+1);
for(int i=1;i<=n;++i) son[i]=tr[i]-'0';
build(1);
for(int i=1;i<=n;++i) {
for(int col:{0,1,2}) {
dp[i][col][0]=INF;
dp[i][col][1]=-INF;
}
}
dfs(1);
printf("%lld ",max(dp[1][0][1],max(dp[1][1][1],dp[1][2][1])));
printf("%lld ",min(dp[1][0][0],min(dp[1][1][0],dp[1][2][0])));
puts("");
return 0;
}
II. [洛谷3177] - 树上染色
思路分析
考虑每条 \(u\to v\) 的边对答案的贡献,设 \(u\) 是 \(v\) 的父亲,且以 \(v\) 为根的子树中选择了 \(x\) 个黑点,则 \(u\to v\) 被计算的次数应该是 \(x\times(k-x)+(siz_v-x)\times[(n-k)-(siz_v-x)]\)
设 \(dp_{u,x}\) 表示以 \(u\) 为根的子树中选择 \(x\) 个黑点,对答案的最大贡献,然后转移的时候枚举每个子节点 \(v\) 内的子结点个数,然后计算 \(u\to v\) 对答案的贡献即可
边界条件: \(u\) 染色或不染色,\(dp_{u,0}=dp_{u,1}=0\)
然后枚举子节点背包转移即可
时间复杂度 \(\Theta(nk^2)\)
总结:
树上背包模板,考虑边的贡献的思路第一次见可能会觉得比较高妙,但再用就会觉得非常基础的一种 trick
状态设计略有不同,注意一下边界条件的处理
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2001;
int dp[MAXN][MAXN],siz[MAXN],n,k;
struct node {
int des,val;
};
vector <node> edge[MAXN];
inline void dfs(int p,int f) {
siz[p]=1;
dp[p][0]=dp[p][1]=0;
for(auto e:edge[p]) {
int v=e.des;
if(v==f) continue;
dfs(v,p);
siz[p]+=siz[v];
for(int i=min(siz[p],k);i>=0;--i) {
for(int j=0;j<=min(i,siz[v]);++j) {
dp[p][i]=max(dp[p][i],dp[p][i-j]+dp[v][j]+e.val*((k-j)*j+(siz[v]-j)*(n-k-(siz[v]-j))));
}
}
}
}
signed main() {
memset(dp,-0x3f,sizeof(dp));
scanf("%lld%lld",&n,&k);
for(int i=1;i<n;++i) {
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
edge[u].push_back((node){v,w});
edge[v].push_back((node){u,w});
}
dfs(1,0);
printf("%lld\n",dp[1][k]);
return 0;
}
III. [BZOJ2466] - 树
思路分析
因为每个节点按/不按都会影响其父亲和儿子的值,所以我们可以设计状态 \(dp_{u,0/1,0/1}\) 表示以 \(u\) 为根的子树,除了 \(u\) 全部点亮,且 \(u\) 没有按/按了,节点 \(u\) 没有亮/亮了的最少方案数
边界条件:\(dp_{u,0,0}=0,dp_{u,1,1}=1,dp_{u,0,1}=\infty,dp_{u,1,0}=\infty\)
然后枚举 \(u\) 的每一个儿子 \(v\) 并转移:
我们以 \(dp_{u,0,0}\) 的转移为例
因为 \(u\) 没有按,所以 \(u\) 的每个儿子都应该是亮的,如果 \(v\) 按了,那么在这之前 \(u\) 应该是亮的,否则 \(u\) 应该是暗的
所以转移如下:
\]
其他状态的转移类似
时间复杂度 \(\Theta(n)\)
答案为 \(\min(dp_{root,0,1},dp_{root,1,1})\)
注意每次转移 \(dp\) 的时候会更新 \(dp\) 的值,我们要先记录之前的 \(dp\) 值然后转移
总结:
状态设计需要注意只保留与转移有关的状态
注意转移之后更新的数不能影响答案
代码呈现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=101,INF=1e9;
int dp[MAXN][2][2];
//node-id,pressed,lighted
vector <int> edge[MAXN];
inline void dfs(int p,int f) {
dp[p][0][0]=0,dp[p][1][1]=1;
dp[p][0][1]=dp[p][1][0]=INF;
for(int v:edge[p]) {
if(v==f) continue;
dfs(v,p);
int tmp[2][2];
for(int i:{0,1}) for(int j:{0,1}) tmp[i][j]=dp[p][i][j];
dp[p][0][0]=min(tmp[0][0]+dp[v][0][1],tmp[0][1]+dp[v][1][1]);
dp[p][0][1]=min(tmp[0][0]+dp[v][1][1],tmp[0][1]+dp[v][0][1]);
dp[p][1][0]=min(tmp[1][0]+dp[v][0][0],tmp[1][1]+dp[v][1][0]);
dp[p][1][1]=min(tmp[1][0]+dp[v][1][0],tmp[1][1]+dp[v][0][0]);
}
}
signed main() {
while(true) {
int n;
scanf("%lld",&n);
if(!n) break;
for(int i=1;i<n;++i) {
int u,v;
scanf("%lld%lld",&u,&v);
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1,0);
printf("%lld\n",min(dp[1][0][1],dp[1][1][1]));
for(int i=1;i<=n;++i) edge[i].clear();
}
return 0;
}
IV. [洛谷4037] - 魔兽地图
思路分析
树形 dp 高妙题,由于每个物品可能有一些要交给父亲去合成所以不能计入贡献,所以我们可以设 \(dp_{u,k,w}\) 为 \(u\) 物品有 \(k\) 件交给父亲合成,总花费为 \(w\) 的答案
对于叶节点,直接枚举购买量和上交量即可
对于非叶节点,枚举购买量,然后对每种购买量从儿子处转移,要求儿子上交对应量的物品并进行背包,最后枚举上交量更新
时间复杂度 \(\Theta(100nm^2)\)
总结:
状态设计比较巧妙
对于每种购买量都做背包再更新答案
注意转移的顺序和边界条件
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=52,MAXM=2001,INF=1e18;
int dp[MAXN][101][MAXM],f[MAXM],n,m;
/*
* dp: node-id give-to-father total-cost
* f: temporary record
*/
int cnt[MAXN],val[MAXN],cost[MAXN],deg[MAXN];
struct node{
int id,cnt;
};
vector <node> item[MAXN];
inline void DP(int p) {
if(!p) {
for(int i=0;i<=m;++i) dp[p][0][i]=0;
for(auto e:item[p]) {
int v=e.id;
DP(v);
for(int i=m;i>=0;--i) {
for(int j=0;j<=i;++j) {
dp[p][0][i]=max(dp[p][0][i],dp[v][0][j]+dp[p][0][i-j]);
}
}
}
return ;
}
if(item[p].empty()) {
cnt[p]=min(cnt[p],m/cost[p]);
for(int i=cnt[p];i>=0;--i) {
for(int j=0;j<=i;++j) {
dp[p][j][i*cost[p]]=max(dp[p][j][i*cost[p]],(i-j)*val[p]);
}
}
return ;
}
cnt[p]=INF;
for(auto e:item[p]) {
int v=e.id;
DP(v);
cost[p]+=e.cnt*cost[v];
cnt[p]=min(cnt[v]/e.cnt,cnt[p]);
}
cnt[p]=min(cnt[p],m/cost[p]);
for(int i=cnt[p];i>=0;--i) {
memset(f,-0x3f,sizeof(f));
f[0]=0;
for(auto e:item[p]) {
int v=e.id;
for(int j=m;j>=0;--j) {
int tmp=-INF;
//先清空再转移
for(int k=0;k<=j;++k) {
tmp=max(tmp,f[j-k]+dp[v][i*e.cnt][k]);
}
f[j]=tmp;
}
}
for(int j=0;j<=m;++j) {
for(int k=0;k<=i;++k) {
dp[p][k][j]=max(dp[p][k][j],f[j]+val[p]*(i-k));
}
}
}
}
signed main() {
memset(dp,-0x3f,sizeof(dp));
cin>>n>>m;
for(int i=1;i<=n;++i) {
cin>>val[i];
char op;
cin>>op;
if(op=='A') {
int c;
cin>>c;
while(c--) {
int id,x;
cin>>id>>x;
item[i].push_back((node){id,x});
++deg[id];
}
} else cin>>cost[i]>>cnt[i];
}
for(int i=1;i<=n;++i) if(!deg[i]) item[0].push_back((node){i,0});
DP(0);
int res=-INF;
for(int i=0;i<=m;++i) res=max(res,dp[0][0][i]);
cout<<res<<'\n';
return 0;
}
V. [洛谷4649] - 训练路径
思路分析
反面考虑,计算最多能加几条非树边使图上没有偶环
如果某条非树边 \((u,v)\) 添加后使得 \(u\to \operatorname{LCA}(u,v)\to v\to u\) 的长度为偶数,则不能必选择 \((u,v)\)
只考虑添加 \((u,v)\) 使得 \(u\to \operatorname{LCA}(u,v) \to v\to u\) 的长度为奇数的边 \((u,v)\)
如果两条边 \((u,v)\) 和 \((x,y)\) 的树上路径有交集,如下图所示,也会形成一个偶环:
所以我们的目标就是在树上调价若干条不相交的非树边使得其权值和最大
考虑在添加某条非树边 \((u,v)\) 后把 \(u\to v\) 的所有边都删掉,然后分成若干棵子树进行 dp
我们把每条非树边 \((u,v)\) 在 \(\operatorname{LCA}(u,v)\) 处考虑,设 \(dp_{p,\mathbf S}\) 表示以 \(u\) 为根的子树,且 \(p\) 到集合 \(\mathbf S\)(状态压缩)中的儿子的边已经被删掉后能选择的最优答案
那么对于一条路径 \((u,v)\) 满足 \(\operatorname{LCA}(u,v)=p\) 他能带来的价值为:
\]
设 \(u\to v\) 的路径包含 \(x\to p\to y\) 且 \(x,y\) 均为 \(p\) 的儿子,我们可以用用这个价值去转移:
dp_{p,S}\gets value(u,v)+dp_{p,\mathbf S\cup\{x,y\}}
\]
最终的答案为 \(\sum w(u,v)-dp_{1,\varnothing}\)
时间复杂度 \(\Theta(m\log n+n2^{|\mathbf S|})\)
总结:
神仙题
观察性质转化成生成仙人掌
然后状压树上 dp
很强
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1001;
vector <int> tree[MAXN];
struct node {
int src,des,val;
};
vector <node> vec,link[MAXN];
int dep[MAXN],fa[MAXN],siz[MAXN],son[MAXN],root[MAXN],fid[MAXN];
int dp[MAXN][1<<10];
inline int bit(int x) {
return 1<<x;
}
inline void init1(int p,int f) {
fa[p]=f,siz[p]=1,dep[p]=dep[f]+1;
for(int v:tree[p]) {
if(v==f) continue;
init1(v,p);
siz[p]+=siz[v];
if(siz[v]>siz[son[p]]) son[p]=v;
}
}
inline void init2(int p,int r) {
root[p]=r;
if(son[p]) init2(son[p],r);
for(int v:tree[p]) {
if(v==son[p]||v==fa[p]) continue;
init2(v,v);
}
}
inline void init3(int p) {
for(int i=0;i<tree[p].size();++i) {
int v=tree[p][i];
if(v==fa[p]) continue;
fid[v]=bit(i);
init3(v);
}
}
inline int LCA(int u,int v) {
while(root[u]!=root[v]) {
if(dep[root[u]]<dep[root[v]]) swap(u,v);
u=fa[root[u]];
}
return dep[u]>dep[v]?v:u;
}
inline void dfs(int p) {
for(int v:tree[p]) {
if(v==fa[p]) continue;
dfs(v);
}
for(int s=0;s<bit(tree[p].size());++s) {
for(int i=0;i<tree[p].size();++i) {
if(!((s>>i)&1)) dp[p][s]+=dp[tree[p][i]][0];
}
}
for(auto e:link[p]) {
int val=e.val,u=0,v=0;;
if(e.src!=p) {
val+=dp[e.src][0];
for(u=e.src;fa[u]!=p;u=fa[u]) val+=dp[fa[u]][fid[u]];
}
if(e.des!=p) {
val+=dp[e.des][0];
for(v=e.des;fa[v]!=p;v=fa[v]) val+=dp[fa[v]][fid[v]];
}
int t=fid[u]|fid[v];
for(int s=0;s<bit(tree[p].size());++s) {
if(!(s&t)) dp[p][s]=max(dp[p][s],val+dp[p][s|t]);
}
}
}
signed main() {
int n,m,res=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if(!w) {
tree[u].push_back(v);
tree[v].push_back(u);
} else {
res+=w;
vec.push_back((node){u,v,w});
}
}
init1(1,0);
init2(1,1);
init3(1);
for(auto e:vec) {
int lca=LCA(e.src,e.des);
if(!((dep[e.src]+dep[e.des]-dep[lca]*2)&1)) link[lca].push_back(e);
}
dfs(1);
printf("%d\n",res-dp[1][0]);
return 0;
}
VI. [洛谷4381] - 岛屿
思路分析
显然,本题建图之后会形成一棵基环树森林,题意就是求若干棵基环树的直径之和
基环树的路径只有两种,经过环上边的和不经过环上边的
不经过环上边的可以通过以环上每个节点为根做一次树形 dp 求得,同时记录出每个点 \(u\) 向环外最远能拓展多少的路径的长度 \(dp_u\)
考虑经过环的直径,其形态一定是:过 \(u\) 的环外最长链 \(\to\) 环上 \(u\) 到 \(v\) 的距离 \(\to\) 过 \(v\) 的最长链
那么这段距离的长度为:
\]
考虑破环为链然后做一次前缀和用 \(sum_i\) 示第 \(1\) 个点到第 \(i\) 个点的距离
设环的长度为 \(x\),则我们要求的答案应该是:
\]
考虑把 \(u,v\) 拆开:
\]
所以我们只需要枚举 \(v\) 然后以 \(dp_u-sum_u\) 维护一个递减的单调队列即可
时间复杂度 \(\Theta(n)\)
总结:
先处理环外直径显然,考虑将环上问题抽象出来是解题的瓶颈,如果能够看出来问题本质或者想到拆环为链,那么列出式子和单调队列优化都非常显然
处理基环树问题的时候,可以考虑拆换为链然后转成数列问题
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e6+1;
struct node {
int val,des,id;
node() { val=des=id=0; }
node(int _val,int _des,int _id) { val=_val,des=_des,id=_id; }
} sk[MAXN];
vector <node> edge[MAXN];
int dp[MAXN],lst[MAXN],cir[MAXN],x,top,sum[MAXN<<1],w[MAXN<<1],q[MAXN<<1],tot,chain,ans;
bool vis[MAXN],get_circle;
inline void find_circle(int p,node l) {
vis[p]=true;
sk[++top]=l;
for(auto e:edge[p]) {
if(e.id==l.id) continue;
int v=e.des;
if(vis[v]) {
if(get_circle) continue;
int t;
get_circle=true;
do {
t=sk[top].des;
lst[t]=sk[top].val;
cir[++x]=t;
--top;
} while(t!=v&&top);
lst[v]=e.val;
continue;
}
find_circle(v,e);
}
if(!get_circle) --top;
}
inline void dfs(int p,int f) {
vector <int> vec;
for(auto e:edge[p]) {
int v=e.des;
if(v==f||lst[v]) continue;
dfs(v,p);
vec.push_back(dp[v]+e.val);
}
sort(vec.begin(),vec.end(),greater<int>());
if(vec.size()) dp[p]=vec[0],chain=max(chain,vec[0]);
if(vec.size()>1) chain=max(chain,vec[0]+vec[1]);
}
inline int calc(int u) {
int r=(u%x+x)%x;
if(!r) return x;
else return r;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin>>n;
for(int i=1;i<=n;++i) {
int x,w;
cin>>x>>w;
edge[i].push_back((node){w,x,i});
edge[x].push_back((node){w,i,i});
}
for(int i=1;i<=n;++i) {
if(!vis[i]) {
get_circle=false;
top=x=chain=0;
node st(0,i,0);
find_circle(i,st);
for(int i=1;i<=x;++i) dfs(cir[i],0);
for(int i=1;i<=x*2;++i) {
w[i]=dp[cir[calc(i)]];
sum[i]=sum[i-1]+lst[cir[calc(i-1)]];
}
int head=1,tail=0;
for(int i=1;i<=x*2;++i) {
while(head<=tail&&q[head]<=i-x) ++head;
if(head<=tail) chain=max(chain,w[i]+w[q[head]]+sum[i]-sum[q[head]]);
while(head<=tail&&w[q[tail]]-sum[q[tail]]<=w[i]-sum[i]) --tail;
q[++tail]=i;
}
ans+=chain;
}
}
printf("%lld\n",ans);
return 0;
}
VIII. [UOJ#11] - ydc 的大树
思路分析
显然,本题中的白色叶节点都可以删去,直到只留下黑色叶节点
考虑树的一个性质:树上所有点到其他节点的最长路一定经过树的中心(直径中点)
因此考虑在当前的树中找到直径中点,把这个节点提到根,此时所有黑节点到他的朋友就一定要过根
然后枚举某个白色节点 \(u\),显然,以 \(u\) 为根的子树一定会不高兴
然后考虑切掉 \(u\) 的子树会不会让外部其他的点不高兴,将切掉根形成的若干个连通块称为若干个分支
计算出每棵子树中包含的最长链,如果 \(u\) 所在的子树包含其分支中所有的最长链,那么其他分支中的点可能就要过 \(u\) 去找好朋友
如果 \(u\) 所在的分支独占了这整棵树中的所有最长链,那么其他所有的点都要经过 \(u\) 去找好朋友,这些点都会不高兴
如果 \(u\) 所在的分支占领了这棵树中的最长链,或者 \(u\) 独占了这棵树中的次长链,但这棵树中有且仅有另一个分支中有最长链,那么那个包含最长链分支中的节点都过必须 \(u\) 来找好朋友
枚举每个 \(u\) 处理,时间复杂度 \(\Theta(n)\)
总结:
遇到树上全源最长路,提树的中心到根是一个好想法
剩下枚举分支统计贡献的方式比较 trivial,核心还是转化成过根路径,只统计跨分支贡献
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+1,INF=1e9;
struct node {
int des,val;
};
vector <node> edge[MAXN];
int fa[MAXN],col[MAXN],siz[MAXN],dis[MAXN],cnt[MAXN],head[MAXN];
int vertex,n,m;
inline void diameter(int p,int f,int d) {
fa[p]=f,dis[p]=d;
if(col[p]&&dis[p]>dis[vertex]) vertex=p;
for(auto e:edge[p]) {
int v=e.des;
if(v==f) continue;
diameter(v,p,d+e.val);
}
}
inline void dfs(int p,int f,int d) {
siz[p]=col[p]?1:0;
cnt[p]=col[p]?1:0;
dis[p]=col[p]?d:0;
for(auto e:edge[p]) {
int v=e.des;
if(v==f) continue;
head[v]=f?head[p]:v;
dfs(v,p,d+e.val);
siz[p]+=siz[v];
if(dis[v]>dis[p]) dis[p]=dis[v],cnt[p]=cnt[v];
else if(dis[v]==dis[p]) cnt[p]+=cnt[v];
}
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int u;
scanf("%d",&u);
col[u]=1;
}
for(int i=1;i<n;++i) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
edge[u].push_back((node){v,w});
edge[v].push_back((node){u,w});
}
diameter(1,0,0);
diameter(vertex,0,0);
int length=dis[vertex],root=vertex;
while(vertex) {
if(max(dis[vertex],length-dis[vertex])<=max(dis[root],length-dis[root])) {
root=vertex;
}
vertex=fa[vertex];
}
dfs(root,0,0);
int firlen=0,seclen=0;
int fircnt=0,seccnt=0,maxtot=0;
for(auto e:edge[root]) {
int v=e.des;
if(!siz[v]) continue;
if(dis[v]>firlen) {
seclen=firlen;
seccnt=fircnt;
firlen=dis[v];
fircnt=1;
maxtot=siz[v];
} else if(dis[v]==firlen) {
++fircnt;
maxtot+=siz[v];
} else if(dis[v]>seclen) {
seccnt=1;
seclen=dis[v];
} else if(dis[v]==seclen) ++seccnt;
}
int ans=0,tot=0;
for(int i=1;i<=n;++i) {
if(col[i]) continue;
int res=siz[i],up=head[i];
if(i!=root&&dis[i]==dis[up]&&cnt[i]==cnt[up]) {
if(dis[i]==firlen&&fircnt==1) res+=m-siz[up];
else if(dis[i]==firlen&&fircnt==2) res+=maxtot-siz[up];
else if(dis[i]==seclen&&fircnt==1&&seccnt==1) res+=maxtot;
}
if(res>ans) ans=res,tot=1;
else if(res==ans) ++tot;
}
printf("%d %d\n",ans,tot);
return 0;
}
树形 dp 与树上问题的更多相关文章
- 2019CCPC-江西省赛 -A Cotree (树形DP,求树上一点到其他点的距离之和)
我是傻逼我是傻逼 #include<bits/stdc++.h> using namespace std; const int maxn=4e5+50; typedef long long ...
- 树形dp复习 树上依赖背包问题
选课 今天又看了一下这道题,竟然AC不了了 自己的学习效率有点低下 要明白本质,搞透彻 #include<bits/stdc++.h> #define REP(i, a, b) for(r ...
- 【BZOJ2427】[HAOI2010] 软件安装(缩点+树形DP)
点此看题面 大致题意: 有\(N\)个软件,每个软件有至多一个依赖以及一个所占空间大小\(W_i\),只有当一个软件的直接依赖和所有的间接依赖都安装了,它才能正常工作并造成\(V_i\)的价值.求在容 ...
- 树形dp - 求树的直径
随着杭州西湖的知名度的进一步提升,园林规划专家湫湫希望设计出一条新的经典观光线路,根据老板马小腾的指示,新的风景线最好能建成环形,如果没有条件建成环形,那就建的越长越好. 现在已经勘探确定了n个位置可 ...
- 中南大学oj 1317 Find the max Link 边权可以为负的树上最长路 树形dp 不能两遍dfs
http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1317经典问题:树上最长路,边权可以为负值的,树形dp,不能用两边dfs.反例:5 41 2 22 ...
- 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分
树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...
- 【BZOJ4033】[HAOI2015]树上染色 树形DP
[BZOJ4033][HAOI2015]树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染 ...
- BZOJ_4033_[HAOI2015]树上染色_树形DP
BZOJ_4033_[HAOI2015]树上染色_树形DP Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并 将其他的 ...
- hihocoder 1676 树上等差数列 黑科技树形dp
#1676 : 树上的等差数列 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一棵包含N个节点的无根树,节点编号1~N.其中每个节点都具有一个权值,第i个节点的权值 ...
随机推荐
- JUC(3)
文章目录 1.集合类不安全 2.在高并发情况下arraylist()并不安全 3.高并发下set并不安全 3.测试map(高并发情况下出现问题) 1.集合类不安全 2.在高并发情况下arraylist ...
- 畸变矫正、透视变换加速(OpenCV C++)
前两周,同事和我说检测时间超时,其中对图像做畸变矫正和投影变换就要花费25ms(3000×3000的图).而此时我们已经用上了文章opencv图像畸变矫正加速.透视变换加速方法总结中的方法.突然我想到 ...
- SQL的表的连接Left Join / Right Join /inner join相关
Left Join / Right Join /inner join相关关于左连接和右连接总结性的一句话:左连接where只影向右表,右连接where只影响左表.Left Joinselect * f ...
- Oracle:ORA-39006、ORA-39213解决办法
执行Oracle数据库导入,遇到报错ORA-39006: internal error.ORA-39213: Metadata processing is not available.这还是第一次遇到 ...
- Byte和byte的区别
Byte和byte的区别 背景 今天学习网络编程中,在建立Udp连接时,使用byte[]数组接收传输的数据,但是byte[]错写为Byte[],导致错误. //接收数据: Byt ...
- 记录一次新节点加入K8S集群
新节点初始化 安装docker kubelet kubeadm(指定版本) #先查看当前集群docker版本 [root@lecode-k8s-master manifests]# docker ve ...
- JMX port被占用
JMX port被占用 解决方案 win+R打开DOS窗口,进入window命令,注意:要以管理员身份打开(快捷键:ctrl+shift+enter): 使用命令:netstat -aon|finds ...
- kotlin的suspend对比csharp的async&await
协程的出现大大降低了异步编程的复杂度,可以让我们像写同步代码一样去写异步代码,如果没有它,那么很多异步的代码都是需要靠回调函数来一层层嵌套,这个在我之前的一篇有介绍 rxjava回调地狱-kotlin ...
- FIXMAP内存管理器
fixed map是被linux kernel用来解决一类问题的机制,这类问题的共同特点是: (1)在很早期的阶段需要进行地址映射,而此时,由于内存管理模块还没有完成初始化,不能动态分配内存,也就是无 ...
- 单例模式实现的多种方式、pickle序列化模块、选课系统需求分析等
目录 单例模式实现的多种方式 方式一: 方式二: 方式三 方式四 pickle序列化模块 选课系统需求分析 功能提炼 选课系统架构设计 三层架构 选课系统目录搭建 选课系统功能搭建 单例模式实现的多种 ...