洛谷P3345:[ZJOI2015]幻想乡战略游戏 (动态点分治)

题目传送门:https://www.luogu.org/problemnew/show/P3345


题目分析:这题简直是噩梦,调了我一个下午。

首先考虑暴力怎么做:在树上随机找一个点,然后计算以该点为补给点的答案,并算出它周围的点的答案。如果它周围存在一个点答案更优,就往该点走,直到当前点为最优点就停止。这样一次询问就是 n2d n 2 d 的,其中 d=20 d = 20 ,因为每个点的度数不超过20。

上面这个做法的正确性,可以用一个看上去显然的结论证明:如果当前最优点为root,那么对于两个点u,v,若v在u到root的路径上,则v处算得的答案比u处优。这个结论的证明自己YY一下即可反正我就是感性认识QAQ

接下来用点分治加速找答案和移动的过程。因为这棵树是静态的,可以一开始先进行一次点分,并记录下每个点到每个分治中心的距离。由于每个点顶多只会被 log(n) log ⁡ ( n ) 个分治中心覆盖,直接开一些 nlog(n) n log ⁡ ( n ) 的数组即可,这样算答案的时候就不用求树上两点间的路径长度了。求最优点的时候,可以从第一层分治中心向周围的所有点进行试探,看往哪里走更优,然后跳到对应连通块的分治中心去继续找。修改的时候,也只需要改 log(n) log ⁡ ( n ) 个分治中心的信息。所以总时间是 O(nlog(n)+qdlog2(n)) O ( n log ⁡ ( n ) + q d log 2 ⁡ ( n ) ) 的,实际跑起来很快。

然而tututu有一种更加优美的方法:直接找整棵树的带权重心即为答案。就是找一个点,使得这个点为根时,其子树的最大权值最小。而且这等价于找一个点,使得这个点的子树的最大权值不超过总权值/2。至于正确性证明,tututu说这就是中位数定理在树上的扩展(中位数定理:x轴上有若干点,每个点有权值,要求一个到所有点距离乘以权值最小的点,带权中位数即为答案)。

这题我早上写了2h,然后一交,T了。肉眼debug发现我重心找错了,找了最大子树最大那个点。又交,WA了,然后再debug。一直没找出错,反而找出了很多变量重名的情况,我也不知道编译怎么通过的……自己出了几组小数据,手算了一下过了。上网找tututu的代码,出小数据也对了。无奈之下只好对拍,终于发现了错。主要是因为我naive地以为求出与当前重心相邻的点的答案可以 O(1) O ( 1 ) 算,只要计算对应子连通块的影响就可以了,我debug了好几次都没意识到这样不对。结果最后还是要 log(n) log ⁡ ( n ) 算。这样时间复杂度就多了个 log(n) log ⁡ ( n ) ,不过时间好像并没有差很远。归根到底,还是我没有考虑清楚。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxl=25;
typedef long long LL;

struct edge
{
    int obj;
    LL len;
    edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

int Fa[maxn][maxl];
LL Dep[maxn][maxl];
int Id[maxn][maxl];
int num[maxn];

LL Len[maxn][maxl];
int Son[maxn][maxl];
int cnt[maxn];

int Size[maxn];
int max_Size[maxn];

int tree[maxn];
int Ts;

LL sum[maxn][maxl];
LL val[maxn][maxl];

bool vis[maxn];
LL a[maxn];
int n,q;

void Add(int x,int y,LL z)
{
    cur++;
    e[cur].obj=y;
    e[cur].len=z;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

void Dfs1(int node,int fa)
{
    tree[++Ts]=node;
    Size[node]=1;
    max_Size[node]=0;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if ( son==fa || vis[son] ) continue;
        Dfs1(son,node);
        Size[node]+=Size[son];
        max_Size[node]=max(max_Size[node],Size[son]);
    }
}

void Dfs2(int node,int fa,LL dep,int id,int mid)
{
    ++num[node];
    Fa[node][ num[node] ]=mid;
    Dep[node][ num[node] ]=dep;
    Id[node][ num[node] ]=id;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if ( son==fa || vis[son] ) continue;
        Dfs2(son,node,dep+p->len,id,mid);
    }
}

void Solve(int node)
{
    Ts=0;
    Dfs1(node,node);
    if (Ts==1) return;

    int root=tree[1],tp=Size[node];
    for (int i=1; i<=Ts; i++)
    {
        int x=tree[i];
        Size[x]=max(max_Size[x],tp-Size[x]); //!!!
        if (Size[x]<Size[root]) root=x; //!!!
    }

    for (edge *p=head[root]; p; p=p->Next)
    {
        int son=p->obj;
        if (vis[son]) continue;
        ++cnt[root];
        Son[root][ cnt[root] ]=son;
        Len[root][ cnt[root] ]=p->len;
        Dfs2(son,root,p->len,cnt[root],root);
    }

    vis[root]=true;
    for (int i=1; i<=cnt[root]; i++) Solve(Son[root][i]);
}

void Work(int node,LL v)
{
    a[node]+=v;
    for (int i=1; i<=num[node]; i++)
    {
        int x=Id[node][i];
        int root=Fa[node][i];
        sum[root][x]+=(v*Dep[node][i]);
        val[root][x]+=v;
        sum[root][0]+=(v*Dep[node][i]);
        val[root][0]+=v;
    }
}

LL Calc(int node)
{
    LL temp=0;
    for (int i=1; i<=num[node]; i++)
    {
        int root=Fa[node][i];
        int x=Id[node][i];
        temp+=(sum[root][0]-sum[root][x]);
        temp+=( (val[root][0]-val[root][x]+a[root])*Dep[node][i] );
    }
    temp+=sum[node][0];
    return temp;
}

LL Find(int node,int x)
{
    if (Fa[node][x]) node=Fa[node][x];
    LL temp=Calc(node);
    LL ans=temp;
    int y=node;
    for (int i=1; i<=cnt[node]; i++)
    {
        int son=Son[node][i];
        LL now=Calc(son);
        if (now<=ans) ans=now,y=son;
    }
    if (y==node) return temp;
    else return Find(y,x+1);
}

int main()
{
    freopen("3345.in","r",stdin);
    freopen("3345.out","w",stdout);

    scanf("%d%d",&n,&q);
    for (int i=1; i<=n; i++) head[i]=NULL;
    for (int i=1; i<n; i++)
    {
        int A,b,c;
        scanf("%d%d%d",&A,&b,&c); //!!!
        Add(A,b,c);
        Add(b,A,c);
    }

    Solve(1);

    for (int i=1; i<=q; i++)
    {
        int u,e;
        scanf("%d%d",&u,&e);
        Work(u,e);
        printf("%I64d\n", Find(1,1) );
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值