1. 程式人生 > >[BZOJ5466][NOIP2018]保衛王國 倍增

[BZOJ5466][NOIP2018]保衛王國 倍增

題面

首先可以寫一個暴力dp的式子,非常經典的樹形dp

\(dp[i][0]\)表示\(i\)這個點沒有駐軍,\(dp[i][1]\)就是有駐軍,\(j\)\(i\)的孩子。那麼顯然:
\[ \begin{align*} dp[i][0]&=dp[j][1]\\ dp[i][1]&=\min\{dp[j][0],dp[j][1]\} \end{align*} \]
然後我們發現,對於一個孩子\(j\),它的轉移與其他孩子無關。也就是其他孩子的值對他沒有影響。

這樣的性質決定了這道題目的可倍增性。

在說到倍增前,我們再做個預處理,設\(h[i][0/1]\)分別表示無或有駐軍的時候,除了\(i\)

的子樹外的最優值。那麼有
\[ \begin{align*} h[j][0]&=h[i][1]+dp[i][1]-\min\{dp[j][0],dp[j][1]\}\\ h[j][1]&=\min\{h[i][0]+dp[i][0]-dp[j][1]\ ,\ h[i][1]+dp[i][1]-\min\{dp[j][0],dp[j][1]\}\} \end{align*} \]
下面,我們再回去看一下題目。可以發現,每一次會影響到的點,只在\(x\)\(y\)的路徑上還有在\(lca\)到跟的路徑上。

對於其它點,根據我們之前的結論,對\(x\)\(y\)的操作,與它們無關。所以我們只需要搞定會影響到的點。

我們考慮先把路徑外的點得該用的值先做出來。對於樹上的路徑,倍增是很實用的。

定義\(f[i][j]\)表示\(i\)的第\(2^j\)層祖先,\(p[i][j][0/1][0/1]\)表示\(i\)取不取,那個祖先取不取的不計算上\(i\)的最優解。

初始化:
\[ \begin{align*} f[i][0]&=fa\\ p[i][0][0][0]&=INF\\ p[i][0][1][0]&=dp[fa][0]-dp[i][1]\\ p[i][0][0][1]&=dp[fa][1]-\min\{dp[i][0],dp[i][1]\}\\ p[i][0][1][1]&=dp[fa][1]-\min\{dp[i][0],dp[i][1]\} \end{align*} \]


轉移的話找相同的中間值轉移就可以了。
\[ \begin{align*} f[i][j]&=f[f[i][j-1]][j-1]\\ p[i][j][0][0]&=\min\{p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][0]\}\\ p[i][j][0][1]&=\min\{p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][1]\}\\ p[i][j][1][0]&=\min\{p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][0]\}\\ p[i][j][1][1]&=\min\{p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][1]\} \end{align*} \]
然後處理詢問的時候,設定\(x0,x1,y0,y1\)幾個值,存下到目前的\(x,y\)分別選/不選的答案。

如果被禁止了選還是不選,那麼就設定對應的值為\(INF\)

也還是找相同的中間值。這裡跟這個差不多,就略去轉移過程。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define REP(i,a,n) for(register int i(a);i<=(n);++i)
#define PER(i,a,n) for(register int i(a);i>=(n);--i)
#define FEC(i,x,y) for(register int i=head[x],y=g[i].to;i;i=g[i].ne,y=g[i].to)
#define dbg(...) fprintf(stderr,__VA_ARGS__)
const int SZ=(1<<21)+1;char ibuf[SZ],*iS,*iT,obuf[SZ+128],*oS=obuf,*oT=obuf+SZ-1;
#ifdef ONLINE_JUDGE
#define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SZ,stdin),(iS==iT?EOF:*iS++)):*iS++)
#else
#define gc() getchar()
#endif
template<typename I>inline void read(I&x){char c=gc();int f=0;for(;c<'0'||c>'9';c=gc())c=='-'?f=1:0;for(x=0;c>='0'&&c<='9';c=gc())x=(x<<1)+(x<<3)+(c&15);f?x=-x:0;}
inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
#define printf(...) (oS>oT&&(flush(),1),oS+=sprintf(oS,__VA_ARGS__))
template<typename A,typename B>inline char SMAX(A&a,const B&b){return a<b?a=b,1:0;}
template<typename A,typename B>inline char SMIN(A&a,const B&b){return a>b?a=b,1:0;}
typedef long long ll;typedef unsigned long long ull;typedef std::pair<int,int>pii;

