[Noip2018]保衛王國


這題寫得心累(ˇωˇ」∠)_

題面大家都有吧

Luogu P5024
BZOJ 5466

題意

~~完了我現在在難受到什么都不想寫...┐(‘~`;)┌ ~~

  有棵樹,可以花\(p_i\)代價把\(i\)點染色,要求任2個相鄰點至少有1個被染色。給出\(m\)組詢問,每次強制兩個點的狀態(染/不染),求出每次的最小花費。

咕咕咕

  \(noip2018 day2T3\) 毒瘤題,考ddp個人覺得跟天天愛跑步有得比。(代碼似乎更長)

  這里不講\(O(nm)\)的暴力\(dp\)做法(大家都會吧...)

分析

  很容易發現,暴力不優秀的地方在於每次都把以前算過的丟掉並重新\(O(n)\)算了一遍。我們考慮優化這一點

  假設沒有任何限制,我們可以簡單地先對樹進行一遍\(dp\),即\(dp[0/1][u]\)表示點\(u\)不選/選。設\(v\)\(u\)的一個兒子,就得到一個十分重要的方程:$$\left{ \begin{array}{lr} dp[0][u]=\sum dp[1][v],\ dp[1][u]=\sum min\lbrace dp[0][v],dp[1][v]\rbrace. \end{array} \right. $$

  對於一組詢問\((u,v)\),由於強制選或不選,我們會把不能出現的情況的\(dp\)值置為無限大.(比如強制\(u\)選,那么我們使\(dp[0][u]=INF\)就可以),此時我們觀察這對所有\(dp\)值的影響,如圖(以1為根)。
  
  可以發現,改變的是\(u,v\)兩點到根的兩條鏈,也可看作它們到\(lca\),再從\(lca\)到根的路徑。也就是說,其他的子樹都可以用預先處理出來的答案直接代入

  現在問題變成了如何求這幾條鏈上的答案。
  
  這里設\(L\)\(u,v\)\(lca\).那些毛一樣的東西連着小的子樹.

  其中\(A,B\)\(u,v\)所屬子樹,\(C\)是根節點\(1\)在另一邊的子樹,\(D\)\(L\)的不包含\(u,v\)的子樹。

  於是我們現在知道\(A,B\)的答案,試圖通過加粗的幾條鏈把答案更新到根節點。因為改變\(u,v\)狀態只是把一些\(dp\)值改為\(INF\),我們可以考慮也預處理出來。

倍增預處理

  我們希望預處理一個\(f\)數組使\(dp\)值能快速轉移。鑒於我們之前已經對樹\(dp\)過,在\(f\)的狀態里就不需要有關\(dp\)值的東西了(若有需要可以直接拉過來用)。我們只關心點選不選

  比如用\(f[0/1][0/1][x][y]\)表示\(x\)不選/選,\(y\)不選/選時,\(x\)\(y\)的鏈(假設\(dp\)值往一個方向更新)上能得到的最大的\(dp\)值.

  什么意思?

簡化一下就是說,我們把x到y的鏈拉出來,假設y是葉子,x是根,我們從y開始往上dp,一直到x時的答案(\(min(dp[0][x],dp[1][x])\)).

  有了這個,我們就可以直接把\(u\)\(L\)\(v\)\(L\)\(f\)的值加起來討論一下啦。然鵝空間是\(n^2\)的...於是有了倍增

  \(f[0/1][0/1][i][u]\)表示\(u\)不選/選,\(u\)往上跳\(2^i\)步的祖先不選/選時,從\(u\)開始\(dp\)到那個祖先的答案。

  轉移\(f\)數組很簡單,只需要討論一下\(u\)和那個祖先的中點的狀態,取\(min\)就行。並且可以證明,這樣的一個方程對於確定的\(x\)\(i\),值都是固定的。

  現在你可能發現問題了...

那些毛怎么辦?

顯然這些子樹都是計算過的,可以直接把\(dp\)值拉過來。所以我們重定義方程。

  \(f[0/1][0/1][i][u]\)表示\(u\)不選/選,\(u\)往上跳\(2^i\)步的祖先不選/選時,從\(u\)開始(不包括u)\(dp\)\(u\)往上跳\(2^i\)步的祖先 且計入其他子樹的最終\(dp\)值。即在整個祖先的子樹中減去u的子樹的影響。

  \(e.g.\)
  
  f[0/1][0/1][0][u]即為上圖紅色部分的貢獻。

  好了,這樣我們可以直接把\(u,v\)\(L\)的路徑上的\(f\)數組加起來,並加上\(u\)的子樹的\(dp\)值和\(v\)的子樹的\(dp\),添上圖中\(D\)的貢獻,再從\(L\)開始加到根節點,以及加上圖中\(C\)的貢獻,就能算出總費用啦...(^し^)

還有一些問題

  顯然一開始做出的\(dp\)值都是包含當前我們在做的鏈下面的子樹的貢獻的,比如我們要用新的\(u\)\(dp\)值更新出新的\(u\)的父親\(fa\)\(dp\)值時,要計算\(fa\)的別的子樹的貢獻,它保存在\(dp[0/1][fa]\)中,可是\(dp[0/1][u]\),即我們當前要替換的舊的\(dp\)值也算在里面了,怎么辦?

顯然,設新的\(u\)\(dp\)值為\(New\),新的\(fa\)\(dp\)值即為\(\left\{ \begin{array}{cc} &dp[0][fa]-dp[1][u]+New, \\ or &dp[1][fa]-min\lbrace dp[0][u],dp[1][u] \rbrace+New \end{array} \right.\)

  \(lca\)處的新\(dp\)值怎么算?

為了方便,我們在做\(u,v\)的時候更新到\(lca\)的兒子處,並討論\(lca\)的狀態即可,具體都可見代碼。

  特殊情況?

\(u,v\)在一條鏈上時,倍增\(u\)之后已經在\(lca\)上了,此時直接倍增\(lca\)即可;
\(lca\)為根節點時,顯然不需要倍增\(lca\),此時直接算出總答案。

總結

  大致步驟如下:
  1.\(dfs\)預處理\(dp\)數組和\(f\)數組(倍增數組);
  對於詢問:
  2.將深度大的點\(u\)\(v\)的深度跳(類似倍增\(lca\))同時更新新的\(dp\)值;
  3.\(u,v\)同時往上跳至\(lca\)的兒子處同時更新新的\(dp\)值;
  4.得到\(lca\)\(dp\)值,再往上跳至根節點的兒子處同時更新新的\(dp\)值;
  5.討論根節點的狀態得出最終答案。(特殊情況見上)

代碼

  好丑啊...⁄(⁄⁄•⁄ω⁄•⁄⁄)⁄

#include <iostream>
#include <cstdio>
#include <cctype>
#define il inline
#define vd void
#define mn 100005
#define INF 100000000000000 //1e14
#define rg register
#define ll long long
#define rep(i,x,y) for(register int i=x;i<=y;++i)
#define drp(i,x,y) for(register int i=x;i>=y;--i)
using namespace std;
const int Len=2333333,aa[18]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072};
char buf[Len],*p1=buf,*p2=buf,duf[Len],*q1=duf;
il char gc(); il int rd(); il vd pc(char c); il vd rt(ll x); il vd flush();
template<class T> il T Max(T a,T b){return a>b?a:b;}
template<class T> il T Min(T a,T b){return a<b?a:b;}
int n,m,ty,u,v,cnt,x,y,p[mn],h[mn],fa[19][mn],dep[mn],Log[mn];
ll dp[3][mn],f[3][3][19][mn];
struct E{int to,nxt;}e[mn<<1];
il vd Add(int u,int v){e[++cnt]=(E){v,h[u]},h[u]=cnt;}
vd Dfs(int u){
    dep[u]=dep[fa[0][u]]+1,dp[1][u]=p[u],f[0][0][0][u]=INF; //相鄰點不能都為0
    for(rg int i=1;aa[i]<dep[u];++i) fa[i][u]=fa[i-1][fa[i-1][u]];
    for(rg int i=h[u];i;i=e[i].nxt){int v=e[i].to;
        if(v!=fa[0][u]) fa[0][v]=u,Dfs(v),
            dp[0][u]+=dp[1][v],dp[1][u]+=Min(dp[0][v],dp[1][v]);
    } //以上為dp轉移
}
vd Cfs(int u){
    //f[0][1][0][u]=dp[1][fa[0][u]]-dp[0][u],
    //f[1][0][0][u]=dp[0][fa[0][u]]-dp[1][u],
    //f[1][1][0][u]=dp[1][fa[0][u]]-dp[1][u];
    f[1][0][0][u]=dp[0][fa[0][u]]-dp[1][u],
    f[0][1][0][u]=f[1][1][0][u]=dp[1][fa[0][u]]-Min(dp[0][u],dp[1][u]);
    for(rg int i=1;aa[i]<dep[u];++i){
        int F=fa[i-1][u];
        f[0][0][i][u]=Min(f[0][0][i-1][u]+f[0][0][i-1][F],f[0][1][i-1][u]+f[1][0][i-1][F]),
        f[0][1][i][u]=Min(f[0][0][i-1][u]+f[0][1][i-1][F],f[0][1][i-1][u]+f[1][1][i-1][F]),
        f[1][0][i][u]=Min(f[1][0][i-1][u]+f[0][0][i-1][F],f[1][1][i-1][u]+f[1][0][i-1][F]),
        f[1][1][i][u]=Min(f[1][0][i-1][u]+f[0][1][i-1][F],f[1][1][i-1][u]+f[1][1][i-1][F]);
    }// 4種情況的合並
    for(rg int i=h[u];i;i=e[i].nxt) if(e[i].to!=fa[0][u]) Cfs(e[i].to);
}
il vd Work(){
    if(dep[u]<dep[v]) swap(u,v),swap(x,y);
    int L;
    ll u0=INF,u1=INF,v0=INF,v1=INF,l0=INF,l1=INF,ans;
    x?u1=dp[1][u]:u0=dp[0][u],y?v1=dp[1][v]:v0=dp[0][v];
    for(rg int i=Log[dep[u]-dep[v]];i>=0;--i) if(dep[u]-aa[i]>=dep[v]){
        ll t0=u0,t1=u1;
        u0=Min(t0+f[0][0][i][u],t1+f[1][0][i][u]),
        u1=Min(t0+f[0][1][i][u],t1+f[1][1][i][u]),
        //printf("%d ",u);
        u=fa[i][u];
        //printf("%d %lld %lld\n",u,u0,u1);
    } //u往上跳
    if(u==v) L=u,y?l1=u1:l0=u0; //在1條鏈上
    else{
        for(rg int i=Log[dep[u]-1];i>=0;--i) if(fa[i][u]!=fa[i][v]){
          	ll t0=u0,t1=u1,p0=v0,p1=v1;
            u0=Min(t0+f[0][0][i][u],t1+f[1][0][i][u]),
            u1=Min(t0+f[0][1][i][u],t1+f[1][1][i][u]),
            v0=Min(p0+f[0][0][i][v],p1+f[1][0][i][v]),
            v1=Min(p0+f[0][1][i][v],p1+f[1][1][i][v]),
            u=fa[i][u],v=fa[i][v];
        } //一起跳
        L=fa[0][u],l0=dp[0][L]-dp[1][u]-dp[1][v]+u1+v1,
      	l1=dp[1][L]-Min(dp[0][u],dp[1][u])-Min(dp[0][v],dp[1][v])+Min(u0,u1)+Min(v0,v1);
    }// 注意這里減去兩個兒子的貢獻
    //printf("%d\n",L);
    if(L==1) ans=Min(l0,l1); //特判L=1
    else{
        for(rg int i=Log[dep[L]-2];i>=0;--i) if(dep[L]-aa[i]>1){
            ll t0=l0,t1=l1;
            l0=Min(t0+f[0][0][i][L],t1+f[1][0][i][L]),
            l1=Min(t0+f[0][1][i][L],t1+f[1][1][i][L]),
            L=fa[i][L];
        }//L往上跳
        ans=Min(dp[0][1]-dp[1][L]+l1,dp[1][1]-Min(dp[0][L],dp[1][L])+Min(l0,l1));
    }
    rt(ans<INF?ans:-1),pc('\n');
}
int main(){
    n=rd(),m=rd(),ty=rd();
    rep(i,1,n) p[i]=rd();
    rep(i,2,n) u=rd(),v=rd(),Add(u,v),Add(v,u),Log[i]=Log[i>>1]+1;
    Dfs(1),Cfs(1);
    //rep(i,1,n) printf("%d 0:%lld 1:%lld\n",i,dp[0][i],dp[1][i]);
    //puts("");
    //int P=1;
    //rep(i,1,n) printf("%d %lld %lld %lld %lld\n",i,f[0][1][P][i],f[1][0][P][i],f[1][1][P][i],f[0][0][P][i]);
    //puts("");
    while(m--) u=rd(),x=rd(),v=rd(),y=rd(),Work();
    return flush(),0;
}

il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Len,stdin),p1==p2)?-1:*p1++;}
il int rd(){char c; int f=1;
    while(!isdigit(c=gc())&&c!='-');
    c=='-'?f=-1,c=gc():0; int x=c^48;
    while(isdigit(c=gc())) x=((x+(x<<2))<<1)+(c^48);
    return x*f;
}
il vd pc(char c){q1==duf+Len&&fwrite(q1=duf,1,Len,stdout),*q1++=c;}
il vd rt(ll x){x<0?pc('-'),x=-x:0,pc((x>=10?rt(x/10),x%10:x)+48);}
il vd flush(){fwrite(duf,1,q1-duf,stdout);}

有點Luosuo...還有問題大家一定要提出啊ioi


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM