基本思路:点分治,是一种针对可带权树上简单路径统计问题的算法。对于一个节点,只解决经过这棵子树的根节点的路径,对于子节点问题下推子树。
//当初的主要问题是vis[]在干什么qwq,终于知道了
#include<iostream>
#include<cstdio>
#include<algorithm>
#define R register int
using namespace std;
#define ull unsigned long long
#define ll long long
#define pause (for(R i=1;i<=10000000000;++i))
#define In freopen("NOIPAK++.in","r",stdin)
#define Out freopen("out.out","w",stdout)
namespace Fread {
static char B[1<<15],*S=B,*D=B;
#ifndef JACK
#define getchar() (S==D&&(D=(S=B)+fread(B,1,1<<15,stdin),S==D)?EOF:*S++)
#endif
inline int g() {
R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix;
if(ch==EOF) return EOF; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
} inline bool isempty(const char& ch) {return (ch<=36||ch>=127);}
inline void gs(char* s) {
register char ch; while(isempty(ch=getchar()));
do *s++=ch; while(!isempty(ch=getchar()));
}
} using Fread::g; using Fread::gs;
namespace Jack {
const int N=10010,Inf=0x3f3f3f3f;
int n,m,cnt,sum,rt,tot;
int vr[N<<1],nxt[N<<1],w[N<<1],fir[N],sz[N],mx[N],d[N],a[N],b[N],q[110];
bool ans[110],vis[N];
inline bool cmp(int a,int b) {return d[a]<d[b];}//按路径长度排序
inline void add(int u,int v,int ww) {vr[++cnt]=v,nxt[cnt]=fir[u],w[cnt]=ww,fir[u]=cnt;}
inline void getrt(int u,int fa) { sz[u]=1,mx[u]=0;//找根节点
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(v==fa||vis[v]) continue;//若是father(vis避免扫回父亲),就continue
getrt(v,u); sz[u]+=sz[v];//合并子树的size
mx[u]=max(mx[u],sz[v]);//取max
} mx[u]=max(mx[u],sum-sz[u]);
if(!rt||mx[u]<mx[rt]) rt=u;//选根节点
}
inline void dfs(int u,int fa,int top) {
a[++tot]=u;//将子树中的点添加到队列中
b[u]=top;//记录所属次级子树(即本次分治节点的子树)的根节点
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(v==fa||vis[v]) continue;
d[v]=d[u]+w[i]; dfs(v,u,top);
}
}
inline void calc(int u) {//计算经过u的路径条数
tot=0; a[++tot]=u;//初始化队列
d[u]=0; b[u]=u;
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(vis[v]) continue;//不访问已经分治过的father
d[v]=w[i]; dfs(v,u,v);
} sort(a+1,a+tot+1,cmp);//按到当前根的距离排序
for(R i=1;i<=m;++i) {
if(ans[i]) continue;
R l=1,r=tot; //双指针扫一遍
while(l<r) {
if(d[a[l]]+d[a[r]]>q[i]) --r;//过大则左移右指针
else if(d[a[l]]+d[a[r]]<q[i]) ++l;//过小右移左指针
else if(b[a[l]]==b[a[r]]) //同属于一棵子树
if(d[a[r]]==d[a[r-1]]) --r;//右边权值相等左移右指针
else ++l;
else {ans[i]=true; break;}
}
}
}
inline void solve(int u) { vis[u]=true; //已经过,打标记
calc(u);
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(vis[v]) continue;//vis[u],表示已经分治过的父亲。
sum=sz[v]; rt=0;
getrt(v,0);
solve(rt);//传入重心,solve
}
}
void main() {
n=g(),m=g(); for(R i=1,u,v,w;i<n;++i)
u=g(),v=g(),w=g(),add(u,v,w),add(v,u,w);
for(R i=1;i<=m;++i) q[i]=g();
mx[rt]=sum=n;
getrt(1,0);
solve(rt);
for(R i=1;i<=m;++i)
ans[i]?printf("AYE
"):printf("NAY
");
}
}
signed main() {
Jack::main();
}
后来又重新学了学点分治,改了改写法。
之前的写法其实求子树的(size)那里是不对的(就是会让下一次传进去的(sum)是错误的)
再简单记一下思路吧:
先在一棵树(原始的树或递归子树)中找出重心,分别对重心的每个子树进行求值(比如路径长度),然后对于求出的数据进行答案计数,然后分别递归每一颗子树。
#include<bits/stdc++.h>
#define R register int
using namespace std;
namespace Luitaryi {
template<class I> inline I g(I& x) { x=0; register I f=1;
register char ch; while(!isdigit(ch=getchar())) f=ch=='-'?-1:f;
do x=x*10+(ch^48); while(isdigit(ch=getchar())); return x*=f;
} const int N=10010,M=100,Inf=1e+9;
int n,m,q[M],cnt,c,sum,rt,tot; bool ans[M],vis[N],mem[10000010];
int vr[N<<1],nxt[N<<1],fir[N],w[N<<1],d[N],mx[N],sz[N],buf[N],dis[N];
inline void add(int u,int v,int ww) {
vr[++c]=v,nxt[c]=fir[u],w[c]=ww,fir[u]=c;
vr[++c]=u,nxt[c]=fir[v],w[c]=ww,fir[v]=c;
}
inline void getsz(int u,int fa) {
sz[u]=1,mx[u]=0; for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(vis[v]||v==fa) continue;
getsz(v,u); sz[u]+=sz[v];
mx[u]=max(mx[u],sz[v]);
} mx[u]=max(mx[u],sum-sz[u]);
if(mx[u]<mx[rt]) rt=u;
}
inline void getdis(int u,int fa) { dis[++cnt]=d[u];
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(vis[v]||v==fa) continue;
d[v]=d[u]+w[i]; getdis(v,u);
}
}
inline void solve(int u,int fa) { tot=0;
mem[0]=true,buf[++tot]=0,vis[u]=true;
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(vis[v]||v==fa) continue;
d[v]=w[i]; getdis(v,u);
for(R i=1;i<=cnt;++i)
for(R j=1;j<=m;++j) if(q[j]>=dis[i])
ans[j]|=mem[q[j]-dis[i]];
for(R k=1;k<=cnt;++k) buf[++tot]=dis[k],mem[dis[k]]=true;
cnt=0;
} while(tot) mem[buf[tot]]=false,--tot;
for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
if(vis[v]||v==fa) continue;
sum=sz[v]; rt=0,mx[rt]=Inf;
getsz(v,u),getsz(rt,-1);
solve(rt,u);
}
}
inline void main() {
g(n),g(m); for(R i=1,u,v,w;i<n;++i) g(u),g(v),g(w),add(u,v,w);
for(R i=1;i<=m;++i) g(q[i]); sum=n,rt=0,mx[rt]=Inf;
getsz(1,-1),getsz(rt,-1); solve(rt,-1);
for(R i=1;i<=m;++i) ans[i]?puts("AYE"):puts("NAY");
}
} signed main() {Luitaryi::main(); return 0;}
补一下点分治的一些题:
Luogu P4178 Tree
Luogu P2634 [国家集训队]聪聪可可
Luogu P2993 [FJOI2014]最短路径树问题
Luogu P4149 [IOI2011]Race
Luogu P2056 [ZJOI2007]捉迷藏
其实很多就是一些板子题,但是由于本人的代码能力过弱,思路较差,还是要赘述一番。
2019.08.29
71