点分治
当我们遇到一些规模较大的树上距离的问题,暴力显然不适用了,所以我们引入了一个新的算法。
点分治,顾名思义,就是在处理问题时利用点进行分治,从而达到降低时间复杂度的目的。
但是,我们又需要利用那些点来进行分治呢?
我们为了问题简单化,先假设这是棵有根树。我们为了解决这类问题,选取两个点。这两个点关于第三个点有两种关系:路径经过该点或不经过该点。
对于经过该点的是我们在分治当前点时需要考虑的,而不经过的情况,就可以交给其他的点。
可以看出,我们的大概思路时:利用一个点,对其子树进行分治。
出题人:我构造数据,使时间复杂度退化,阁下又该如何应对?
这里我们引入一个新名词:重心 三条中线的交点(bushi)
重心就是在当前子树里,最大子树最小的一个点。
code
void getroot(int x,int father){
w[x]=0; //最大子树的节点数
size[x]=1; //子树大小
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
getroot(y.y,x);
size[x]+=size[y.y];
w[x]=max(w[x],size[y.y]);
}
}
w[x]=max(w[x],sum-size[x]); //如果你是根,那么父亲也是儿子
if(!root||w[x]<=w[root]){
root=x;
}
}
我们找到了重心,接着利用重心不断分治,解决问题即可。
P3806【模板】点分治1
暴力就是 \(O(n^2)\) 的枚举点对,然后暴跳求距离,显然,会 \(T\) 的飞起,考虑点分治优化。
点分治板子不再说,直接讲求解函数。
我们枚举队列里的点,逐个比较权值和,但是依然跑得飞慢,考虑迷之优化。
我们将所有询问离线下来,如果所有询问都出现过,直接疯狂 \(return\),可以通过本题。
有个注意点,你在分治一个点时,其中两个点可能在同一棵子树里,这样就会多算贡献,我们可以在寻找子树重心时容斥掉这些点对,当然也可以预先存下它属于那棵子树。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
const int M=1e4+10;
struct abc{
int y,va;
};
int n,m,tot;
int sum,cnt,root;
int w[M],size[M];
int d[M],belong[M];
int dis[M];
int ans[M];
bool z[M],zz[N];
vector<abc>h[M];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
void getroot(int x,int father){
w[x]=0; size[x]=1;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
getroot(y.y,x);
size[x]+=size[y.y];
w[x]=max(w[x],size[y.y]);
}
}
w[x]=max(w[x],sum-size[x]);
if(!root||w[x]<=w[root]){
root=x;
}
if(tot==m){
return;
}
}
void dfs(int x,int father,int ch){
belong[x]=ch;
d[++cnt]=x;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
dis[y.y]=dis[x]+y.va;
dfs(y.y,x,ch);
}
}
if(tot==m){
return;
}
}
inline void calc(int x){
d[++cnt]=x;
dis[x]=belong[x]=0;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
dis[y.y]=y.va;
dfs(y.y,x,i+1);
}
}
for(int i=1;i<=cnt;i++){
for(int j=i+1;j<=cnt;j++){
int l=d[i],r=d[j];
if(belong[l]!=belong[r]){
if(dis[l]+dis[r]<=N){
if(zz[dis[l]+dis[r]]==1){
zz[dis[l]+dis[r]]=0;
tot++;
}
}
}
}
}
cnt=0;
if(tot==m){
return;
}
}
void solve(int x){
z[x]=1;
calc(x);
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
sum=size[y.y];
root=0;
getroot(y.y,0);
solve(root);
}
}
if(tot==m){
return;
}
}
int main(){
n=read(); m=read();
sum=n;
for(int i=1;i<n;i++){
int x=read(),y=read(),w=read();
h[x].push_back((abc){y,w});
h[y].push_back((abc){x,w});
}
for(int i=1;i<=m;i++){
ans[i]=read();
zz[ans[i]]=1;
}
getroot(1,0);
solve(root);
for(int i=1;i<=m;i++){
if(zz[ans[i]]==0){
cout<<"AYE"<<'\n';
}
else {
cout<<"NAY"<<'\n';
}
}
return 0;
}
P2634 [国家集训队] 聪聪可可
在 \(dfs\) 的时候存一下重心其他子树权值为 \(\{0,1,2\}\) 的路径数,合并一下即可。
code
#include<bits/stdc++.h>
using namespace std;
const int M=2e4+10;
struct abc{
int y,va;
};
int n,zi,mu;
int root,sum;
int w[M],size[M];
int f[3][2],dis[M],belong[M];
bool z[M];
vector<abc>h[M];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
void getroot(int x,int father){
w[x]=0; size[x]=1;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
getroot(y.y,x);
size[x]+=size[y.y];
w[x]=max(w[x],size[y.y]);
}
}
w[x]=max(w[x],sum-size[x]);
if(w[x]<=w[root]||!root){
root=x;
}
}
void dfs(int x,int father){
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
dis[y.y]=(dis[x]+y.va)%3;
f[dis[y.y]][1]++;
dfs(y.y,x);
}
}
}
void calc(int x){
f[0][0]=1;
int tot=0;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
dis[y.y]=y.va;
f[dis[y.y]][1]++;
dfs(y.y,x);
tot+=f[0][0]*f[0][1]+f[2][0]*f[1][1]+f[1][0]*f[2][1];
f[0][0]+=f[0][1]; f[1][0]+=f[1][1]; f[2][0]+=f[2][1];
f[0][1]=f[1][1]=f[2][1]=0;
}
}
zi+=tot*2;
memset(f,0,sizeof(f));
}
void solve(int x){
zi++;
z[x]=1;
calc(x);
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
sum=size[y.y];
root=0;
getroot(y.y,0);
solve(root);
}
}
}
int getgcd(int a,int b){
return b==0?a:getgcd(b,a%b);
}
int main(){
n=read();
sum=n; mu=n*n;
for(int i=1;i<n;i++){
int x=read(),y=read(),w=read();
h[x].push_back((abc){y,w%3});
h[y].push_back((abc){x,w%3});
}
getroot(1,0);
solve(root);
int gcd=getgcd(zi,mu);
cout<<zi/gcd<<"/"<<mu/gcd<<endl;
return 0;
}
P4149 [IOI2011] Race
和题解不一样,但是卡过去了
与 \(P3806\) 类似,只不过再加一些剪枝和一个双指针。
对于单点权值大于 \(k\) 的,一定不会作为答案中的一个点,直接 \(return\)。
把队列按权值排个序,然后枚举两端的权值,大于的话 \(r\)--,小于的话 \(l\)++,等于直接更新答案。
还是建议去看下题解
code
#include<bits/stdc++.h>
using namespace std;
const int inf=2147483647;
const int M=2e5+10;
struct abc{
int y,va;
};
struct abd{
int id,w;
}d[M];
struct abe{
long long x,y,to,va;
}e[M<<1];
int n,k,tot;
int sum,cnt,root,ans=inf;
int w[M],size[M],deep[M];
int belong[M];
int dis[M];
int head[M];
bool z[M];
vector<abc>h[M];
void add(int x,int y,int va){
e[++tot].x=x;
e[tot].y=y;
e[tot].va=va;
e[tot].to=head[x];
head[x]=tot;
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
bool cmp(abd a,abd b){
return a.w<b.w;
}
void getroot(int x,int father){
w[x]=0; size[x]=1;
for(long long i=head[x];i;i=e[i].to){
int y=e[i].y;
if(y!=father&&z[y]==0){
getroot(y,x);
size[x]+=size[y];
w[x]=max(w[x],size[y]);
}
}
w[x]=max(w[x],sum-size[x]);
if(w[x]<w[root]||!root){
root=x;
}
}
void dfs(int x,int father,int ch){
deep[x]=deep[father]+1;
if(deep[x]>ans||dis[x]>k){
return;
}
belong[x]=ch;
d[++cnt]=(abd){x,dis[x]};
for(long long i=head[x];i;i=e[i].to){
int y=e[i].y;
if(y!=father&&z[y]==0){
dis[y]=dis[x]+e[i].va;
dfs(y,x,ch);
}
}
}
void calc(int x){
d[++cnt]=(abd){x,0};
dis[x]=deep[x]=belong[x]=0;
for(long long i=head[x];i;i=e[i].to){
int y=e[i].y;
if(z[y]==0){
dis[y]=e[i].va;
dfs(y,x,i+1);
}
}
sort(d+1,d+cnt+1,cmp);
int ll=1,rr=cnt;
while(ll<=rr){
abd l=d[ll],r=d[rr];
if(dis[l.id]+dis[r.id]>k){
rr--;
}
if(dis[l.id]+dis[r.id]==k){
int lll=ll;
for(int i=ll+1;i<=cnt;i++){
if(dis[d[i].id]==dis[l.id]){
lll++;
}
else {
break;
}
}
int rrr=rr;
for(int i=rr-1;i>=0;i--){
if(dis[d[i].id]==dis[r.id]){
rrr--;
}
else {
break;
}
}
for(int i=ll;i<=lll;i++){
for(int j=rrr;j<=rr;j++){
if(belong[d[i].id]!=belong[d[j].id]){
ans=min(ans,deep[d[i].id]+deep[d[j].id]);
}
}
}
ll=lll+1; rr=rrr-1;
}
if(dis[l.id]+dis[r.id]<k){
ll++;
}
}
cnt=0;
}
void solve(int x){
z[x]=1;
calc(x);
for(long long i=head[x];i;i=e[i].to){
int y=e[i].y;
if(z[y]==0){
root=0; sum=size[y];
getroot(y,0);
solve(root);
}
}
}
int main(){
n=read(); k=read();
sum=n;
for(int i=1;i<n;i++){
int x=read()+1,y=read()+1,w=read();
add(x,y,w); add(y,x,w);
}
getroot(1,0);
solve(root);
if(ans==inf){
cout<<-1<<'\n';
}
else {
cout<<ans<<'\n';
}
return 0;
}
P4178 Tree
双指针+容斥
我们对于当前队列进行排序,找到对于每一个 \(l\) 使其与队列所有现存点的权值和都小于 \(k\) 的 \(r\),直接将答案加和,容掉在同一棵子树里的即可。
code
#include<bits/stdc++.h>
using namespace std;
const int M=4e4+10;
struct abc{
int y,va;
};
int n,m;
int sum,cnt,root;
int ans;
int w[M],size[M];
int d[M],dis[M];
bool z[M];
vector<abc>h[M];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
void getroot(int x,int father){
w[x]=0; size[x]=1;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
getroot(y.y,x);
size[x]+=size[y.y];
w[x]=max(w[x],size[y.y]);
}
}
w[x]=max(w[x],sum-size[x]);
if(!root||w[x]<w[root]){
root=x;
}
}
void dfs(int x,int father){
if(dis[x]>m){
return;
}
d[++cnt]=x;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(y.y!=father&&z[y.y]==0){
dis[y.y]=dis[x]+y.va;
dfs(y.y,x);
}
}
}
bool cmp(int a,int b){
return dis[a]<dis[b];
}
inline void calc(int x,int c,int w){
dis[x]=w;
d[++cnt]=x;
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
dis[y.y]=w+y.va;
dfs(y.y,x);
}
}
sort(d+1,d+cnt+1,cmp);
int l=1,r=cnt;
while(l<=r){
if(dis[d[l]]+dis[d[r]]<=m){
ans+=(r-l)*c;
l++;
}
else {
r--;
}
}
cnt=0;
}
void solve(int x){
z[x]=1;
calc(x,1,0);
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
calc(y.y,-1,y.va);
}
}
for(int i=0;i<h[x].size();i++){
abc y=h[x][i];
if(z[y.y]==0){
root=0; sum=size[y.y];
getroot(y.y,0);
solve(root);
}
}
}
int main(){
n=read();
sum=n;
for(int i=1;i<n;i++){
int x=read(),y=read(),w=read();
h[x].push_back((abc){y,w});
h[y].push_back((abc){x,w});
}
m=read();
getroot(1,0);
solve(root);
cout<<ans<<'\n';
return 0;
}
采药人的路径
麻烦的采药人
可以联想一下聪聪可可。
首先可以将阳性的药材权值设为 1,阴性的药材权值设为 -1,这样当权值和为 0 时就满足阴阳平衡的限制。
在 \(dfs\) 的时候,存一下前几颗子树的路径状态,直接合并。
code
#include<bits/stdc++.h>
using namespace std;
const long long M=1e5+10;
long long n,head[M],to[M*2],nextt[M*2],w[M*2],tot=1;
long long size[M],F[M],sum,root,f[M*2][2],g[M*2][2],ans;
long long maxn,dis[M],np[M*2],z[M];
inline long long read(){
long long x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
void add(long long x,long long y,long long z){
w[tot]=z;
to[tot]=y;
nextt[tot]=head[x];
head[x]=tot++;
}
void getroot(long long u,long long fa){
F[u]=0;
size[u]=1;
for(long long i=head[u];i;i=nextt[i]){
long long v=to[i];
if(v==fa || z[v])
continue;
getroot(v,u);
size[u] +=size[v];
F[u]=max(F[u],size[v]);
}
F[u]=max(F[u],sum - size[u]);
if(F[u] < F[root]||root==0)
root=u;
}
void dfs(long long u,long long fa){
maxn=max(maxn,abs(dis[u]));
if(np[dis[u]+M]){
g[dis[u]+M][1]++;
}
else {
g[dis[u]+M][0]++;
}
np[dis[u]+M]++;
for(long long i=head[u];i;i=nextt[i]){
long long v=to[i];
if(z[v]==0&&v!=fa){
dis[v]=dis[u]+w[i];
dfs(v,u);
}
}
np[dis[u]+M]--;
}
void solve(long long u){
z[u]=1;
long long top=0;
for(long long i=head[u];i;i=nextt[i]){
long long v=to[i];
if(z[v])
continue;
dis[v]=w[i];
maxn=0;
dfs(v,u);
top=max(top,maxn);
for(long long j=-maxn;j<=maxn;j++){
ans+=f[M+j][0]*g[M-j][1];
ans+=f[M+j][1]*g[M-j][0];
ans+=f[M+j][1]*g[M-j][1];
}
ans+=f[M][0]*g[M][0];
ans+=g[M][1];
for(long long j=-maxn;j<=maxn;j++){
f[M+j][0]+=g[M+j][0];
f[M+j][1]+=g[M+j][1];
g[M+j][0]=g[M+j][1]=0;
}
}
for(long long i=-top;i<=top;i++){
f[M+i][0]=f[M+i][1]=0;
}
for(long long i=head[u];i;i=nextt[i]){
long long v=to[i];
if(z[v]==0){
sum=size[v];root=0;
getroot(v,0);
solve(root);
}
}
}
int main(){
n=read();sum=n;
for(long long i=1;i<n;i++){
long long x=read(),y=read(),w=read();
if(w==0){
w=-1;
}
add(x,y,w);add(y,x,w);
}
getroot(1,0);
solve(root);
cout<<ans<<'\n';
return 0;
}

浙公网安备 33010602011771号