洛谷 P3345 [ZJOI2015]幻想乡战略游戏 解题报告

P3345 [ZJOI2015]幻想乡战略游戏

题目描述

傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。

在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有\(n\)块空地,这些空地被\(n-1\)条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。

在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点\(u\)上,并且空地\(v\)上有\(d_v\)个单位的军队,那么幽香每天就要花费\(d_v\times dist(u,v)\)的金钱来补给这些军队。

由于幽香需要补给所有的军队,因此幽香总共就要花费为\(\sum d_v\times dist(u,v)\),其中\(1\le V\le N\)的代价。其中\(dist(u,v)\)表示\(u\)\(v\)在树上的距离(唯一路径的权和)。

因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。

但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

输入输出格式

输入格式:

第一行两个数\(n\)\(Q\)分别表示树的点数和幽香操作的个数,其中点从\(1\)\(n\)标号。

接下来\(n-1\)行,每行三个正整数\(a,b,c\),表示\(a\)\(b\)之间有一条边权为\(c\)的边。

接下来\(Q\)行,每行两个数\(u,e\),表示幽香在点\(u\)上放了\(e\)单位个军队(如果\(e<0\),就相当于是幽香在\(u\)上减少了\(|e|\)单位个军队,说白了就是\(d_u←d_u+e\)。数据保证任何时刻每个点上的军队数量都是非负的。

输出格式:

对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。

说明

对于所有数据,\(1\le c\le 1000, 0\le|e|\le 1000, n\le 10^5\),\(Q\le 10^5\) 非常神奇的是,对于所有数据,这棵树上的点的度数都不超过\(20\),且\(N,Q\ge1\)


花了很久才弄明白啊,我这个智力可能不适合做\(OI\)...


简单来说,这题就是一个修改点权询问带权重心的题。

首先我们考虑随便选择一个点作为答案,为了方便,暂且将这个点当做树的根。

考虑将答案移向相邻点(也相当于换根)的条件,令\(sumd_i\)代表\(i\)点的子树的带权大小也就是\(\sum\limits_{v\in i}d_v\)。若想从\(now\)移动到\(v\),设\(dis_{now,v}\)代表从\(now\)\(v\)的长度。

那么移动后的答案改变量就是\((sumd_{now}-sumd_v-sumd_v)\times dis_{now,v}\),于是当\(sumd_{now}<sumd_v\times 2\)时,我们进行移动,可以简单的证明,每个点最多只有一个儿子可以移动过去。

暴力移动并进行换根是行不通的,考虑构造点分树进行移动与换根。


点分树有几个显著特点

  1. 树高\(\log\),维护信息暴力跳就可以了
  2. 点分树的子树是原树的一个联通快,这也是在点分树上进行换根操作的基础。

对这个题,我们先建好点分树,然后对每个点维护几个信息。

\(d_i\)表示自己点权,\(sumd_i\)表示\(i\)点点分树的子树\(\sum d\)\(sumf_i\)表示点分树子树所有点到点分树父亲的答案(如果一个点没有父亲,可以简单的指向它自己)

修改操作比较简单,直接在点分树向上暴力修改即可。

查询的时候,先选定点分树的根,然后遍历点分树的儿子比较\(sumd\),注意这里点分树的\(sumd\)和本身原树上儿子的\(sumd\)的值是一样的,所以更严谨的说其实找的还是原树的儿子。

如果可以移动,那么答案点一定在点\(v\)的子树中(点分树的),然后我们考虑把根换成\(v\)

如图,\(now\)是当前节点,\(v\)是应该移向的分治子树,\(w\)是在\(v\)的子树中与\(now\)在原树中直接相连的点。

然后我们直接把\(now\)及绿色子树的点权给\(w\),并在外部加上\(now\)和绿色子树本身的答案。这样就把\(v\)子树外面的点等价到了内部并且统计了外部答案。注意更新了\(w\)之后还要把\(w\)父亲的信息同样更新。

然后递归进行这个过程直到找不到一个儿子可以去移动即可,递归回溯时要把\(now\)加在\(w\)的信息进行还原。


需要\(RMQLCA\)进行\(O(1)\)的查询两点间距离保证复杂度为\(O(n\log^2n)\),事实上树剖好像也可以跑。

注意这个做法会被菊花树卡掉因为点分树儿子个数可能会很多但题目保证了度数就没关系了啦。


Code:

#include <cstdio>
#include <vector>
#define ll long long
const int N=2e5+10;
int head[N],to[N],Next[N],edge[N],cnt;
void add(int u,int v,int w)
{
    to[++cnt]=v,edge[cnt]=w,Next[cnt]=head[u],head[u]=cnt;
}
namespace RMQLCA
{
    ll dis[N];int dep[N],st[N][20],dfn[N],Log[N],dfs_clock;
    void dfs(int now,int fa)
    {
        dep[now]=dep[fa]+1;
        st[dfn[now]=++dfs_clock][0]=now;
        for(int v,i=head[now];i;i=Next[i])
            if((v=to[i])!=fa)
                dis[v]=dis[now]+edge[i],dfs(v,now),st[++dfs_clock][0]=now;
    }
    void init()
    {
        dfs(1,0);
        for(int i=2;i<=dfs_clock;i++) Log[i]=Log[i>>1]+1;
        for(int j=1;j<=18;j++)
        {
            for(int x,y,i=1;i<=dfs_clock-(1<<j)+1;i++)
            {
                x=st[i][j-1],y=st[i+(1<<j-1)][j-1];
                st[i][j]=dep[x]<dep[y]?x:y;
            }
        }
    }
    ll getdis(int x,int y)
    {
        ll ret=dis[x]+dis[y];
        x=dfn[x],y=dfn[y];
        if(x>y) std::swap(x,y);
        int d=Log[y+1-x],a=st[x][d],b=st[y-(1<<d)+1][d];
        int lca=dep[a]<dep[b]?a:b;
        return ret-(dis[lca]<<1);
    }
}
struct node
{
    int v,w,len;
    node(){}
    node(int v,int w,int len){this->v=v,this->w=w,this->len=len;}
};
std::vector <node> Edge[N];
int siz[N],del[N],fa[N],si,rt,mi,root,n,m;
void dfs(int now,int fa)
{
    siz[now]=1;int mx=0;
    for(int v,i=head[now];i;i=Next[i])
    {
        if((v=to[i])!=fa&&!del[v])
            dfs(v,now),siz[now]+=siz[v],mx=mx>siz[v]?mx:siz[v];
    }
    mx=mx>si-siz[now]?mx:si-siz[now];
    if(mi>mx) mi=mx,rt=now;
}
int getroot(int now){mi=N+1;dfs(now,0);return rt;}
void divide(int now)
{
    del[now]=1;
    for(int v,w,i=head[now];i;i=Next[i])
    {
        if(!del[w=to[i]])
        {
            si=siz[w],fa[v=getroot(w)]=now;
            Edge[now].push_back(node(v,w,edge[i]));
            divide(v);
        }
    }
}
int d[N],sumd[N];ll sumf[N];
void change(int now,int To,int delta)
{
    d[now]+=delta;int las=now;
    while(now!=To)
    {
        sumd[now]+=delta;
        sumf[now]+=RMQLCA::getdis(las,fa[now]?fa[now]:now)*delta;
        now=fa[now];
    }
}
ll cal(int now,int oth,int len)
{
    ll ret=1ll*d[now]*len;
    for(int v,i=0;i<Edge[now].size();i++)
        if((v=Edge[now][i].v)!=oth)
        {
            ret+=sumf[v];
            ret+=1ll*sumd[v]*len;
        }
    return ret;
}
ll query(int now)
{
    ll ret,Ret=0;
    for(int v,w,i=0;i<Edge[now].size();i++)
    {
        if(sumd[v=Edge[now][i].v]<<1>sumd[now])
        {
            ret=cal(now,v,Edge[now][i].len);
            int delta=sumd[now]-sumd[v];
            change(w=Edge[now][i].w,now,delta);
            ret+=query(v);
            change(w,now,-delta);
            return ret;
        }
        Ret+=sumf[v];
    }
    return Ret;
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("dew.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int u,v,w,i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w),add(v,u,w);
    }
    RMQLCA::init();
    mi=N,si=n,root=getroot(1);divide(root);
    for(int u,de,i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&de);
        change(u,0,de);
        printf("%lld\n",query(root));
    }
    return 0;
}

2018.12.5

posted @ 2018-12-05 15:42  露迭月  阅读(95)  评论(0编辑  收藏