const int N=100000+7,LOG=18;const ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,x,y,ha,hb,a[N],f[N][LOG],dep[N];ll dp[N][2],h[N][2],p[N][LOG][2][2],abc;//錯誤筆記:要開ll 
struct Edge{int to,ne;}g[N<<1];int head[N],tot;
inline void Addedge(int x,int y){g[++tot].to=y;g[tot].ne=head[x];head[x]=tot;}

inline void DP(int x,int fa=0){
    dp[x][0]=0;dp[x][1]=a[x];f[x][0]=fa;dep[x]=dep[fa]+1;
    FEC(i,x,y)if(y!=fa)DP(y,x),dp[x][0]+=dp[y][1],dp[x][1]+=std::min(dp[y][0],dp[y][1]);
}
inline void DP2(int x,int fa=0){
    FEC(i,x,y)if(y!=fa)h[y][0]=h[x][1]+dp[x][1]-std::min(dp[y][0],dp[y][1]),//錯誤筆記:還有一個小錯誤,因為這裡用的是刷錶轉移的,所以下一行中沒注意把x,y打混掉了。 
                       h[y][1]=std::min(h[x][0]+dp[x][0]-dp[y][1],h[x][1]+dp[x][1]-std::min(dp[y][0],dp[y][1])),DP2(y,x);//錯誤筆記:把dp打成f 這不是最重要的,最重要的是要先更新陣列再遞迴!!!
}
inline void Preprocess(){
    REP(i,1,n){
        p[i][0][0][0]=INF;
        p[i][0][1][0]=dp[f[i][0]][0]-dp[i][1];
        p[i][0][1][1]=p[i][0][0][1]=dp[f[i][0]][1]-std::min(dp[i][0],dp[i][1]);//錯誤筆記:i,x不分
    }
    REP(j,1,LOG-1)REP(i,1,n)f[i][j]=f[f[i][j-1]][j-1],
                            p[i][j][0][0]=std::min(p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][0]),
                            p[i][j][0][1]=std::min(p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][1]),
                            p[i][j][1][0]=std::min(p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][0]),
                            p[i][j][1][1]=std::min(p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][1]);
}

inline ll Solve(int x,int a,int y,int b){
    if(dep[x]<dep[y])std::swap(x,y),std::swap(a,b);
    ll x0=INF,x1=INF,y0=INF,y1=INF,tx0,ty0,tx1,ty1;
    if(!a)x0=dp[x][0];else x1=dp[x][1];
    if(!b)y0=dp[y][0];else y1=dp[y][1];
    PER(i,LOG-1,0)if(dep[f[x][i]]>=dep[y])tx0=std::min(x0+p[x][i][0][0],x1+p[x][i][1][0]),SMIN(tx0,INF),
                                          tx1=std::min(x0+p[x][i][0][1],x1+p[x][i][1][1]),SMIN(tx1,INF),
                                          x0=tx0,x1=tx1,x=f[x][i];
    if(x==y)return (b?x1:x0)+h[y][b];
    PER(i,LOG-1,0)if(f[x][i]!=f[y][i])tx0=std::min(x0+p[x][i][0][0],x1+p[x][i][1][0]),SMIN(tx0,INF),
                                      tx1=std::min(x0+p[x][i][0][1],x1+p[x][i][1][1]),SMIN(tx1,INF),
                                      ty0=std::min(y0+p[y][i][0][0],y1+p[y][i][1][0]),SMIN(ty0,INF),
                                      ty1=std::min(y0+p[y][i][0][1],y1+p[y][i][1][1]),SMIN(ty1,INF),
                                      x0=tx0,x1=tx1,x=f[x][i],y0=ty0,y1=ty1,y=f[y][i];
    return std::min(std::min(h[f[x][0]][0]-dp[x][1]-dp[y][1]+dp[f[x][0]][0]+x1+y1,(ll)h[f[x][0]][1]-std::min(dp[x][0],dp[x][1])-std::min(dp[y][0],dp[y][1])+dp[f[x][0]][1]+std::min(x0,x1)+std::min(y0,y1)),(ll)INF);//錯誤筆記:防止加的過程中會爆ll 
}

int main(){
    read(n),read(m);while(gc()!='\n');
    REP(i,1,n)read(a[i]);
    REP(i,1,n-1)read(x),read(y),Addedge(x,y),Addedge(y,x);
    DP(1);Preprocess();DP2(1);
    REP(i,1,m)read(x),read(ha),read(y),read(hb),abc=Solve(x,ha,y,hb),printf("%lld\n",abc>=INF?-1:abc);
    return flush(),0;
}