【0/1型樹形dp】← 也只有我這樣叫... 這題是真的很模板...html
f[x] 即 拜訪x時最大數量,g[x] 即 不拜訪x時最大數量。node
轉移方程:f[x]=1+∑g[son[i]],g[x]=∑max(f[son[i]],g[son[i]])。ios
不妨假設從1號點出發,那麼答案即爲max(f[1],g[1])。數組
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p2996】拜訪奶牛 N(1<=N<=50000)個朋友構成一棵樹。求:能夠拜訪的朋友的最大數目。 限制:對於由一條路直接相連的兩個奶牛,只能拜訪其中的一個。 */ /*【0/1型樹形dp】← 也只有我這樣叫... f[x] 即 拜訪x時最大數量,g[x] 即 不拜訪x時最大數量。 轉移方程:f[x]=1+∑g[son[i]],g[x]=∑max(f[son[i]],g[son[i]])。 不妨假設從1號點出發,那麼答案即爲max(f[1],g[1])。 */ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=100019; int n,f[N],g[N],head[N],tot=0; struct node{ int ver,nextt; }e[N*2]; void add(int x,int y) { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; } void dp(int x,int fa_){ f[x]=1; //選擇本身 for(int i=head[x],y;i;i=e[i].nextt){ if(e[i].ver==fa_) continue; y=e[i].ver; dp(y,x); f[x]+=g[y]; //(1),不能選兒子 g[x]+=max(f[y],g[y]); //(2),不選x,下方隨意 } } int main(){ reads(n); for(int i=1,x,y;i<n;i++) reads(x),reads(y),add(x,y),add(y,x); dp(1,0); cout<<max(f[1],g[1])<<endl; }
【0/1型樹形dp】由於只用考慮0色點的個數,因此每一個點能夠分爲:0色/非0色。ide
那麼就和上一題很類似了。f[i]/g[i] 表示 i 爲/不爲 0色時,以i爲根的子樹中 0色點的最值個數。優化
二叉樹則:f[i]=1+g[ls[i]]+g[rs[i]],g[i]=min/max(f[ls[i]]+g[rs[i]],g[ls[i]]+f[rs[i]])。ui
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p2585】三色二叉樹 遞歸給出一棵二叉樹。每一個節點的顏色能夠是0,1,2。 要求:父子、兄弟節點的顏色不一樣。求最多/最少的0色點個數。 */ /*【0/1型樹形dp】由於只用考慮0色點的個數,因此每一個點能夠分爲:0色/非0色。 那麼就和 模板題p2996 很類似了。f[i]/g[i] 表示 i 爲/不爲 0色時,以i爲根的子樹中 0色點的最值個數。 二叉樹則:f[i]=1+g[ls[i]]+g[rs[i]],g[i]=min/max(f[ls[i]]+g[rs[i]],g[ls[i]]+f[rs[i]])。 */ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=500019; char ss[N]; int f[N],g[N],ls[N],rs[N],p,n; void init(){ p++,n++; int now=n; if(ss[p]=='2') ls[now]=n+1,init(),rs[now]=n+1,init(); if(ss[p]=='1') ls[now]=n+1,init(); } void dp_max(int x){ if(!x) return; dp_max(ls[x]),dp_max(rs[x]); f[x]=1+g[ls[x]]+g[rs[x]], //↓↓ 三個點必須只選一個 g[x]=max(f[ls[x]]+g[rs[x]],g[ls[x]]+f[rs[x]]); } void dp_min(int x){ if(!x) return; dp_min(ls[x]),dp_min(rs[x]); f[x]=1+g[ls[x]]+g[rs[x]], //↓↓ 三個點必須只選一個 g[x]=min(f[ls[x]]+g[rs[x]],g[ls[x]]+f[rs[x]]); } int main(){ scanf("%s",ss+1),init(); //遞歸輸入 dp_max(1),printf("%d ",max(f[1],g[1])); memset(f,0,sizeof(f)),memset(g,0,sizeof(g)); dp_min(1),printf("%d\n",min(f[1],g[1])); }
【樹形dp+貪心】每次刪除選定節點i後,它父親fa_增長的重量爲(son_i+c_i)-1。spa
那麼就能夠把每一個節點的權值當作子節點數目+櫻花數(son_i+c_i)。code
因而就有貪心策略:優先選擇權值小的,判斷可否去掉,若能去掉則更新當前節點的重量。htm
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p4107】兔子與櫻花 n個節點的櫻花樹,0號節點是根節點。第i個節點有c_i朵櫻花。 每個節點都有載重量m。對於節點i,要求:兒子個數son_i<=m-c_i。 要刪除一些節點。每次刪除以後兒子節點會向上鏈接。求最多能刪除多少節點。*/ /*【樹形dp+貪心】每次刪除選定節點i後,它父親fa_增長的重量爲(son_i+c_i)-1。 那麼就能夠把每一個節點的權值當作子節點數目+櫻花數(son_i+c_i)。 因而就有貪心策略:優先選擇權值小的,判斷可否去掉,若能去掉則更新當前節點的重量。*/ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=2000019; int n,m,ans=0,c[N]; vector<int> son[N]; //存兒子節點 bool cmp(int a,int b){ return c[a]<c[b]; } void dp(int x){ //↓↓先遞歸到最底部,再自底而上一步步貪心 for(int i=0;i<(int)son[x].size();i++) dp(son[x][i]); sort(son[x].begin(),son[x].end(),cmp); //每層都要從新排序 c[x]+=son[x].size(); //當前節點的權值改爲son_i+c_i //↑↑由於自底而上,因此不會有衝突和重複,每一個節點只會遍歷一次 for(int i=0;i<(int)son[x].size();i++) { if(c[x]+c[son[x][i]]-1>m) break; //不能再刪除了 c[x]+=c[son[x][i]]-1,ans++; } //按照貪心策略... } int main(){ reads(n),reads(m); for(int i=1;i<=n;i++) reads(c[i]); for(int i=1,k,x;i<=n;i++){ reads(k); //↓↓轉化爲根節點爲1 while(k--) reads(x),son[i].push_back(x+1); } dp(1); printf("%d\n",ans); return 0; }
【樹形dp+貪心】dfs求出以0爲起點的最長一條鏈,此鏈上的點只通過一次,消耗1步;
其它的點通過後須要返回這條鏈上,消耗2步。而後分類討論:是否能走完鏈、走完樹。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3698】小Q的棋盤 求 ‘從0號點出發,移動p步’ 最多能通過多少節點。*/ /*【樹形dp+貪心】dfs求出以0爲起點的最長一條鏈,此鏈上的點只通過一次,消耗1步; 其它的點通過後須要返回這條鏈上,消耗2步。而後分類討論:是否能走完鏈、走完樹。*/ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=519; int n,p,max_l=0,x,y; int head[N],ver[N<<1],nextt[N<<1],tot=0,dep[N]; void add(int x,int y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } void dfs(int x,int fa){ for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa) dep[ver[i]]=dep[x]+1,dfs(ver[i],x); } int main(){ reads(n),reads(p); for(int i=1;i<n;i++) reads(x),reads(y),add(x,y),add(y,x); dfs(0,-1); for(int i=1;i<n;i++) if(max_l<dep[i]) max_l=dep[i]; if(p<=max_l) printf("%d\n",p+1); //比最長鏈短 else if(p>=max_l+2*(n-max_l-1)) printf("%d\n",n); //比全部長度還長 else printf("%d\n",max_l+(p-max_l)/2+1); //只能走除最長鏈以外的一部分 }
【樹形dp】f[x]表示子樹x中全部點到點x的距離之和,g[x]表示整個樹中全部點到點x的距離之和。
x的子樹的對應路徑中,有si[to[i]]個點到x的距離比to[i]多1:f[x]=∑(f[to[i]]+si[to[i]])。
可知g[1]=f[1]。由於有n-si[to[i]]個點到to[i]的距離比到x多1,加n-si[to[i]];
有si[to[i]]個點到to[i]的距離比到x少1,因此再減si[to[i]];因此g[to[i]]=g[x]+n-2*si[to[i]]。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3478】Station 給出一個N個點的樹,找出一個點來,以這個點爲根的樹時,全部點的深度之和最大。*/ /*【樹形dp】f[x]表示子樹x中全部點到點x的距離之和,g[x]表示整個樹中全部點到點x的距離之和。 x的子樹的對應路徑中,有si[to[i]]個點到x的距離比to[i]多1:f[x]=∑(f[to[i]]+si[to[i]])。 可知g[1]=f[1]。由於有n-si[to[i]]個點到to[i]的距離比到x多1,加n-si[to[i]]; 有si[to[i]]個點到to[i]的距離比到x少1,因此再減si[to[i]];因此g[to[i]]=g[x]+n-2*si[to[i]]。*/ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=1000019; int n,ans=0,f[N],g[N]; int head[N],ver[N<<1],nextt[N<<1],tot=0,siz[N]; void add(int x,int y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } void dfs1(int x,int fa){ siz[x]=1; for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa) dfs1(ver[i],x),siz[x]+=siz[ver[i]],f[x]+=f[ver[i]]+siz[ver[i]]; } void dfs2(int x,int fa){ for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa) g[ver[i]]=g[x]+n-2*siz[ver[i]],dfs2(ver[i],x); } int main(){ reads(n); for(int i=1,x,y;i<n;i++) reads(x),reads(y),add(x,y),add(y,x); dfs1(1,0); g[1]=f[1]; dfs2(1,0); //分別求出f[],g[] for(int i=1;i<=n;i++) if(g[ans]<g[i]) ans=i; cout<<ans<<endl; return 0; //g[]中最大的就是ans }
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3478】Station 給出一個N個點的樹,找出一個點來,以這個點爲根的樹時,全部點的深度之和最大。*/ /*【樹形dp】f[x]表示子樹x中全部點到點x的距離之和,g[x]表示整個樹中全部點到點x的距離之和。 x的子樹的對應路徑中,有si[to[i]]個點到x的距離比to[i]多1:f[x]=∑(f[to[i]]+si[to[i]])。 可知g[1]=f[1]。由於有n-si[to[i]]個點到to[i]的距離比到x多1,加n-si[to[i]]; 有si[to[i]]個點到to[i]的距離比到x少1,因此再減si[to[i]];因此g[to[i]]=g[x]+n-2*si[to[i]]。*/ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=1000019; int n,ans=0; ll f[N],g[N],siz[N]; int head[N],ver[N<<1],nextt[N<<1],tot=0; void add(int x,int y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } void dfs1(int x,int fa){ siz[x]=1; for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa) dfs1(ver[i],x),siz[x]+=siz[ver[i]],f[x]+=f[ver[i]]+siz[ver[i]]; } void dfs2(int x,int fa){ for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa) g[ver[i]]=g[x]+n-2*siz[ver[i]],dfs2(ver[i],x); } int main(){ reads(n); for(int i=1,x,y;i<n;i++) reads(x),reads(y),add(x,y),add(y,x); dfs1(1,0); g[1]=f[1]; dfs2(1,0); //分別求出f[],g[] for(int i=1;i<=n;i++) if(g[ans]<g[i]) ans=i; cout<<ans<<endl; return 0; //g[]中最大的就是ans }
點亮燈泡k,點亮它的一個子樹,再點亮它另外的子樹,
而後回到k的父節點,點亮fa以後再點亮fa的其餘子樹……
對於一個節點u,有這樣兩種狀況:1.u尚未被點亮,則下一個被點亮的必定是它的兒子
2.u是下方(葉子)節點,在下一個被點亮的必定是它的某一級祖先,或者是它某一級祖先的兒子
倒序推導,注意討論當前節點的兒子個數。統計答案時,根據點亮的過程累加便可。
ps:因爲這是一棵徹底二叉樹,因此能夠不用遞歸的方式dfs。
直接預處理出每一個節點的兒子和它到各級祖先的距離,用循環轉移便可。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> #include <map> using namespace std; typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; /*【p4253】小凸玩密室 // 樹形DP 一棵有n個節點的徹底二叉樹,每一個節點有一個燈泡。點亮全部燈泡便可逃出密室。 點值ai,邊權bi。點亮第一個燈泡不須要花費,以後每點亮一個新的燈泡v的花費, 等於上一個被點亮的燈泡u到這個點v的距離Du,v,乘以這個點的權值av。 在點亮一個燈泡後必須先點亮其子樹全部燈泡。求最少花費。 */ /*【分析】點亮燈泡k,點亮它的一個子樹,再點亮它另外的子樹, 而後回到k的父節點,點亮fa以後再點亮fa的其餘子樹…… 因此對於一個節點u,有這樣兩種狀況: 1.u尚未被點亮,則下一個被點亮的必定是它的兒子 2.u是下方(葉子)節點,在下一個被點亮的必定是它的某一級祖先,或者是它某一級祖先的兒子 f[i][j]表示點亮i以後回到i的第j個祖先的最小花費。 g[i][j]表示點亮i以後回到i的第j個祖先的另外一個兒子的最小花費。 倒序推導,注意討論當前節點的兒子個數。統計答案時,根據點亮的過程累加便可。 ps:因爲這是一棵徹底二叉樹,因此能夠不用遞歸的方式dfs。 直接預處理出每一個節點的兒子和它到各級祖先的距離,用循環轉移便可。*/ void reads(int &x){ //讀入優化(正負整數) int fx_=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx_=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx_; //正負號 } const int N=200019; int n,w[N],fa[N],ls[N],rs[N]; ll ans=(ll)1e17; ll f[N][20]; //f[][]:i是亮的,回到i的第j個祖先的最小花費 ll g[N][20]; //g[][]:i是亮的,回到i的第j個祖先的另外一個兒子的最小花費 ll dis[N][20]; //dis[][]:從i到i的第j個祖先的距離 int brother(int k,int x){ return k>>(x-1)^1; } ll work(){ for(int k=n;k>=1;k--){ if(!ls[k]) for(int i=1;k>>(i-1);i++) //葉子節點 g[k][i]=(dis[k][i]+dis[brother(k,i)][1])*w[brother(k,i)]; else if(!rs[k]) for(int i=1;k>>(i-1);i++) g[k][i]=dis[ls[k]][1]*w[ls[k]]+g[ls[k]][i+1]; else for(int i=1;k>>(i-1);i++) g[k][i]=min(dis[ls[k]][1]*w[ls[k]]+g[ls[k]][1]+g[rs[k]][i+1], dis[rs[k]][1]*w[rs[k]]+g[rs[k]][1]+g[ls[k]][i+1]); } for(int k=n;k>=1;k--){ if(!ls[k]) for(int i=1;k>>(i-1);i++) f[k][i]=dis[k][i]*w[k>>i]; else if(!rs[k]) for(int i=1;k>>(i-1);i++) f[k][i]=f[ls[k]][i+1]+dis[ls[k]][1]*w[ls[k]]; else for(int i=1;k>>(i-1);i++) f[k][i]=min(dis[ls[k]][1]*w[ls[k]]+g[ls[k]][1]+f[rs[k]][i+1], dis[rs[k]][1]*w[rs[k]]+g[rs[k]][1]+f[ls[k]][i+1]); } for(int k=1;k<=n;k++){ ll sum=f[k][1]; for(int i=1,fa=k>>1;fa;i++,fa>>=1){ int bro=brother(k,i); if (bro>n) sum+=dis[fa][1]*w[fa>>1]; else sum+=dis[bro][1]*w[bro]+f[bro][2]; } ans=min(ans,sum); } return ans; } int main(){ reads(n); for(int i=1;i<=n;i++) reads(w[i]); for(int i=2;i<=n;i++) scanf("%lld",&dis[i][1]); //邊權 for(int i=1;i<=(n>>1)+1;i++){ //徹底二叉樹 if((i<<1)<=n) ls[i]=(i<<1); else break; if((i<<1|1)<=n) rs[i]=(i<<1|1); //記錄徹底二叉樹對應的左右兒子 } for(int i=2;i<=18;i++) for(int k=n;k>>i;k--) dis[k][i]=dis[k][i-1]+dis[k>>(i-1)][1]; printf("%lld\n",work()); return 0; }
【樹形dp+log( )壓縮】改變後每一個節點與根節點權值有固定的倍數關係。
對於樹上任一個點,其權值一旦肯定,整棵樹的權值便可肯定。
先初始化一下,肯定改變後每一個節點與根節點權值的倍數關係,
將原權值乘上這個倍數關係,最後求出最多的‘相等權值’便可。
因爲數據特別大,因此使用log將乘法壓縮爲加法來保存(主要是讓數變小)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3237】米特運輸 給定一棵以1爲根的樹,每一個節點又有一個權值。 問:最少要改變多少個節點的權值,使得: (1)每一個節點的子節點的權值相同。 (2)每一個節點的子節點權值之和等於該點的權值。*/ /*【樹形dp】改變後每一個節點與根節點權值有固定的倍數關係。 對於樹上任一個點,其權值一旦肯定,整棵樹的權值便可肯定。 先初始化一下,肯定改變後每一個節點與根節點權值的倍數關係, 將原權值乘上這個倍數關係,最後求出最多的‘相等權值’便可。 因爲數據特別大,因此使用log將乘法壓縮爲加法來保存。*/ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=500019; double a[N],s[N],val[N]; int siz[N],ver[N*2],nextt[N*2],head[N],tot=0; void add(int x,int y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } void pre_dfs(int x,int fa){ for(int i=head[x],y;i;i=nextt[i]){ y=ver[i]; if(y==fa) continue; //用log壓縮 s[y]=s[x]+log(siz[x]),pre_dfs(y,x); //s保存倍數關係 } } int main(){ int n,tmp=1,ans=0; reads(n); for(int i=1;i<=n;i++) scanf("%lf",&a[i]); //初始權值 for(int i=1,x,y;i<n;i++) reads(x),reads(y), add(x,y),add(y,x),siz[x]++,siz[y]++; for(int i=2;i<=n;i++) siz[i]--; //計算子樹大小 s[1]=log(1); pre_dfs(1,0); for(int i=1;i<=n;i++) val[i]=s[i]+log(a[i]); sort(val+1,val+n+1); for(int i=2;i<=n;i++){ //最多的相同個數 if(val[i]-val[i-1]<=1e-5) tmp++; else ans=max(ans,tmp),tmp=1; } printf("%d\n",n-max(tmp,ans)); }
【樹形dp】記a[i]爲該點入度,某條鏈對應的毛毛蟲點數爲:∑a[i]−(s−1)+1。
簡化式子:∑a[i]−(s−1)+1 = ∑a[i]−s+2 = ∑(a[i]−1)+2;設定點權以後找直徑便可。
// 關於這題的數據... 真心無語.. 沒法承受的數據之水...
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3174】毛毛蟲 毛毛蟲:樹上某條鏈和與該鏈相連的邊構成的圖。求最多的點數。*/ /*【樹形dp】記a[i]爲該點入度,某條鏈對應的毛毛蟲點數爲:∑a[i]−(s−1)+1。 簡化式子:∑a[i]−(s−1)+1 = ∑a[i]−s+2 = ∑(a[i]−1)+2;設定點權以後找直徑便可。*/ void reads(int &x){ //讀入優化(正負整數) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正負號 } const int N=500019; int a[N],ver[N*2],nextt[N*2],head[N],tot=0,now,mx; void add(int x,int y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } void dfs(int x,int fa_,int dis){ if(dis>mx) mx=dis,now=x; for(int i=head[x];i;i=nextt[i]){ if(ver[i]==fa_) continue; dfs(ver[i],x,dis+a[ver[i]]); } } int main(){ int n,m; reads(n),reads(m); //m=n-1 for(int i=1,x,y;i<=m;i++) reads(x),reads(y), add(x,y),add(y,x),a[x]++,a[y]++; for(int i=1;i<=n;i++) a[i]--; //ans=∑(a[i]−1)+2 dfs(1,0,a[1]),mx=0,dfs(now,0,a[now]),cout<<mx+2<<endl; }
【樹形dp】若是樹上三個點兩兩距離相同,
那麼這三條路徑的中點重合,且長度必定是偶數。
能夠枚舉這個中點,那麼題意轉化爲:
- 求 ‘ 選出三個點到這個中點距離相同 ’ 的方案數。
設 f 1/2/3 [i] 表示選出 1/2/3 個深度爲 i 的點的方案數。
枚舉每一個點以後,記得要清空數組(動態清空 g [ ] )。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3565】Hotel 在邊權爲1的樹上選三個點使得兩兩距離相等,求方案數。*/ /*【樹形dp】若是樹上三個點兩兩距離相同,那麼這三條路徑的中點重合,且長度必定是偶數。 能夠枚舉這個中點,那麼題意轉化爲:求 ‘ 選出三個點到這個中點距離相同 ’ 的方案數。 設f1/2/3[i]表示選出1/2/3個深度爲i的點的方案數。*/ void reads(int &x){ //讀入優化(正負整數) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; /* hs_love_wjy */ } const int N=500019; ll ans,f1[N],f2[N],f3[N],g[N]; int n,dep[N],ver[N*2],nextt[N*2],head[N],tot=0,max_dep; void add(int x,int y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } void dfs(int x,int fa_){ max_dep=max(max_dep,dep[x]),g[dep[x]]++; for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa_) dep[ver[i]]=dep[x]+1,dfs(ver[i],x); } ll query(int x){ memset(f1,0,sizeof(f1)),memset(f2,0,sizeof(f2)),memset(f3,0,sizeof(f3)); for(int i=head[x];i;i=nextt[i]){ dep[ver[i]]=max_dep=1,dfs(ver[i],x); //以此點爲根,求dep for(int j=max_dep;j;j--) // 注意 : g[]直接在這裏清空了↓↓ f3[j]+=f2[j]*g[j],f2[j]+=f1[j]*g[j],f1[j]+=g[j],g[j]=0; } ll sum=0; for(int i=1;i<=n;i++) sum+=f3[i]; return sum; } int main(){ reads(n); for(int i=1,x,y;i<n;i++) reads(x),reads(y),add(x,y),add(y,x); for(int i=1;i<=n;i++) ans+=query(i); //此點做爲中點 printf("%lld\n",ans); return 0; //記得用LL }
【樹形dp】f[i]表明以i爲根的子樹上的‘最大到達時間’。
因而目的就是最小化f[1]。用siz[i]表明遍歷此點子樹的用時,
由於要遍歷完整個子樹才能夠去另外一棵,獲得轉移方程:
- dp[u] = max{ dp[a] , dp[b] + siz[a] + 2 };
- dp[u] = max{ dp[b] , dp[a] + siz[b] + 2 };
那麼就是max{ dp[b] + siz[a] + 2 , dp[a] + siz[b] + 2}。
貪心按 ‘ dp[b] + siz[a] + 2 < dp[a] + siz[b] + 2 ’ 排序便可。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p3574】Farmcraft 一棵樹,從root=1出發,邊權都爲1, 每一個點有點權,求最小的max(點權+到達時間)。*/ /*【樹形dp】f[i]表明以i爲根的子樹上的‘最大到達時間’。 因而目的就是最小化f[1]。用siz[i]表明遍歷此點子樹的用時, 由於要遍歷完整個子樹才能夠去另外一棵,獲得轉移方程: dp[u] = max{ dp[a] , dp[b] + siz[a] + 2 }; dp[u] = max{ dp[b] , dp[a] + siz[b] + 2 }; 那麼就是max{ dp[b] + siz[a] + 2 , dp[a] + siz[b] + 2}。 貪心按‘ dp[b] + siz[a] + 2 < dp[a] + siz[b] + 2 ’排序便可。*/ void reads(ll &x){ //讀入優化(正負整數) ll fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; /* hs_love_wjy */ } const int N=600019; ll n,x,y,a[N],siz[N],dp[N],ver[N*2],nextt[N*2],head[N],tot=0; void add(ll x,ll y){ ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; } struct Node{ ll id,dp_,siz_; }P[N]; bool cmp(Node a,Node b) { ll p=max(a.dp_,b.dp_+a.siz_+2),q=max(b.dp_,a.dp_+b.siz_+2); return p<q; } void DP(ll u, ll fa){ if(u!=1) dp[u]=a[u]; ll cnt=0; for(ll i=head[u];i;i=nextt[i]) if(ver[i]!=fa) DP(ver[i],u); for(ll i=head[u];i;i=nextt[i]) if(ver[i]!=fa) P[++cnt]=(Node){ver[i],dp[ver[i]],siz[ver[i]]}; if(cnt){ sort(P+1,P+1+cnt,cmp); for(ll i=1;i<=cnt;i++){ ll v=P[i].id; dp[u]=max(dp[u],dp[v]+(siz[u]+1)),siz[u]+=siz[v]+2; } } } int main(){ reads(n); for(ll i=1;i<=n;i++) reads(a[i]); for(ll i=1;i<n;i++) reads(x),reads(y),add(x,y),add(y,x); DP(1,0); printf("%lld\n",max(dp[1],(n-1)*2+a[1])); }
【樹形dp】初始化每一個非根節點與其父節點連線的權值,
遞推出每一個點的子樹siz,那麼兩端個數差爲:siz-(n-siz)=2*siz-n。
因爲棧的限制,普通的dfs樹形dp會爆棧,因此採用bfs。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p2052】道路修建 連一條邊的代價 = 此邊邊權 * 兩端節點個數之差的絕對值。求總費用。*/ /*【樹形dp】初始化每一個非根節點與其父節點連線的權值, 遞推出每一個點的子樹siz,那麼兩端個數差爲:siz-(n-siz)=2*siz-n。 因爲棧的限制,普通的dfs樹形dp會爆棧,因此採用bfs。*/ void reads(int &x){ //讀入優化(正負整數) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; /* hs_love_wjy */ } const int N=1000019; ll z,ans=0,val[N*2],v[N]; int x,y,q[N],qh=1,qt=1; //bfs隊列 int n,fa[N],siz[N],ver[N*2],nextt[N*2],head[N],tot=0; void add(int x,int y,ll z) { ver[++tot]=y,nextt[tot]=head[x],val[tot]=z,head[x]=tot; } int main(){ reads(n); for(int i=1;i<n;i++) reads(x),reads(y),cin>>z,add(x,y,z),add(y,x,z); fa[1]=-1,q[1]=1; while(qh<=qt){ //廣搜,head<=tail x=q[qh++]; for(int i=head[x];i;i=nextt[i]){ if(fa[ver[i]]) continue; fa[ver[i]]=x; //↑↑注意,要把fa[1]設爲-1,在此處就會標記爲訪問過 q[++qt]=ver[i],siz[ver[i]]=1,v[ver[i]]=val[i]; } } for(int i=qt;i>=2;i--) x=q[i],siz[fa[x]]+=siz[x], ans+=(ll)v[x]*abs(2*siz[x]-n); //‘此點與父親節點連邊’兩端個數差 printf("%lld\n",ans); return 0; }
【樹形dp】多方向 多層次dfs 預處理
c[i][j]爲從i點的子樹中走到i,選擇j個點的權值和。
b[i][j]爲從i點開始,向子樹中走,選擇j個點的權值和。
a[i]爲i的點權。g[i]爲點i的相鄰節點的權值和。fa[i]爲i的父節點。
初始值:c[x][0]=0,c[x][i]=g[x];b[x][0]=0,b[x][i]=g[x]−a[fa[x]]。
轉移方程:c[x][i]=max(c[y][i],c[y][i−1]+g[x]−a[y]);
b[x][i]=max(b[y][i],b[y][i−1]+g[x]−a[fa[x]])。
這題思惟難度仍是有點大的,並且細節處理有點難想全...
真心就是那種,隨便錯一點,根本調不出來...還會爆零的...
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<map> #include<vector> #include<cmath> using namespace std; typedef long long ll; /*【p4657】chase //複雜の樹形DP 給出一棵樹,求一條路徑:選擇路上的V個點, 使得被選擇的點的相鄰且不在路徑上的點的權值和最大。 */ /*【樹形dp】多方向 多層次dfs 預處理 c[i][j]爲從i點的子樹中走到i,選擇j個點的權值和。 b[i][j]爲從i點開始,向子樹中走,選擇j個點的權值和。 a[i]爲i的點權。g[i]爲點i的相鄰節點的權值和。fa[i]爲i的父節點。 初始值:c[x][0]=0,c[x][i]=g[x];b[x][0]=0,b[x][i]=g[x]−a[fa[x]]。 轉移方程:c[x][i]=max(c[y][i],c[y][i−1]+g[x]−a[y]); b[x][i]=max(b[y][i],b[y][i−1]+g[x]−a[fa[x]])。 */ void reads(int &x){ //讀入優化(正負整數) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; /* hs_love_wjy */ } const int N=100019; int n,v,x,y,tot,top,a[N],sta[N]; int ver[N<<1],nextt[N<<1],head[N]; long long g[N],c[N][105],b[N][105],ans; inline void add(int x,int y) { ver[++tot]=y,nextt[tot]=head[x],head[x]=tot; ver[++tot]=x,nextt[tot]=head[y],head[y]=tot; } inline void DP(int x,int y,int fa_){ for(int i=1;i<=v;i++) //提早賦值 ans=max(ans,c[x][i]+b[y][v-i]); for(int i=1;i<=v;i++) //向下走 / 向上走 c[x][i]=max(c[x][i],max(c[y][i],c[y][i-1]+g[x]-a[y])), b[x][i]=max(b[x][i],max(b[y][i],b[y][i-1]+g[x]-a[fa_])); } void dfs(int x,int fa_){ for(int i=1;i<=v;i++) c[x][i]=g[x],b[x][i]=g[x]-a[fa_]; for(int i=head[x];i;i=nextt[i]) //↓↓更新此節點在每一種狀態下的dp值 if(ver[i]!=fa_) dfs(ver[i],x),DP(x,ver[i],fa_); for(int i=1;i<=v;i++) c[x][i]=g[x],b[x][i]=g[x]-a[fa_]; //要倒着作一遍 top=0; for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa_) sta[++top]=ver[i]; for(int i=top;i;i--) DP(x,sta[i],fa_); //反着走,dp更新反向路徑最優值 ans=max(ans,max(c[x][v],b[x][v])); //以x爲根節點的最優答案 } int main(){ reads(n),reads(v); for(int i=1;i<=n;i++) reads(a[i]); for(int i=1,x,y;i<n;i++) reads(x),reads(y), add(x,y),g[x]+=a[y],g[y]+=a[x]; dfs(1,0); printf("%lld\n",ans); return 0; }
首先把能夠充電-->1-不能充電。而後用兩個數組分別記錄兩次dp的狀態。
- f[x]:在子樹中,節點x不能工做的機率。那麼下方節點不能有相通的連邊。
- g[x]:統計上方節點以後,計算獲得的每一個點不能工做的機率。
第一次dfs求f[x],要自下而上;第二次dfs求g[x],要自上而下。固然,還要推式子...
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p4284】機率充電器 // 多方向樹形dp 一棵樹,每一個點/每條邊都有充電的機率,求進入充電狀態的元件個數的指望。*/ //分析見:https://www.cnblogs.com/GXZlegend/p/7392084.html const int N=500019; const double eps=1e-7; int head[N],ver[N<<1],nextt[N<<1],tot; double w[N<<1],a[N],f[N],g[N]; void add(int x,int y,double z) { ver[++tot]=y,w[tot]=z,nextt[tot]=head[x],head[x]=tot; } void dfs1(int x,int fa){ f[x]=1-a[x]; // f[x]:(在子樹中)節點x不能工做的機率 for(int i=head[x];i;i=nextt[i]) //那麼下方全部節點不能有相通的連邊 if(ver[i]!=fa) dfs1(ver[i],x),f[x]*=1-(1-f[ver[i]])*w[i]; } void dfs2(int x,int fa){ //g[x]:統計上方節點以後,每一個點不能工做的機率 for(int i=head[x];i;i=nextt[i]) if(ver[i]!=fa){ if(1-(1-f[ver[i]])*w[i]<eps) g[ver[i]]=f[ver[i]]*w[i]; //↑↑特判分母(1-(1-f[ver[i]])*w[i])=0的狀況 else g[ver[i]]=f[ver[i]]*(1-(1-g[x]/(1-(1-f[ver[i]])*w[i]))*w[i]); dfs2(ver[i],x); //自上而下,繼續遞歸,更新全部節點的g[] } } int main(){ int n; double z,ans=0; scanf("%d",&n); for(int i=1,x,y;i<n;i++) // 去除百分號 ↓↓ scanf("%d%d%lf",&x,&y,&z),add(x,y,z/100),add(y,x,z/100); for(int i=1;i<=n;i++) scanf("%lf",&a[i]),a[i]/=100; dfs1(1,0),g[1]=f[1],dfs2(1,0); //自下而上求f[x],自上而下求g[x] for(int i=1;i<=n;i++) ans+=1-g[i]; printf("%.6lf\n",ans); }
——時間劃過風的軌跡,那個少年,還在等你