DP研究ノートツリー
PS:この記事はブルーブックと一致しています
木の重心
- コンセプト:その最大のサブツリーノードツリーのノードツリーの最小
- 溶液:各ノードと息子を見つける\(サイズ\) 、サブツリー上のノードの数である(N - size_u \)\、ツリーのそれぞれの子ノードの最大値を見つけるだろうと最小値を見つけますウェル;
(私はあなたのコードを必要としないと思います)
木の直径
- コンセプト:加重最長パスツリー
- 溶液:葉ノード間の最大距離を維持する\(D1 [I] \)と二番目に大きい距離\(D2 [I] \) 、最大距離は$マックス{D1 [I] +である D2 [i]は} $
コード
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e4+5;
int n;
struct pp
{
int to,next;
}w[2*N];
int head[N],cnt;
int d1[N],d2[N];
int ans;
void add(int x,int y)
{
cnt++;
w[cnt].next=head[x];
w[cnt].to=y;
head[x]=cnt;
}
void dfs(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs(t,x);
if(d1[t]+1>d1[x])
{
d2[x]=d1[x];
d1[x]=d1[t]+1;
}
else if(d1[t]+1>d2[x]) d2[x]=d1[t]+1;
}
}
return ;
}
void find_ans(int x,int fa)
{
ans=max(ans,d1[x]+d2[x]);
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa) find_ans(t,x);
}
return;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("diam.in","r",stdin);
freopen("diam.out","w",stdout);
#endif
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
find_ans(1,0);
printf("%d",ans);
return 0;
}
例
P4480不登校の子供たち
- 木の直径と、左右のエンドポイントを取得し、その後、設定:考える(D [i]が\)は\ノードツリーである\(I \)より小さい距離の左端に、次に取得\(MAX \ {D [I] \} \) 、この値を加えた直径である(ANS \)\。
コード
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=2e5+5;
struct pp
{
int next,to;
ll qu;
}w[N*2];
int head[N],cnt;
int n,m;
bool v[N];
ll d1[N],d2[N],dl[N],dr[N];
int f1[N],f2[N];
int r,l;
ll ans,mans;
void add(int x,int y,int z)
{
w[++cnt].next=head[x];
w[cnt].qu=z;
w[cnt].to=y;
head[x]=cnt;
}
int read()
{
int f=1;
char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
int res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res*f;
}
void dfs1(int x)
{
if(v[x]) return ;
v[x]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(!v[t])
{
dfs1(t);
if(d1[t]+w[i].qu>d1[x])
{
f2[x]=f1[x];
f1[x]=f1[t];
d2[x]=d1[x];
d1[x]=d1[t]+w[i].qu;
}
else if(d1[t]+w[i].qu>d2[x]) d2[x]=d1[t]+w[i].qu,f2[x]=f1[t];
}
}
return;
}
void find_ans(int x)
{
if(v[x]) return;
v[x]=1;
if(ans<d1[x]+d2[x])
{
ans=d1[x]+d2[x];
l=f1[x];
r=f2[x];
}
for(int i=head[x];i;i=w[i].next) find_ans(w[i].to);
}
void dfs2(int x)
{
if(v[x]) return;
v[x]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(!v[t])
{
dl[t]=dl[x]+w[i].qu;
dfs2(t);
}
}
return;
}
void dfs3(int x)
{
if(v[x])return;
v[x]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(!v[t])
{
dr[t]=dr[x]+w[i].qu;
dfs3(t);
}
}
return;
}
void dfs_ans(int x)
{
if(v[x]) return;
v[x]=1;
mans=max(mans,min(dl[x],dr[x]));
for(int i=head[x];i;i=w[i].next) dfs_ans(w[i].to);
return;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("Chris.in","r",stdin);
freopen("Chris.out","w",stdout);
#endif
n=read();
m=read();
for(int i=1;i<=m;i++)
{
int x,y,z;
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
for(int i=1;i<=n;i++) f1[i]=i;
dfs1(1);
memset(v,0,sizeof(v));
find_ans(1);
memset(v,0,sizeof(v));
dfs2(l);
memset(v,0,sizeof(v));
dfs3(r);
memset(v,0,sizeof(v));
dfs_ans(1);
printf("%lld",ans+mans);
return 0;
}
センターのツリー
概念:他のノードの最小の最大距離ように、ツリーにノードを求める加重ツリー、このノードを指定されました。
溶液:それはツリーの負のエッジの重みでない場合、直微細の直径の中間点に、
しかし、ここで我々は負のエッジの重みのケースを考えてみます。
2つのケースがあります。
- (U \)\最長パスポイント上方へ\(最大[U] \) 。
- (U \)\点の下方、すなわち\(U \)最も遠いリーフノードに、に設定されている(\ D1 [U])\(CI遠くに(D2 [U] \)\)。
\(D1 [U] \)と\(D2 [u]が\)の質問は聞いてきます(アップ[U] \)\見つける方法?
または議論の分類、ましょう\(U \)の父である(\ X-)\、\(D1 [X-] \)サブノードから\(V \) 、およびそのための\(U \) 。
- もし\(U = V \!) 、次いで\(アップ[U] = maxの\ {D1 [X]、最大[X] \} + DIS [X] [T] \) 。
- もし\(U == V \) 、次いで\(アップ[U] = maxの\ {D2 [X]、最大[X] \} + DIS [X] [T] \)理由維持することである、(\ D2 [X] \)の理由。
コード
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
struct pp
{
int next,to;
}w[2*N];
int n,k;
int head[N],cnt;
int d1[N],d2[N],pre[N],u[N];
int root,far;
int read()
{
int f=1;
char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
int res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res*f;
}
void add(int x,int y)
{
cnt++;
w[cnt].next=head[x];
w[cnt].to=y;
head[x]=cnt;
}
bool cmp(int x,int y) {return x>y;}
void dfs1(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs1(t,x);
if(d1[t]+1>d1[x])
{
pre[x]=t;
d2[x]=d1[x];
d1[x]=d1[t]+1;
}
else if(d1[t]+1>d2[x]) d2[x]=d1[t]+1;
}
}
return;
}
void dfs2(int x,int fa)
{
int minx=min(u[x],d1[x]);
if(far<minx)
{
root=x;
far=minx;
}
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if (t!=fa)
{
if(pre[x]!=t) u[t]=max(d1[x],u[x])+1;
else u[t]=max(d2[x],u[x])+1;
dfs2(t,x);
}
}
return ;
}
int main()
{
n=read(),k=read();
for(int i=1;i<n;i++)
{
int x,y;
x=read(),y=read();
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,0);
printf("%d",root);
return 0;
}
例
P5536の中核都市
- アイデアは:;このセンターは、根のない風雲の木は、それが根付いた木のルートに行くことを見つけるために、そして、ルートノード以外の各ノードを見つける明らかに古い木の中心である都市があるでしょう最大深さが到達することができる(deepfar [I] \)\、ノードから最も遠いが到達可能である。次に、\(ソート\)について(deepfar [] \)\、答えは\(deepfar [K + 1 ] +1 \) 。
コード
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
struct pp
{
int next,to;
}w[2*N];
int n,k;
int head[N],cnt;
int d1[N],d2[N],pre[N],u[N];
int fardeep[N];
int root,far;
int read()
{
int f=1;
char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
int res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res*f;
}
void add(int x,int y)
{
cnt++;
w[cnt].next=head[x];
w[cnt].to=y;
head[x]=cnt;
}
bool cmp(int x,int y) {return x>y;}
void dfs1(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs1(t,x);
if(d1[t]+1>d1[x])
{
pre[x]=t;
d2[x]=d1[x];
d1[x]=d1[t]+1;
}
else if(d1[t]+1>d2[x]) d2[x]=d1[t]+1;
}
}
return;
}
void dfs2(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if (t!=fa)
{
if(pre[x]!=t) u[t]=max(d1[x],u[x])+1;
else u[t]=max(d2[x],u[x])+1;
dfs2(t,x);
}
}
return ;
}
void dfs3(int x,int fa)
{
int minx=min(u[x],d1[x]);
if(far<minx)
{
root=x;
far=minx;
}
for(int i=head[x];i;i=w[i].next) if(w[i].to!=fa) dfs3(w[i].to,x);
return;
}
void dfs4(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs4(w[i].to,x);
fardeep[x]=max(fardeep[x],fardeep[t]+1);
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("XR-3.in","r",stdin);
freopen("XR-3.out","w",stdout);
#endif
n=read(),k=read();
for(int i=1;i<n;i++)
{
int x,y;
x=read(),y=read();
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,0);
dfs3(1,0);
dfs4(root,0);
sort(fardeep+1,fardeep+1+n,cmp);
printf("%d",fardeep[k+1]+1);
return 0;
}
上記ツリーに関する古典的な質問のいくつかである、以下は今日のヒーローである - 木のDP
バックパックツリーDP
(私は実際には、左と右のサブツリーDPクラスツリーは、このカテゴリに分類されるかもしれないと思います)
例
コース
書籍の時間複雑である(\ nは^ 3)\アルゴリズムは、最適化が言うことを低減することができる、ここで説明した(\ ^ N-2)\ ;
- 最適化の一般化アイテム:私は知らない、特に何、を参照してください2009年ナショナルチーム紙-徐カイヘン「いくつかのカテゴリナップザック問題について」、詳細な説明を含みます。
最適化なし前に、DP式:
\ [DP [U] [J] = maxの\ {DP [U] [J]、DP [U] [JK- 1] + DP [V] [K] \} + V [U] \]
このように、各ノードのためでなければならない\(N ^ 2 \)激しい列挙\(J \)と\(K \) 。
最適化された、我々は、DPは、式になる:
\ [\ケース} {DP [V] [J] DP = [U]は[J] + V [V] \\ DP [U] [J] = maxの開始\ {DP [U] [J]
、DP [V] [J-1] \} \端{ケース} \] が最適化のアイテムの別の一般化、基本的なツリーバックパックDP方程式である。我々は唯一のに必要であろう(\ O(N)\)列挙\(J \)など。
コード
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m;
struct edge
{
int next,to;
}e[1000];
int rt,head[1000],tot,val[1000],dp[1000][1000];
void add(int x,int y)
{
e[++tot].next=head[x];
head[x]=tot;
e[tot].to=y;
}
void dfs(int u,int t)
{
if (t<=0) return ;
for (int i=head[u]; i; i=e[i].next)
{
int v = e[i].to;
for (int j=0; j<t; ++j) //这里j从o开始到tot-1,因为v的子树可以选择的节点是u的子树的节点数减一;
dp[v][j] = dp[u][j]+val[v];
dfs(v,t-1);
for (int j=1; j<=t; ++j)
dp[u][j] = max(dp[u][j],dp[v][j-1]);//u必须选,所以u选择j个点v只能选择j-1个点;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int a;
scanf("%d%d",&a,&val[i]);
if(a)
add(a,i);
if(!a)add(0,i);
}
dfs(0,m);
printf("%d",dp[0][m]);
}
カテゴリツリーを選択してDP
DP基本式:
\ {息子(U)}で[V \ \ {ケース} Fを始める[U] [0] = \和F [v] [1] \\ F [U] [1] =分\ {F [V] [1]、F [v] [0] \} + 1つの\端{ケース} \]
例
P2016戦略ゲーム
DP式は直接のように設定します。
コード
#include<iostream>
#include<cstdio>
using namespace std;
int n;
int dp[1605][2];
struct pp
{
int next,to;
}w[1600<<1];
int head[1600],cnt;
void add(int x,int y)
{
cnt++;
w[cnt].to=y;
w[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int x,int fa)
{
dp[x][1]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t==fa) continue;
dfs(t,x);
dp[x][0]+=dp[t][1];
dp[x][1]+=min(dp[t][0],dp[t][1]);
}
return;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a,k;
scanf("%d%d",&a,&k);
for(int i=1;i<=k;i++)
{
int b;
scanf("%d",&b);
add(a,b);
add(b,a);
}
}
dfs(0,0);
printf("%d",min(dp[0][1],dp[0][0]));
return 0;
}
DP普通の木
このツリーDPが実質的に固定式DPの二種類があり、またはいくつかの例には、それを向ける前と同じように、より柔軟です。(おかしいです
例
LOJ#10157.宮殿の衛兵
一見タイトルは、エヘン、テンプレートツリーDPを選択し、コードを再生する幸せ、お祝い0;
実際にこの質問をよく見るには党のボスが、カバーDPの質問、何も違いませんか?
この質問は、側端は2があるかもしれない、少なくとも一点を持っているが、何の上司は、ができない点がありまし片側アップ私のボールは終了ではありません。
さて、この場合にはノードuは、最低積立は、彼らが競合していなかったので、オーバー転送されていない彼の息子の選挙によってDP選挙を選択するような単純なことはできませんが、(カバーしなければならない場合、各ノードのカバレッジ半径1)、そのノードの最小要件uは次の3つのケースを検討する必要があるので、それを覆い隠しノードから転送することができます。
第一セット\(DP [U] [0 ] \) ノードを表し\(U \)は、父親によって覆われている\(U \) 、選択されていない\(DP [U] [1] \)自身の子ノードを表します。そしてカバー(U \)\、選択されていない\(DP [U] [2] \)自体をカバー表します。
だから、状態遷移方程式があります。
- 用(DP [U] [0] \)\、以来(U \)\選択されていないので、そのための\(U \)子ノード\(V \) 、のいずれか\(SON(V)\)被覆、どちらか\(V \)自分をカバーします:
\ [DP [U] [0] = \和分\ {DP [V] [1]、DP [V] [2] \} + [F [U]。\]
以下のための\(DP [U] [1] \) 、ことを確実にする\(U \)子ノードに覆われている必要がありますが、また確実にするために、\(U \)子ノードを(V \)は\父親によって覆われていません前提の下で、明らかにすることを覆われている\(DP [U] [1] \)によって(DP [V] [1 \ ] \) と\(DP [V] [2 ] \) から転送され、しかし、どのように確保するために\(DPを[U] [1 ] \) 転移の含まれている必要があります\(DP [V] [2 ] \) それ?
このとき巧妙な方法、設定されたパラメータがある:
\ [D分= \ {D、DP [V] [2] -min \ {DP [V] [1]、DP [V] [2] \} \。 } \]
\(D \)初期値\(0x7FFFFFFFで\) 。以下のためのそのような\(DP [U] [1 ] \) 状態遷移方程式がある
:。\ [DP [U] [1] = \分SUM \ {DP [V] [1]、DP [V] [2] \ } + D \]ため\(DP [U] [2] \) 、上に3つのステータスサブノードのいずれかから転送された、しかしのためにできることは明らかである\(DP [V] [0] \) 、既に再び補充された\(A [ U] \) 、しばらくの間、\(DP [U]は、[2] \) 、および1回だけ適用する必要があります\(A [U] \)、それを行う方法?単一ユニークで言い渡さ\(DP [V] [ 0] \)の場合は、コントロールから転送)\ [U](\ただ、?明らかにそれは可能ですが、あまりにも面倒再びあなたが見ることができるので、追加の考慮、それを追加します\(DP [V] [0 ] \ )のみにDP(\ [U] [2 ] \) 、その後に応じに転送DP(\ [U] [2 ] \) 要求\(DP [V] [0 ] \) 変化の状態遷移方程式変更:
\ [DP [U] [0] = \ SUM分\ {DP [V] [1]、DP [V] [2] \} \]
(\(U \)のためにV(\ \ )それは)です何であるかの感情的な理解、もし\(DP [U] [2 ] \) できなかっヘルプ\(DP [V] [0 ] \) それから転送\(DP [V] [0 ] \) 、役に立たないこと\ (DP [V] [0] \) のそれから転送(DP [U] [2] \)\、これは再度追加(\ [U])\十分である、なぜなら2 [(DP [U] \ ]を\)保証している\(U \)必要がない、選択された\(DPは[V] [0 ] \) 、再度保証。
このようなために(DP [U] [2] \)\:、状態遷移方程式がある
\ [DP [U] [2] = \分SUM \ {DP [V] [1]、DP [V] [2]。 、DP [V] [0] \} + [U] \]
スリーステート遷移方程式が存在すると結論する:
$$
\ケース} {始める
[1] DP [U] [0] = \ {SUM DP分[V]、DP [V] [2]}; \
DP [U] [1] = \和分{DP [V] [1]、DP [V] [2]} + D(D =分{D、DP [V] [2] -min {DP [ V] [1]、DP [V] [2]}})\
DP [U] [2] = \和分{DP [V] [1]、DP [V] [2]、DP [V] [0]} + [U]
\端{ケース}
$$
(だから、本の中で状態遷移方程式が間違っている明確です)
見つけることは困難、改定されない\(DP [V] [0 ] \) 以下に等しい以上でなければならない\(DP [V] [1] \)私が引っ張らときにライトコード; \(DP [U]を[2 ] \)式に移した:
\ [DP [U] [2] = \分SUM \ {DP [V] [2]、DP [V] [0] \} + [U] \]
被写体がすでに解決し、私はまだそこに行ってみたい;この式は、あなたが意味ですか?
私の感情的な理解ではということである(V \)\今ではその父親になる(U \)\カバーに、あることを保証する必要はないかもしれない\(V \) 、その息子でカバーしなければならない変更(\ DP [V] [0] \ ) それがそうであるのと同様、
(まあ、直接バーコードの上に、そんなにナンセンス、便利なものに少しBB)
コード
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1500 + 5;
int dp[N][3];
int v[N], n, root;
struct pp {
int next, to;
} w[N];
int head[N], cnt, du[N];
void add(int x, int y) {
cnt++;
w[cnt].next = head[x];
w[cnt].to = y;
head[x] = cnt;
}
void dfs(int x) {
int d = 0x7fffffff;
for (int i = head[x]; i; i = w[i].next) {
int t = w[i].to;
dfs(t);
dp[x][0] += min(dp[t][1], dp[t][2]);
dp[x][1] += min(dp[t][1], dp[t][2]);
d = min(d, dp[t][2] - min(dp[t][1], dp[t][2]));
dp[x][2] += min(dp[t][2], dp[t][0]);
}
dp[x][1] += d;
dp[x][2] += v[x];
}
int main() {
#ifndef ONLINE_JUDGE
freopen("guard.in", "r", stdin);
freopen("guard.out", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x, m;
scanf("%d", &x);
scanf("%d", &v[x]);
scanf("%d", &m);
for (int j = 1; j <= m; j++) {
int y;
scanf("%d", &y);
add(x, y);
du[y]++;
}
}
for (int i = 1; i <= n; i++)
if (!du[i])
root = i;
dfs(root);
printf("%d", min(dp[root][1], dp[root][2]));
return 0;
}
もうすぐ終わりまあ、少し時間がこれを書くために消費しても、私にとって、これはDPのこんにゃく深め理解され、収穫は小さくないが、それは時間の無駄ではない、右(エスケープ);