DDPミス、少し大きなコードの量が、(実際には、私はそうではない)ので、木DPを乗算することにより、この最適化は、この問題を解決します。
------------
問題分析の意味
ツリーを染色するために、各ノードは、特定の染色コストを必要とする少なくとも2つの隣接するノードが制約の所与の数を有するように染色される必要があり、各規制要求がはるかに最小コストの条件を満足します。
アイデア解析
最初に決定せずにソリューション。もちろん、唯一の$、$ B同士父と息子の関係ならば(ここでは、親子関係を参照することは厳密に隣接している)、および$ X、Yの$解決策、その他の場合は、マルチ染色によるソリューションになることはできませんとき0です。
それをとても具体的な状態を設定する方法を、ツリー-DPを考えるのは簡単ですか!$ LCA(Aに根ざし$ Bが$サブツリーに根ざし$ $サブツリー:指定された2つの制約点のそれぞれは、B $を$ため、答えは、4つの部分から構成されてもよいです$ B $とルートとするサブツリーを引いツリー全体のサブツリーの$ LCA(a、b)は$根ざしをルートとするサブツリーに減算$ $をルートと、B)$サブツリー。$ので、$ Bは$制限はお互いに影響を与えることなく、四つの部分、4つの部分に分割し、したがって、チェーンの状態を染色するために$ $ Bが$からの影響します。
明らかに、A、Bの$制限はたったの$のLCA(a、b)は$サブツリーに影響を与えますし、$に$がマイナス$ bは$をルートとするサブツリーのルートである$サブツリーのルートであります答えは、それゆえ、我々は前出の他の三つの部分に答えることができ、その後、DPのこの部分の残りの部分を扱っています。
$ f1の[i]を[1/0] $ $ I $ときなし最小コスト、$ f2を染め/染めたときに$ I $をルートとするサブツリーで表現[I] [1/0:このような状態は、に設定することができます】最低コストは、$ DP [I] [1/0] [1/0] $ $ LCAが示さない場合染め/染めたとき$ I $をルート$ $ I $サブツリーに減算ツリー全体を表します$ I $染色/非染色および$ I $親染色/非染色(ルートとするサブツリールートとするサブツリーに減算(a、b)は$ $ I $第二の次元に対応するために、及びとき3次元最小コスト)。このように、我々は$ DPコンピューティングB $から$のLCAで(a、b)は$に答えてきたことができます。特に、現在の状態値をINFに設定されない溶液。
しかし、この時の複雑さは明らかに多すぎます。ツリーDPの方向にこの何回も同じように、ツリー乗算器の使用を最適化することが考えられます。私の$のdp $配列の定義は、私は$ 2 ^ j個の$生成祖先を$ [i] [j]は[0/1] [0/1] $ $をで表現$ dpと、変更することはマイナスの$ iをサブツリーのルートでありますときの$ I $の$染色/非染色および$ 2 ^ J $染色生成祖先/最小コスト際に非染色に根ざし$ I $サブツリー。
次の質問はどのようにDPです。上記の3つの配列を使用して、我々は、$ LCAで(a、b)は$にDPを掛けることができます。まず、最初に$、$ DP Bより深く、同じ深さで$、Bの$まで倍増、時間の$ A = Bの$であれば、彼らは直接、元の祖先と子孫の関係を持っていることを示します答え出力は、最大倍加しながらそれ以外の場合は、子ノードの$ LCA(a、b)は$まで、$ DP B、$し、その後最終処理、出力回答を行いました。DPの状態遷移を列挙する。
アイデアを整理:
1.前処理アウト$のF1、F2、$ 3つのアレイDP
1 $、$、B $同じ深さまで乗算で上方$大きい深さB
1解析この場合の$ A、Bの$等しく、そうであれば、直接出力に答え、そうでなければそれは上がっ
1 $、$は、$ LCA(a、b)は$子ノード、最終処理のために、出力応答に倍増bは
次のステップは、特定の実装を説明することになります。
実現
1.前処理$ $アレイF1
ツリー-DPの根幹、無パーティーボスと同様に、それらを繰り返すことはしません。また、処理の深さと$ D $アレイ乗算器アレイ祖先$ fが$。
ボイド DFS1(int型、FAをINT X) { [X] [F 0 ] = FA、D [x]は、D [FA] + = 1、[X] F1 [ 1 ] = P [X]を。 以下のために(int型 ; I I = I =ヘッド[X]、yは次に[I]) であれば(FAを=(Y =!版[I])) { DFS1(X、Y) F1 [X] [ 0 ] + = F1 [Y] [ 1 ]。 F1 [X] [ 1 ] + =分(F1 [Y] [ 0 ]、F1 [Y] [ 1 ])。 } }
2.前処理$ F2は$配列
$ iが非染色を$ときは、$ iが$父ノードが染めしなければならないので、$ I $父の染色は、木全体が$に減じたときに答えがある私$父は、サブツリーの回答のルートであるプラス答えのルートに根ざし$ I $弟(すなわち、同じ父ノード)サブツリーへ。$に再帰$ F1は$配列、弟のプロセスを想起I $の統計的同等iは、すなわち、$ F1 [FA] [1] -minを統計値を$答えのルート(f1で根付い$サブツリー$ I $ $ FA $は父を表し[I] [0]、F1 [i]は、[1])$、。
$ Iの$染色は、$ iの$父親色素がシミができていない場合は、私が汚しません$ $が同じであれば、とき$ I $父染色と回答、染色しないとき共感統計、統計的同等$のF1統計を取り消されたとき、$ Iの$の$。
ボイド DFS2(INT X) { ため(int型 I =ヘッド[X]、Y、Iは、I = 次に[I]) であれば(F [x]は[ 0!] =(Y = 版[I])) { F2 [Y] [ 0 ] = F2 [X] [ 1 ] + F1 [X] [ 1 ] -min(F1 [Y] [ 0 ]、F1 [Y] [ 1 ])。 F2 [Y] [ 1 ] =分(F2 [Y] [ 0 ]、F2 [X] [ 0 ] + F1 [X] [ 0 ] -f1 [Y] [ 1 ])。 DFS2(Y)。 } }
3. DP $ $アレイの前処理
最初の$のdp $アレイは無限大に初期化されます。まず、第一の処理DP初期値、つまり、の$ J = 0 $。
$ I $父の染色は、$ iの色素がシミができていない$とき、および$上記のため、答えは、明らかに、ときに私は何の解決策はありません$と$ I $の親、$染めていない、それを触れないでください同様のF2 [I] [0] $転送が、残りの部分を追加しないでください。$ I $父親は染色しないとき、$ iの$は、同様の転送、染色されている必要があり、それらを繰り返すことはしません。
乗算は、第1の2 ^ $ Jの$生成祖先のために、処理され、二次転写のための先祖生成列挙^ $ $ $ I $ J及び{J-1} $ 2 $ 2の^状態生成祖先。転送の心は、本質的に似ています。
ボイドプレ() { memsetの(DP、0x3f3f、はsizeof (DP)); DFS1(0、1)、DFS2(1); // 最初の処理F1、F2アレイ のための(INT I = 1 ; I <= N - 、Iを++ ) { DP [I] [ 0 ] [ 0 ] [ 1 ] DP = [I] [ 0 ] [ 1 ] [ 1 ] = F1 [F [I] [ 0 ] [ 1 ] -min(F1 [I] [ 0 ]、F1 [I] [ 1 ]); // 親ノード染色 DP [I] [ 0] [ 1 ] [ 0 ] = [F [I F1] [ 0 ] [ 0 ] -f1 [I] [ 1 ]; // 親ノードは染色しない } のために(INT J = 1。 ; J <= 19。 ; J ++ ) のための(INT I = 1 ; I <= N; I ++ ) { int型 FA = F [I]、[J- 1 ]、 F [I] [J] = F [FA] [J- 1 ]; // 最初の計算祖先 のために(INT X = 0 ; X < 2 ; X ++)//現在のポイント状況列挙 用(INT Y = 0 ; Y < 2、Y ++)// 2 ^ jを生成祖先状態の列挙 のために(INT Z = 0 ; Z < 2 ; Z ++)// 列挙2 ^(J-1)状態生成祖先 DP [I] [J] [X] [Y] =分(DP [I] [J] [X] [Y]、DP [I]、[J- 1 ] [ X] [Z] + DP [FA] [J- 1 ] [Z] [Y]); } }
次に、問い合わせ処理を説明します。
4. $、$まで深くシフト上のB $、同じ深さのB $
いない場合我々は、その後、交換を$ $についてより深くシリングことができます。
定義$最終的な答えB $表す4 $アンサ[0/1]のansB [0/1]、ノワ[0/1]、nowb [0/1] $、の配列、場合非染色/染色及び現在の答え。具体的には、その答えは、この祖先の現在の乗数に対する答えはサブツリーのルートです。
なぜなら制約の、処理に先立ち、A $は、$ bを別の状態点はINFに初期化されます。
同様に、転送するとき、中間点の列挙状態が転送されます。各転送の前に、ノワ$ $アレイはINFに初期化され、各転送後、値は配列$ $ $ノワANSA $アレイに割り当てられ、その後、$ $移動します。
上移之后,若$a=b$,则直接返回答案。因为当前点是$b$,而限制条件对$b$起作用,因此答案就是$ansa[y]+f2[a][y]$,即以$b$为根的子树的答案加上剩下部分的答案。
if(d[a]<d[b]) swap(a,b),swap(x,y);//令x为深度较大的点 ansa[1-x]=ansb[1-y]=INF; ansa[x]=f1[a][x],ansb[y]=f1[b][y];//初值 for(int j=19;j>=0;j--)//倍增 if(d[f[a][j]]>=d[b])//上移到同一深度 { nowa[0]=nowa[1]=INF;//初始化 for(int u=0;u<2;u++)//枚举2^j辈祖先的状态 for(int v=0;v<2;v++)//枚举当前点的状态 nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]); ansa[0]=nowa[0],ansa[1]=nowa[1],a=f[a][j];//数组值转移 } if(a==b) return ansa[y]+f2[a][y];//相等直接返回
5. 将$a,b$同时上移到$lca(a,b)$的子节点处
与上一步类似,只是同时上移,就不赘述了。
for(int j=19;j>=0;j--) if(f[a][j]!=f[b][j]) { nowa[0]=nowa[1]=nowb[0]=nowb[1]=INF; for(int u=0;u<2;u++) for(int v=0;v<2;v++) { nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]); nowb[u]=min(nowb[u],ansb[v]+dp[b][j][v][u]); } ansa[0]=nowa[0],ansa[1]=nowa[1],ansb[0]=nowb[0],ansb[1]=nowb[1],a=f[a][j],b=f[b][j]; }
6. 对$a,b,lca(a,b)$的状态进行枚举,进行最后的处理
还是一样的思想,若$lca(a,b)$染色,则$a,b$染不染色都行;否则$a,b$必须染色。式子看起来很长,实际上只是之前的式子多了一个节点罢了。
int fa=f[a][0]; ans0=f1[fa][0]-f1[a][1]-f1[b][1]+f2[fa][0]+ansa[1]+ansb[1];//lca(a,b)不染色 ans1=f1[fa][1]-min(f1[a][0],f1[a][1])-min(f1[b][0],f1[b][1])+f2[fa][1]+min(ansa[0],ansa[1])+min(ansb[0],ansb[1]);//lca(a,b)染色 return min(ans0,ans1);
完结撒花~
(所以对于正解来说type并没有什么用,不过在考场上打不出正解的时候确实是拿部分分的一个好助手)
------------
最后奉上完整代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=2e5,M=3e5,L=20; const ll INF=1e17; int n,m,tot; int p[N],d[N],f[N][L]; int head[N],ver[2*M],Next[2*M]; ll ans0,ans1; ll nowa[2],nowb[2],ansa[2],ansb[2],f1[N][2],f2[N][2],dp[N][L][2][2]; string tp; void add(int x,int y) { ver[++tot]=y,Next[tot]=head[x],head[x]=tot; ver[++tot]=x,Next[tot]=head[y],head[y]=tot; } void dfs1(int fa,int x) { f[x][0]=fa,d[x]=d[fa]+1,f1[x][1]=p[x]; for(int i=head[x],y;i;i=Next[i]) if(fa!=(y=ver[i])) { dfs1(x,y); f1[x][0]+=f1[y][1]; f1[x][1]+=min(f1[y][0],f1[y][1]); } }//1. 预处理 $f1$ 数组 void dfs2(int x) { for(int i=head[x],y;i;i=Next[i]) if(f[x][0]!=(y=ver[i])) { f2[y][0]=f2[x][1]+f1[x][1]-min(f1[y][0],f1[y][1]); f2[y][1]=min(f2[y][0],f2[x][0]+f1[x][0]-f1[y][1]); dfs2(y); } }//2. 预处理 $f2$ 数组 void pre() { memset(dp,0x3f3f,sizeof(dp)); dfs1(0,1),dfs2(1); for(int i=1;i<=n;i++) { dp[i][0][0][1]=dp[i][0][1][1]=f1[f[i][0]][1]-min(f1[i][0],f1[i][1]); dp[i][0][1][0]=f1[f[i][0]][0]-f1[i][1]; } for(int j=1;j<=19;j++) for(int i=1;i<=n;i++) { int fa=f[i][j-1]; f[i][j]=f[fa][j-1]; for(int x=0;x<2;x++) for(int y=0;y<2;y++) for(int z=0;z<2;z++) dp[i][j][x][y]=min(dp[i][j][x][y],dp[i][j-1][x][z]+dp[fa][j-1][z][y]); } }//3. 预处理 $dp$ 数组 bool check(int a,int x,int b,int y) { return !x && !y &&(f[a][0]==b || f[b][0]==a); } ll ask(int a,int x,int b,int y) { if(d[a]<d[b]) swap(a,b),swap(x,y); ansa[1-x]=ansb[1-y]=INF; ansa[x]=f1[a][x],ansb[y]=f1[b][y]; for(int j=19;j>=0;j--) if(d[f[a][j]]>=d[b]) { nowa[0]=nowa[1]=INF; for(int u=0;u<2;u++) for(int v=0;v<2;v++) nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]); ansa[0]=nowa[0],ansa[1]=nowa[1],a=f[a][j]; } if(a==b) return ansa[y]+f2[a][y];//4. 将 $a,b$ 中深度较大的一个上移,直到 $a,b$ 处于同一深度 for(int j=19;j>=0;j--) if(f[a][j]!=f[b][j]) { nowa[0]=nowa[1]=nowb[0]=nowb[1]=INF; for(int u=0;u<2;u++) for(int v=0;v<2;v++) { nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]); nowb[u]=min(nowb[u],ansb[v]+dp[b][j][v][u]); } ansa[0]=nowa[0],ansa[1]=nowa[1],ansb[0]=nowb[0],ansb[1]=nowb[1],a=f[a][j],b=f[b][j]; }//5. 将 $a,b$ 同时上移到 $lca(a,b)$ 的子节点处 int fa=f[a][0]; ans0=f1[fa][0]-f1[a][1]-f1[b][1]+f2[fa][0]+ansa[1]+ansb[1]; ans1=f1[fa][1]-min(f1[a][0],f1[a][1])-min(f1[b][0],f1[b][1])+f2[fa][1]+min(ansa[0],ansa[1])+min(ansb[0],ansb[1]); return min(ans0,ans1);//6. 对 $a,b,lca(a,b)$ 的状态进行枚举,进行最后的处理 } int main() { scanf("%d%d",&n,&m);cin>>tp; for(int i=1;i<=n;i++) scanf("%d",&p[i]); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); } pre(); for(int i=1,a,x,b,y;i<=m;i++) { scanf("%d%d%d%d",&a,&x,&b,&y); if(check(a,x,b,y)) { puts("-1"); continue; } printf("%lld\n",ask(a,x,b,y)); } return 0; }