浅谈点分治
Luogu P4178 Tree
我觉得淀粉质还是得从这道题讲起。
点分治,是一种处理树上路径问题的工具,我认为其思想还是对于子树分治处理。
这道题要求距离小于等于 \(k\) 的点对数量,考虑怎么淀粉质。因为我们很容易就可以 \(dfs\) 求出子树中的点到根节点的距离,所以我们只用找出任意两条路径小于等于 \(k\)(不需要管是不是不同在子树),再利用总情况减去不合法情况就好了。
关于点分治,因为要保证复杂度,所以应该在树的重心点分治,所以我们需要找出树的重心。
找出路径后怎么统计答案呢?可以用两个指针来统计
先让所有路径按长度从小到大排序,初始时:\(l=1,r=cnt\)。
while(l<r){
if(f[l]+f[r]<=k){//符合条件
res+=r-l;//统计答案
l++;//跳较小的,更容易再次满足条件
}
else r--;//只有跳大的了
}
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
return ans*f;
}
const int N=4e4+5;
struct lzz{
int nx,to,w;
}edge[N<<1];
int n,hd[N],tot,k,ans,cnt,vis[N],f[N],dis[N],sz[N],root,rootsum,sum;
void adde(int u,int v,int val){
edge[++tot].nx=hd[u];edge[tot].to=v;edge[tot].w=val;hd[u]=tot;
edge[++tot].nx=hd[v];edge[tot].to=u;edge[tot].w=val;hd[v]=tot;
}
void dfs(int u,int fa){
f[++cnt]=dis[u];//找出路径
for(int i=hd[u];i;i=edge[i].nx){
int v=edge[i].to;
if(v==fa||vis[v]) continue;
dis[v]=dis[u]+edge[i].w;
dfs(v,u);
}
}
void findroot(int u,int fa){//求重心
sz[u]=1;
int maxx=0;
for(int i=hd[u];i;i=edge[i].nx){
int v=edge[i].to;
if(v==fa||vis[v]) continue;
findroot(v,u);
sz[u]+=sz[v];
maxx=max(maxx,sz[v]);
}
maxx=max(maxx,sum-sz[u]);
if(rootsum>maxx){
root=u;
rootsum=maxx;
}
}
int cul(int u,int val){//计算答案
int res=0;
memset(dis,0,sizeof(dis));
cnt=0;
dis[u]=val;
dfs(u,0);
sort(f+1,f+1+cnt);//排序
int l=1,r=cnt;//尺取法?好像是这么叫的
while(l<r){
if(f[l]+f[r]<=k){
res+=r-l;
l++;
}
else r--;
}
return res;
}
void solve(int u,int fa){
vis[u]=1;
ans+=cul(u,0);//总情况数
for(int i=hd[u];i;i=edge[i].nx){
int v=edge[i].to;
if(v==fa||vis[v]) continue;
ans-=cul(v,edge[i].w);//不合法情况数
sum=sz[v];
rootsum=n;
findroot(v,u);
solve(root,0);
}
}
int main(){
n=read();
for(int i=1;i<n;i++){
int x=read(),y=read(),z=read();
adde(x,y,z);
}
k=read();
sum=n;
rootsum=n;
findroot(1,0);
solve(root,0);
printf("%d",ans);
return 0;
}
Luogu P3806 【模板】点分治1
考虑像上一题类似的做法做,用一个数组 \(check\) 来表示第 \(i\) 个询问是否合法,不过这题不能由全部情况减去不符合条件的情况得到答案,所以我们需要另外判断这两点是否是在同一子树内的,思路大致和上题相同。
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
return ans*f;
}
const int N=1e4+5;
int n,m;
int q[N];
int hd[N],nx[N<<1],to[N<<1],w[N<<1],tot;
int check[N];
int root,rootsum,sum;
int sz[N],vis[N];
void adde(int u,int v,int val){
nx[++tot]=hd[u];to[tot]=v;w[tot]=val;hd[u]=tot;
nx[++tot]=hd[v];to[tot]=u;w[tot]=val;hd[v]=tot;
}
void findroot(int u,int father){
sz[u]=1;
int maxx=0;
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(v==father||vis[v]) continue;
findroot(v,u);
sz[u]+=sz[v];
maxx=max(maxx,sz[v]);
}
maxx=max(maxx,sum-sz[u]);
if(rootsum>maxx){
root=u;
rootsum=maxx;
}
}
struct QK{
int id,val;
bool operator<(const QK x)const{
return val<x.val;
}
}f[N];
int dis[N],type[N],cnt;
void dfs(int u,int father,int d,int sub){
f[++cnt].val=d;
f[cnt].id=u;
type[u]=sub;
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(v==father||vis[v]) continue;
dfs(v,u,d+w[i],sub);
}
}
void calc(int u){
cnt=0;
f[++cnt].id=u;//f数组取出路径
f[cnt].val=0;
type[u]=u;//type数组标记属于哪个子树
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(vis[v]) continue;
dfs(v,u,w[i],v);
}
sort(f+1,f+1+cnt);
for(int i=1;i<=m;i++){
if(check[i]) continue;
int l=1,r=cnt;
while(l<r){
if(f[l].val+f[r].val>q[i]) r--;//如果大了就跳大的
else{
if(f[l].val+f[r].val<q[i]) l++;//小了就跳小的,贪心
else
if(type[f[l].id]==type[f[r].id]){//在同一子树内
if(f[r].val==f[r-1].val) r--;
else l++;
}
else{check[i]=1;break;}
}
}
}
}
void solve(int u){
vis[u]=1;
calc(u);
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(vis[v]) continue;
rootsum=n;
sum=sz[v];
findroot(v,u);
solve(root);
}
}
int main(){
n=read(),m=read();
for(int i=1;i<n;i++){
int x=read(),y=read(),z=read();
adde(x,y,z);
}
for(int i=1;i<=m;i++){
q[i]=read();
if(!q[i]) check[i]=1;
}
sum=n;
rootsum=n;
findroot(1,0);
solve(root);
for(int i=1;i<=m;i++){
if(check[i]) printf("AYE\n");
else printf("NAY\n");
}
return 0;
}

浙公网安备 33010602011771号