1. 程式人生 > >BZOJ.1758.[WC2010]重建計劃(分數規劃 點分治 單調佇列/長鏈剖分 線段樹)

BZOJ.1758.[WC2010]重建計劃(分數規劃 點分治 單調佇列/長鏈剖分 線段樹)

題目連結 BZOJ
洛谷

點分治 單調佇列:

二分答案,然後判斷是否存在一條長度在\([L,R]\)的路徑滿足權值和非負。可以點分治。
對於(距當前根節點)深度為\(d\)的一條路徑,可以用其它子樹深度在\([L-d,R-d]\)內的最大值更新。這可以用單調佇列維護。
這需要子樹中的點按dep排好序。可以用BFS,省掉sort。

直接這樣的話,每次用之前的子樹更新當前子樹時,每次複雜度是\(O(\max\{dep\})\)的(之前子樹中最大的深度)。能被卡成\(O(n^2\log n)\)
可以再對每個點的所有子樹按最大深度排序,從小的開始計算,這樣複雜度就還是\(O(\sum dep)\)

。總複雜度\(O(n\log n\log v)\)
但是常數也比較大。

在二分前要先將點分樹建出來(直接用vector存每個點作為根時它的整棵子樹就行了)。
二分邊界最好優化下。
最長鏈的2倍不足L就跳過。優化很明顯...(8400->5500)

//36744kb   5548ms(沒有O2用一堆vector就是慢...)
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 200000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define eps 1e-9
typedef long long LL;
const int N=1e5+5;
const double INF=1ll<<60;

int L,R,Enum,H[N],nxt[N<<1],to[N<<1],len[N<<1],Min,root,sz[N];
bool vis[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Node
{
    int dep; LL dis;
};
struct Block//每個連通塊 
{
    int mxd;
    std::vector<Node> vec;
    bool operator <(const Block &x)const
    {
        return mxd<x.mxd;
    }
};
std::vector<Block> bl[N];

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AE(int u,int v,int w)
{
    to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
    to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
}
void Find_root(int x,int fa,int tot)
{
    int mx=0; sz[x]=1;
    for(int i=H[x],v; i; i=nxt[i])
        if(!vis[v=to[i]]&&v!=fa)
            Find_root(v,x,tot), sz[x]+=sz[v], mx=std::max(mx,sz[v]);
    mx=std::max(mx,tot-sz[x]);
    if(mx<Min) Min=mx, root=x;
}
void BFS(int s,int rt,int sl)
{
    static int q[N],pre[N],dep[N];
    static LL dis[N];
    int h=0,t=0; q[t++]=s, pre[s]=rt, dep[s]=1, dis[s]=sl;
    bl[rt].push_back(Block());
    std::vector<Block>::iterator it=--bl[rt].end();
    while(h<t)
    {
        int x=q[h++]; it->vec.push_back((Node){dep[x],dis[x]});
        for(int i=H[x],v; i; i=nxt[i])
            if(!vis[v=to[i]]&&v!=pre[x])
                pre[v]=x, dep[v]=dep[x]+1, dis[v]=dis[x]+len[i], q[t++]=v;
    }
    it->mxd=dep[q[h-1]];
    std::reverse(it->vec.begin(),it->vec.end());//dep從大到小 保證匹配區間是遞增的(遞減的話邊界不好找吧)。
}
void Solve(int x)
{
    vis[x]=1;
    for(int i=H[x],v; i; i=nxt[i])
        if(!vis[v=to[i]]) BFS(v,x,len[i]);
    std::sort(bl[x].begin(),bl[x].end());
    for(int i=H[x],v; i; i=nxt[i])
        if(!vis[v=to[i]]) Min=N, Find_root(v,x,sz[v]), Solve(root);
}
void Init(int n)
{
    Min=N, Find_root(1,1,n), Solve(root);
}
bool Check(int n,double X)
{
    static int q[N];
    static double mx[N];
    for(int i=1; i<=n; ++i) mx[i]=-INF;
    std::vector<Node>::iterator it2,ed2;
    std::vector<Block>::iterator it1,ed1;
    for(int s=1; s<=n; ++s)
    {
        if(!bl[s].size()||2*(--bl[s].end())->mxd<L) continue;//最長鏈的2倍不足L就跳過。優化很明顯...
        bool vic=0;
        for(it1=bl[s].begin(),ed1=bl[s].end(); it1!=ed1; ++it1)
        {
            int mxd=it1->mxd,now=1,h=1,t=0;
            for(it2=it1->vec.begin(),ed2=it1->vec.end(); it2!=ed2; ++it2)//用當前子樹的值和之前子樹的鏈匹配 
            {
                int l=std::max(0,L-it2->dep),r=std::min(mxd,R-it2->dep);//當前鏈能匹配的路徑區間 
                if(l>r) continue;
                while(now<=r)
                {
                    while(h<=t && mx[q[t]]<mx[now]) --t;
                    q[++t]=now++;
                }
                while(h<=t && q[h]<l) ++h;
                if(mx[q[h]]+it2->dis-X*it2->dep>eps) {vic=1; goto Skip;}
            }
            for(it2=it1->vec.begin(),ed2=it1->vec.end(); it2!=ed2; ++it2)//用當前子樹更新狀態,順便判斷一下是否有到根節點的滿足條件的路徑。
            {
                int d=it2->dep; mx[d]=std::max(mx[d],it2->dis-X*d);
                if(L<=d && d<=R && mx[d]>eps) {vic=1; goto Skip;}
            }
        }
        Skip: ;
        for(it1=bl[s].begin(),ed1=bl[s].end(); it1!=ed1; ++it1)
            for(it2=it1->vec.begin(),ed2=it1->vec.end(); it2!=ed2; ++it2)
                mx[it2->dep]=-INF;
        if(vic) return 1;
    }
    return 0;
}

int main()
{
    int n=read(),mn=1e6,mx=0; L=read(),R=read();
    for(int i=1,u,v,w; i<n; ++i) u=read(),v=read(),w=read(),AE(u,v,w),mn=std::min(mn,w),mx=std::max(mx,w);
    Init(n);
    double l=mn,r=mx,mid;
    for(int T=1; T<=31; ++T)
        if(Check(n,mid=(l+r)*0.5)) l=mid;
        else r=mid;
    printf("%.3lf\n",l);

    return 0;
}

長鏈剖分 線段樹: