树上乱搞 - 树形dp
树形dp,本质上就是遍历一颗树,维护某些值,使得原本很大的枚举量,降到O(n), 结合 dfs,进行状态转移。
有时由上往下递推,有时由下往上递推
往往满足最优子结构的特点,就是说局部最优,可以推出全局最优,因为每一步都深深受上一步影响。
Anniversary party
题意:一棵树,每个节点有权值,他和他的父结点只能选一个,求最大权值和,
设dp(i,0)表示这个点不选,dp(i,1)表示这个点选择。
那么对于他的子节点 dp[u][0]+=max(dp[v][0],dp[v][1]);
dp[u][1]+=dp[v][0];
初始化dp(i,0)为0. dp(i,1)为a[i],
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=6e3+5; vector<int>e[N]; #define pb push_back int dp[N][2],w[N],pre[N]; void dfs(int u){ dp[u][0]=0; dp[u][1]=w[u]; for(int i=0;i<e[u].size();i++){ int v=e[u][i]; dfs(v); dp[u][0]+=max(dp[v][0],dp[v][1]); dp[u][1]+=dp[v][0]; } } int main(){ int n; while(~scanf("%d",&n)){ for(int i=1;i<=n;i++)scanf("%d",&w[i]); int u,v; for(int i=1;i<=n;i++)e[i].clear(),pre[i]=-1; memset(dp,0,sizeof dp); while(~scanf("%d %d",&u,&v),u+v){ e[v].pb(u); pre[u]=v; } int t=1; while(pre[t]!=-1)t=pre[t]; dfs(t); printf("%d\n",max(dp[t][0],dp[t][1])); } // system("pause"); return 0; }
Computer
题意:一棵树,每条边有权值,求每个点距离最大点,1e5数据
考虑一个点最远的点会在哪里,只有两种情况:
1. 在子树上;
2. 在这颗子树外;
那么设 f(u) 表示这个点子树上最远的点距离,
有 f(u)=max(f(v)+w(u,v)) v表示子节点
由上往下递归实现。先递推再求和,因为每个节点状态总是受到子节点影响。
设 g (v) 表示子树之外的最远点距离
有 g(u) = w (u,pre(u)) + max (g(pre[u]),f(v)+w(pre[u],v)) v表示兄弟节点
先处理g,然后往下递推,因为每一个节点的g总是受父结点影响,所以先处理再递推。
#include<bits/stdc++.h> using namespace std; const int N=1e4+5; #define pb push_back struct edge{int v,w;edge(int a,int b){v=a,w=b;}}; vector<edge>e[N]; int f[N],g[N],pre[N]; int dfs1(int u,int fa){ f[u]=0; for(int i=0;i<e[u].size();i++){ int v=e[u][i].v,w=e[u][i].w; if(v==fa)continue; f[u]=max(f[u],dfs1(v,pre[v]=u)+w); } return f[u]; } void dfs2(int u,int fa){ g[u]=g[fa]; int t=0; for(int i=0;i<e[fa].size();i++){ int v=e[fa][i].v,w=e[fa][i].w; if(v==pre[fa]||v==fa)continue; else if(v==u)t=w; else g[u]=max(g[u],f[v]+w); } g[u]+=t; for(int i=0;i<e[u].size();i++){ int v=e[u][i].v; if(v!=fa)dfs2(v,u); } } int main(){ int n; while(~scanf("%d",&n)){ for(int i=1;i<=n;i++)e[i].clear(); // memset(g,0,sizeof g) memset(f,0,sizeof f); memset(g,0,sizeof g); for(int u=2,v,w;u<=n;u++){ scanf("%d %d",&v,&w); e[u].pb(edge(v,w)); e[v].pb(edge(u,w)); } dfs1(1,0); dfs2(1,0); for(int i=1;i<=n;i++)printf("%d\n",max(f[i],g[i])); } return 0; }
Godfather
题意:教父这个点,去掉之后,这个树最大联块,最小,求有多少个会是教父。
直接一遍dfs
#include<cstdio> #include<iostream> #include<cstring> #include<vector> using namespace std; const int N=5e4+5; struct edge{int v,next;}e[N*10]; int f[N],sz[N],head[N]; int ecnt,n; void add(int u,int v){ e[ecnt].v=v,e[ecnt].next=head[u],head[u]=ecnt++; } void dfs_size(int u,int fa){ sz[u]=1; for(int i=head[u];~i;i=e[i].next){ int v=e[i].v; if(v==fa)continue; dfs_size(v,u); sz[u]+=sz[v]; } } void dfs_ans(int u,int fa){ f[u]=n-sz[u]; for(int i=head[u];~i;i=e[i].next){ int v=e[i].v; if(v==fa)continue; f[u]=max(f[u],sz[v]); } for(int i=head[u];~i;i=e[i].next){ int v=e[i].v; if(v==fa)continue; // f[u]=max(f[u],sz[v]); dfs_ans(v,u); } } int main(){ memset(head,-1,sizeof head); ecnt=0; scanf("%d",&n); for(int i=1,u,v;i<n;i++){ scanf("%d %d",&u,&v); add(u,v);add(v,u); } dfs_size(1,-1); dfs_ans(1,-1); int Min=1e9; for(int i=1;i<=n;i++)Min=min(Min,f[i]); vector<int>v; for(int i=1;i<=n;i++)if(f[i]==Min)v.push_back(i); for(int i=0;i<v.size();i++)printf("%d ",v[i]); puts(""); // system("pause"); return 0; }
I - Full Tank?
#include <cstdio> #include <cstring> #include <iostream> #include <queue> #include <algorithm> using namespace std; #define pb push_back const int N=1e4+5; const int inf=0x3f3f3f3f; int n,m,ecnt; int head[N],p[N],dp[N][120]; bool vis[N][120]; int C,S,E; struct edge{int v,w,next;}e[N*10]; void add(int u,int v,int w){ e[ecnt].v=v;e[ecnt].w=w;e[ecnt].next=head[u];head[u]=ecnt++; } struct node{ int u,cost,oil; node(int a,int b,int c){u=a;cost=b;oil=c;} bool friend operator<(node a,node b){ return a.cost>b.cost; } }; void dijkstra(){ memset(dp,inf,sizeof dp); memset(vis,0,sizeof vis); priority_queue<node>Q; Q.push(node(S,0,0));dp[S][0]=0; while(!Q.empty()){ node cur=Q.top();Q.pop(); int u=cur.u,oil=cur.oil,cost=cur.cost; vis[u][oil]=1; if(u==E){printf("%d\n",cost);return ;} if(oil+1<=C&&!vis[u][oil+1]&&dp[u][oil+1]>dp[u][oil]+p[u]){ dp[u][oil+1]=dp[u][oil]+p[u]; Q.push(node(u,dp[u][oil+1],oil+1)); } for(int i=head[u];~i;i=e[i].next){ int v=e[i].v;int w=e[i].w; if(oil>=w&&!vis[v][oil-w]&&dp[v][oil-w]>cost){ dp[v][oil-w]=cost; Q.push(node(v,dp[v][oil-w],oil-w)); } } } puts("impossible"); } int main(){ memset(head,-1,sizeof head);ecnt=0; scanf("%d %d",&n,&m); for(int i=0;i<n;i++)scanf("%d",&p[i]); for(int i=1,u,v,w;i<=m;i++){ scanf("%d %d %d",&u,&v,&w); add(u,v,w);add(v,u,w); } // int q,C,S,E; int q; scanf("%d",&q); while(q--){ scanf("%d %d %d",&C,&S,&E); dijkstra(); } // system("pause"); return 0; }
简单最短路;
题意:给你一张图,可以把k个边变成0,求最短路。
解法:考虑每一条边都只有两种情况,变成0,和不变成0,设 dis [ i [[ j ] 表示:取了 j 个0,更新过来最短路。
那么就只有两种情况。变成 0 和 不变成0,
更新最短路,保存等级即可,即可。
#include<cstdio> #include<iostream> #include<cstring> #include<queue> using namespace std; typedef long long ll; const int N=1e5+5; const ll INF=1e14; const int inf =0x3f3f3f3f; int head[N],ecnt,K,n,m;//cnt main里初始化为0,head main初始化为-1 struct edge{int v;long long w,next;}e[N*10];//注意有重边,乘10 struct node{ int id;ll dis;int lev;//id点,dis距离 node(int x,ll y,int z){id=x,dis=y;lev=z;} friend bool operator<(node a,node b){ return a.dis>b.dis;//距离小的出队 } }; ll dis[N][15]; bool done[N][15]; void add(int u,int v,ll w){//加边 e[ecnt].v=v,e[ecnt].w=w,e[ecnt].next=head[u],head[u]=ecnt++; } void init(){ memset(head,-1,sizeof head); ecnt=0; } void dijkstra(){ memset(done,0,sizeof done ); memset(dis,inf,sizeof dis); priority_queue<node>Q; Q.push(node(1,0,0));dis[1][0]=0; // node tmp; while(!Q.empty()){ // tmp=Q.top();Q.pop(); int u=(Q.top()).id,lv=(Q.top()).lev;Q.pop(); if(done[u][lv])continue; done[u][lv]=1; for(int i=head[u];~i;i=e[i].next){ int v=e[i].v;ll w=e[i].w; if(dis[v][lv]>dis[u][lv]+w){ dis[v][lv]=dis[u][lv]+w; Q.push(node(v,dis[v][lv],lv)); } if(lv<K&&dis[v][lv+1]>dis[u][lv]){ dis[v][lv+1]=dis[u][lv]; Q.push(node(v,dis[v][lv+1],lv+1)); } } } } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%d %d %d",&n,&m,&K); init(); int u,v;ll w; for(int i=1;i<=m;i++){ scanf("%d %d %d",&u,&v,&w); add(u,v,w); } dijkstra(); ll ans=INF; for(int i=0;i<=K;i++)ans=min(ans,dis[n][i]); printf("%lld\n",ans); } // system("pause"); return 0; }