P3806 点分治 dsu on tree做法
感言
刚学dsu on tree(树上启发式合并),后来发现这题可以使用树上启发式合并。
调了3天终于过了。纪念一下哈。
正文
由于是刚学的树上启发式合并,所以会着重讲解思路。
树上启发式合并的思想很简单:对于每个子节点,继承它重儿子的信息,再合并轻儿子的信息。而我们一般用一个全局变量来储存这个信息。
那看到这题,求满足条件的点对。想想全局变量存什么信息?
首先要知道为什么树上启发式合并可以优化时间复杂度,是因为子节点继承重儿子的信息是接近
回顾应用容斥原理的朴素算法,我们发现计数器
那么算法的流程十分清晰了:
- 先统计轻儿子的答案,不保留,清空
cnt 。 - 统计重儿子的答案,保留
cnt 。 - 合并轻儿子,并在此过程中计算出答案。
详细的点在代码中会提及:
# include <bits/stdc++.h>
# include <iostream>
using namespace std;
// 建图部分
struct Node
{
int p;
int v;
struct Node* nxt;
};
struct Head
{
struct Node* nxt;
};
struct Head p[10005];
struct Node* ini()
{
struct Node* tmp = (struct Node*) malloc (sizeof(struct Node));
tmp->nxt = NULL;
return tmp;
}
void add(int x,int y,int v)
{
struct Node* tmp1 = ini();
struct Node* tmp2 = ini();
tmp1->v = tmp2->v = v;
tmp1->nxt = p[x].nxt;
tmp1->p = y;
p[x].nxt = tmp1;
tmp2->nxt = p[y].nxt;
tmp2->p = x;
p[y].nxt = tmp2;
return ;
}
//dus on tree部分
int n,m;
int tot[10005],son[10005];
int dis[10005];//所有点的dis值
int q[10005],cnt[100000005]; // q记录重儿子子树上的所有节点 cnt是dis计数器
int nq[10005],ncnt[100000005]; //记录子树上所有节点,最后要合并到q中,作为新的子树信息
// ncnt记录链上节点的dis值
int ask[10005];
int ans[10005];
int top,ntop,root; // top,ntop分别处理q和nq
void dfs(int u,int fa)//找重儿子
{
tot[u] = 1;
int maxson=0;
for (struct Node* tmp = p[u].nxt;tmp!=NULL;tmp = tmp->nxt)
{
int v = tmp->p;
if (v == fa)
{
continue;
}
dfs(v,u);
tot[u] += tot[v];
if (tot[v] > maxson)
{
maxson = tot[v];
son[u] = v;
}
}
return ;
}
void merge(int u,int fa)
{
ncnt[dis[u]]++;//记录u的dis值,计算要用
for (struct Node* tmp = p[u].nxt;tmp!=NULL;tmp = tmp->nxt)
{
int v = tmp->p;
if (v == fa || v == son[root])
{
continue;
}
nq[++ntop] = v;//不放在外面的原因是不让root被记录下来
merge(v,u);
if (u == root)//若子树遍历完了,要遍历下一棵子树,清空这一棵子树的信息,同时合并这棵子树的信息到q和cnt中
{
while (ntop > 0)
{
q[++top] = nq[ntop];
cnt[dis[nq[ntop]]]++;
ntop--;
}
}
}
ncnt[dis[u]]--; //不用这个信息了
for (int i=1;i<=m;i++)
{
if (ask[i]+2*dis[root]-dis[u] >= 0 && cnt[ask[i]+2*dis[root]-dis[u]])//先处理不在同一子树上的情况,用的是合并过的cnt
{
ans[i] = 1;
}
if (dis[u]-ask[i] >= 0 && ncnt[dis[u]-ask[i]])//处理在同一子树上的情况,用的是当前子树的ncnt
{
ans[i] = 1;
}
}
return ;
}
void dsu(int u,int fa,bool keep) // dsu on tree主体部分,u为当前节点,fa为父节点,keep为是否保留信息
{
for (struct Node* tmp = p[u].nxt;tmp!=NULL;tmp = tmp->nxt) // 遍历轻儿子
{
int v = tmp->p;
if (v == fa) // 跳过父节点
{
continue;
}
dis[v] = dis[u] + tmp->v; // 更新儿子dis值
if (v == son[u]) // 跳过重儿子
{
continue;
}
dsu(v,u,false);
}
if (son[u]) // 若存在轻儿子
{
dsu(son[u],u,true);
for (int i=1;i<=m;i++) //注意处理重儿子子树内点对,因为是递归调用,所以回到这一层时只用考虑u->son[u]这一条边有无解
{
if (cnt[ask[i]+dis[u]])
{
ans[i]=1;
}
}
}
root = u; // 防止遍历到父节点
merge(u,fa); // 合并轻儿子
q[++top] = u;
cnt[dis[u]]++;
if (!keep) // 若u是轻儿子
{
while (top > 0)//不保留信息,清空
{
cnt[dis[q[top]]]--;
top--;
}
}
return ;
}
int main (void)
{
scanf ("%d %d",&n,&m);
for (int i=0;i<n-1;i++)
{
int x,y,v;
scanf ("%d %d %d",&x,&y,&v);
add(x,y,v);
}
for (int i=1;i<=m;i++)
{
scanf ("%d",&ask[i]);
}
dfs(1,0);
dsu(1,0,true);
for (int i=1;i<=m;i++)
{
if (ans[i] == 1)
{
printf("AYE\n");
}
else
{
printf("NAY\n");
}
}
return 0;
}