您的位置:首页 > 其它

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

2018-03-20 19:21 459 查看
题目传送门:https://www.luogu.org/problemnew/show/P3345

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

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

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

接下来用点分治加速找答案和移动的过程。因为这棵树是静态的,可以一开始先进行一次点分,并记录下每个点到每个分治中心的距离。由于每个点顶多只会被log(n)log⁡(n)个分治中心覆盖,直接开一些nlog(n)nlog⁡(n)的数组即可,这样算答案的时候就不用求树上两点间的路径长度了。求最优点的时候,可以从第一层分治中心向周围的所有点进行试探,看往哪里走更优,然后跳到对应连通块的分治中心去继续找。修改的时候,也只需要改log(n)log⁡(n)个分治中心的信息。所以总时间是O(nlog(n)+qdlog2(n))O(nlog⁡(n)+qdlog2⁡(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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: