1. 程式人生 > >BZOJ2599:[IOI2011]Race

BZOJ2599:[IOI2011]Race

淺談樹分治:https://www.cnblogs.com/AKMer/p/10014803.html

題目傳送門:https://www.lydsy.com/JudgeOnline/problem.php?id=2599

我們設\(f_i\)為長度為\(i\)的路徑邊數最小可以是多少,依次遍歷當前根的子樹,先用\(cnt+f[k-dis]\)更新答案,再遍歷第二遍當前子樹更新\(f\)陣列。\(cnt\)表示根到當前點一共經過了多少條邊。因為\(k\)的範圍是\(10^6\)級別的,每次處理當前聯通塊前把\(f\)陣列全部賦成極大值會很慢,所以我們每次更新\(f\)陣列的時候把被改動過的\(dis\)用棧記錄下來,每次處理完當前聯通塊就彈棧並且把相應的\(f\)

陣列初始化,這樣做就是\(O(n)\)級別的了。

如果用邊分做的話,記得把新建的邊權值賦成\(-1\),因為可能會有邊權為\(0\)的邊,然後統計\(cnt\)的時候只有在碰到權值不為\(-1\)的邊才算。

時間複雜度:\(O(nlogn)\)

空間複雜度:\(O(n)\)

點分治版程式碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=2e5+5,maxm=1e6+5;;

bool vis[maxn],insta[maxm];
int n,m,tot,mx,rt,N,ans,top;
int siz[maxn],f[maxm],sta[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2];

int read() {
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    return x*f;
}

void add(int a,int b,int c) {
    pre[++tot]=now[a];
    now[a]=tot,son[tot]=b,val[tot]=c;
}

void find_rt(int fa,int u) {
    int res=0;siz[u]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[v]&&v!=fa)find_rt(u,v),siz[u]+=siz[v],res=max(res,siz[v]);
    res=max(res,N-siz[u]);
    if(res<mx)mx=res,rt=u;
}

void query(int fa,int u,int cnt,int dis) {
    if(dis<=m)ans=min(ans,cnt+f[m-dis]);siz[u]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[v]&&v!=fa)query(u,v,cnt+1,dis+val[p]),siz[u]+=siz[v];
}

void solve(int fa,int u,int cnt,int dis) {
    if(dis>m)return;
    f[dis]=min(f[dis],cnt);
    if(!insta[dis])sta[++top]=dis,insta[dis]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[v]&&v!=fa)solve(u,v,cnt+1,dis+val[p]);
}

void work(int u,int size) {
    N=size,mx=rt=n+1,find_rt(0,u),u=rt,vis[u]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[v])query(u,v,1,val[p]),solve(u,v,1,val[p]);
    ans=min(ans,f[m]);
    while(top) {
        f[sta[top]]=f[m+1];
        insta[sta[top--]]=0;
    }
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[v])work(v,siz[v]);
}

int main() {
    n=read(),m=read();
    for(int i=1;i<n;i++) {
        int a=read()+1,b=read()+1,c=read();
        add(a,b,c),add(b,a,c);
    }
    memset(f,127/3,sizeof(f));
    ans=f[0];work(1,n);
    if(ans==f[m+1])puts("-1");
    else printf("%d\n",ans);
    return 0;
}

邊分治版程式碼如下:

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> pii;
#define fr first
#define sc second

const int maxn=4e5+5,maxm=1e6+5;

bool vis[maxn],insta[maxm];
int f[maxm],siz[maxn],sta[maxn];
int n,m,tot,cnt,mx,id,N,top,ans,tmp1,tmp2;
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2];

vector<pii>to[maxn];
vector<pii>::iterator it;

int read() {
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    return x*f;
}

void add(int a,int b,int c) {
    pre[++tot]=now[a];
    now[a]=tot,son[tot]=b,val[tot]=c;
}

void find_son(int fa,int u) {
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(v!=fa)to[u].push_back(make_pair(v,val[p])),find_son(u,v);
}

void rebuild() {
    tot=1;memset(now,0,sizeof(now));
    for(int i=1;i<=cnt;i++) {
        int size=to[i].size();
        if(size<=2) {
            for(it=to[i].begin();it!=to[i].end();it++) {
                pii tmp=*it;
                add(i,tmp.fr,tmp.sc),add(tmp.fr,i,tmp.sc);
            }
        }
        else {
            pii u1=make_pair(++cnt,-1),u2;
            if(size==3)u2=to[i].front();
            else u2=make_pair(++cnt,-1);
            add(i,u1.fr,u1.sc),add(u1.fr,i,u1.sc);
            add(i,u2.fr,u2.sc),add(u2.fr,i,u2.sc);
            if(size==3) {
                for(int j=1;j<=2;j++)
                    to[cnt].push_back(to[i].back()),to[i].pop_back();
            }
            else {
                int p=0;
                for(it=to[i].begin();it!=to[i].end();it++) {
                    if(!p)to[cnt-1].push_back(*it);
                    else to[cnt].push_back(*it);p^=1;
                }
            }
        }
    }
}

void find_edge(int fa,int u) {
    siz[u]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[p>>1]&&v!=fa) {
            find_edge(u,v),siz[u]+=siz[v];
            if(abs(N-2*siz[v])<mx)mx=abs(N-2*siz[v]),id=p>>1;
        }
}


void solve(int fa,int u,int num,int dis) {
    if(dis<=m) {
        f[dis]=min(f[dis],num);
        if(!insta[dis])sta[++top]=dis,insta[dis]=1;
    }siz[u]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[p>>1]&&v!=fa)
            solve(u,v,num+(val[p]!=-1),dis+(val[p]!=-1)*val[p]),siz[u]+=siz[v];
}

void query(int fa,int u,int num,int dis) {
    if(dis+tmp1<=m)ans=min(ans,num+f[m-dis-tmp1]+tmp2);siz[u]=1;
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(!vis[p>>1]&&v!=fa)
            query(u,v,num+(val[p]!=-1),dis+(val[p]!=-1)*val[p]),siz[u]+=siz[v];
}

void work(int u,int size) {
    N=size,mx=id=cnt+1,find_edge(0,u);
    if(id==cnt+1)return;vis[id]=1;
    int u1=son[id<<1],u2=son[id<<1|1];
    tmp1=(val[id<<1]!=-1)*val[id<<1],tmp2=(val[id<<1]!=-1);
    solve(0,u1,0,0),query(0,u2,0,0);
    while(top) {
        f[sta[top]]=f[m+1];
        insta[sta[top--]]=0;
    }
    work(u1,siz[u1]),work(u2,siz[u2]);
}

int main() {
    cnt=n=read(),m=read();
    for(int i=1;i<n;i++) {
        int a=read()+1,b=read()+1,c=read();
        add(a,b,c),add(b,a,c);
    }
    find_son(0,1),rebuild();
    memset(f,127/3,sizeof(f));
    ans=f[0];work(1,cnt);
    if(ans==f[m+1])puts("-1");
    else printf("%d\n",ans);
    return 0;
}