点分治
点分治
点分治,是一种针对可带权树上简单路径统计问题的算法。
本质上是一种带优化的暴力,带上一点容斥的感觉。
注意对于树上路径,并不要求这棵树有根,
即我们只需要对无根树进行统计。
找重心
我们先提前算出一共有多少个节点,
然后对于每一个节点,找出它最大的儿子
然后重心就为最大的儿子最小的
代码
void find_root(int now,int last,int tot){
size[now]=1;
int bigger=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
find_root(Next,now,tot);
MAX(bigger,size[Next]);
size[now]+=size[Next];
}
MAX(bigger,tot-size[now]);
if(bigger<=biggest){
Root=now;
biggest=bigger;
}
return ;
}
寻找根节点到每一个点的路径
我们使用dis数组记录路径
比较简单,但注意要顺带把size算出来,方便后面求重心
代码
void getDis(int now,int last,int len){
dis[++st]=len;
size[now]=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
getDis(Next,now,len+d[now][i].v);
size[now]+=size[Next];
}
return ;
}
[IOI2011]Race
思路
题目要求我们找出一条权值和为k且路径长度最小的路径
方法一
考虑将所有路径,分为经过一个点的路径和不经过该点的路径。
我们可以处理出该点到所有点的权值和以及距离。记 minn[i] 为满足条件的最小的路径长度,path[i] 为权值和,cnt[i] 为路径数目。我们有转移方程 minn[k] = min(minn[k-path[i]]+cnt[i], minn[k])。这样,我们可以保证答案都是符合条件的。
接下来我们分治处理它的子节点。
注意:每一次做完一个节点,我们需要将 minn 归零。
至于我们选取的点,为树的重心,这样它的子树大小不可能大于 size[now]/2。
找重心需要 \(O(size)\) 的时间,找点要 \(O(size)\) 时间,转移次数也为 \(O(size)\)。而最多共有 \(\log n\) 层,所以时间复杂度为 \(O(n \log n)\)。
代码1(原来)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2*1e5,maxm=1e6,oo=0x3f3f3f3f;
struct Main_Tree{
struct node{
int size;
}a[maxn+5];
struct edge{
int to,w;
};
vector<edge>d[maxn+5];
bool vis[maxn+5]={false};
int biggest_child,Root,front,tail,path[maxn],cnt[maxn],n,k,x,y,z,ans=oo;
int minn[maxm+5];
void find_root(int now,int fa,int total_size){
a[now].size=1;
int biggest=0;
for(int i=0;i<d[now].size();i++){
int son=d[now][i].to;
if(son==fa||vis[son])continue;
find_root(son,now,total_size);
if(a[son].size>biggest)
biggest=a[son].size;
a[now].size+=a[son].size;
}
if(total_size-a[now].size>biggest)
biggest=total_size-a[now].size;
if(biggest_child>biggest){
Root=now;
biggest_child=biggest;
}
return ;
}
void getDis(int now,int fa,int length,int num){
tail++;
path[tail]=length;
cnt[tail]=num;
a[now].size=1;
// printf("successful\n");
for(int i=0;i<d[now].size();i++){
int son=d[now][i].to;
if(son==fa||vis[son])continue;
getDis(son,now,length+d[now][i].w,num+1);
a[now].size+=a[son].size;
}
return ;
}
void calc(int root){
front=tail=1;
path[1]=0;
cnt[1]=0;
minn[0]=0;
for(int i=0;i<d[root].size();i++){
int son=d[root][i].to;
if(vis[son])continue;
getDis(son,root,d[root][i].w,1);
for(int j=front+1;j<=tail;j++)
if(k-path[j]>=0)
ans=min(ans,minn[k-path[j]]+cnt[j]);
for(int j=front+1;j<=tail;j++)
if(path[j]<=k)
minn[path[j]]=min(minn[path[j]],cnt[j]);
front=tail;
}
// for(int i=1;i<=k;i++)cout<<minn[i]<<" ";
// cout<<endl;
for(int i=1;i<=tail;i++)
if(path[i]<=k)
minn[path[i]]=oo;
vis[root]=true;
for(int i=0;i<d[root].size();i++){
int son=d[root][i].to;
if(vis[son])continue;
solve(son,root,a[son].size);
}
return ;
}
void solve(int now,int fa,int total_size){
// printf("into solve :\n now %d fa %d total_size %d\n",now,fa,total_size);
Root=now;
biggest_child=total_size-1;
find_root(now,fa,total_size);
// printf("Root is %d \n",Root);
calc(Root);
return ;
}
void input(){
memset(minn,0x3f,sizeof minn);
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
x++;
y++;
d[x].push_back({y,z});
d[y].push_back({x,z});
}
solve(1,0,n);
if(ans==oo)ans=-1;
printf("%d",ans);
}
}T;
int main(){
T.input();
return 0;
}
代码2(更新后)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2*1e5+5,maxk=1e6+5,oo=1061109567;
int n,k,x,y,z,minn=1e9,cnt[maxk];
struct node{
int to,v;
};
vector<node>d[maxn];
void MAX(int &a,int b){
if(a<b)a=b;
return ;
}
void MIN(int &a,int b){
if(a>b)a=b;
return ;
}
struct Main_Tree{
int Root,biggest,st;
int size[maxn],dis[maxn],path[maxn];
bool vis[maxn]={false};
void find_root(int now,int last,int tot){
size[now]=1;
int bigger=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
find_root(Next,now,tot);
MAX(bigger,size[Next]);
size[now]+=size[Next];
}
MAX(bigger,tot-size[now]);
if(bigger<=biggest){
biggest=bigger;
Root=now;
}
return ;
}
void getDis(int now,int last,int len,int NUM){
dis[++st]=len;
path[st]=NUM;
size[now]=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
getDis(Next,now,len+d[now][i].v,NUM+1);
size[now]+=size[Next];
}
return ;
}
void calc(int now){
int sum=0;
st=0;
cnt[0]=0;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(vis[Next])continue;
int L,R;
L=st+1;
getDis(Next,now,d[now][i].v,1);
R=st;
for(int j=L;j<=R;j++)
if(k-dis[j]>=0)
minn=min(minn,cnt[k-dis[j]]+path[j]);
for(int j=L;j<=R;j++)
if(dis[j]<=k)
MIN(cnt[dis[j]],path[j]);
}
for(int i=1;i<=st;i++)
if(dis[i]<=k)
cnt[dis[i]]=oo;
return ;
}
void solve(int now,int tot){
biggest=tot-1,Root=now;
find_root(now,now,tot);
int rt=Root;
vis[rt]=true;
calc(rt);
for(int i=0;i<d[rt].size();i++){
int Next=d[rt][i].to;
if(vis[Next])continue;
solve(Next,size[Next]);
}
}
}Tr;
int main(){
memset(cnt,0x3f,sizeof cnt);
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
if(x==0)x=n;
if(y==0)y=n;
d[x].push_back({y,z});
d[y].push_back({x,z});
}
Tr.solve(1,n);
if(minn==1e9)minn=-1;
cout<<minn;
return 0;
}
P4178 Tree
题目描述
给定一棵 \(n\) 个节点的树,每条边有边权,求出树上两点距离小于等于 \(k\) 的点对数量。
输入格式
第一行输入一个整数 \(n\),表示节点个数。
第二行到第 \(n\) 行每行输入三个整数 \(u,v,w\) ,表示 \(u\) 与 \(v\) 有一条边,边权是 \(w\)。
第 \(n+1\) 行一个整数 \(k\) 。
输出格式
一行一个整数,表示答案。
样例 #1
样例输入 #1
7 1 6 13 6 3 9 3 5 7 4 1 3 2 4 20 4 7 2 10样例输出 #1
5提示
数据规模与约定
对于全部的测试点,保证:
- \(1\leq n\leq 4\times 10^4\)。
- \(1\leq u,v\leq n\)。
- \(0\leq w\leq 10^3\)。
- \(0\leq k\leq 2\times 10^4\)。
方法一
实际上我们可以考虑使用容斥原理,我们把从重心出发的路径全部收集到一个数组,
然后减去不合法的方案:来自同一个子树的
注意,我们采用二分进行统计,我们每次只能选两个不同的数
因此我们从这个数的前一位开始找符合条件的数
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=4*1e4+5;
int size[maxn],dis[maxn];
int Root,st=0,ans=0;
int n,k,x,y,z;
struct node{
int to,v;
};
vector<node>d[maxn];
bool vis[maxn]={false};
int biggest;
void MAX(int &a,int b){
if(b>a)a=b;
return ;
}
struct Main_Tree{
void find_root(int now,int last,int tot){
size[now]=1;
int bigger=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
find_root(Next,now,tot);
MAX(bigger,size[Next]);
size[now]+=size[Next];
}
MAX(bigger,tot-size[now]);
if(bigger<=biggest){
Root=now;
biggest=bigger;
}
return ;
}
void getDis(int now,int last,int len){
dis[++st]=len;
size[now]=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
getDis(Next,now,len+d[now][i].v);
size[now]+=size[Next];
}
return ;
}
int calc(int now,int tmp){
int sum=0;
st=0;
getDis(now,now,tmp);
sort(dis+1,dis+st+1);
for(int i=st;i>=1;i--){
int num=max((int)(upper_bound(dis+1,dis+i-1 +1,k-dis[i])-(dis) -1),0);
sum+=num;
}
return sum;
}
void solve(int now,int tot){
biggest=tot,Root=now;
find_root(now,now,tot);
int rt=Root;
vis[rt]=true;
ans+=calc(rt,0);
for(int i=0;i<d[rt].size();i++){
int Next=d[rt][i].to;
if(vis[Next])continue;
ans-=calc(Next,d[rt][i].v);
solve(Next,size[Next]);
}
}
}Tr;
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
d[x].push_back({y,z});
d[y].push_back({x,z});
}
scanf("%d",&k);
Tr.solve(1,n);
cout<<ans;
return 0;
}
方法二
该题与上一题不同的是,题目要求我们求权值和小于等于k的路径数目,
我们仅需要在上一题的基础上,使用线段树维护即可
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5*1e4;
struct Tree{
int a[maxn+5];
void update(int now,int c){
now++;
for(;now<=maxn+1;now+=now&(-now))a[now]+=c;
}
int check(int now){
int ans=0;
now++;
for(;now>0;now-=now&(-now))ans+=a[now];
return ans;
}
}cnt;
struct Main_Tree{
struct edge{
int to,w;
};
vector<edge>d[maxn+5];
int size[maxn+5],path[maxn+5],big_child_size,front,tail,k,ans=0,n,m,x,y,z,root;
bool vis[maxn+5]={false};
void find_root(int now,int fa,int total_size){
size[now]=1;
int big_size=0;
// printf("now %d fa %d total_size %d successful\n",now,fa,total_size);
for(int i=0;i<d[now].size();i++){
int son=d[now][i].to;
if(son==fa||vis[son])continue;
find_root(son,now,total_size);
if(size[son]>big_size)
big_size=size[son];
size[now]+=size[son];
}
if(total_size-size[now]>big_size)
big_size=total_size-size[now];
if(big_child_size>big_size){
root=now;
big_child_size=big_size;
}
// cout<<now<<" "<<big_size<<endl;
return ;
}
void getDis(int now,int fa,int length){
path[++tail]=length;
size[now]=1;
for(int i=0;i<d[now].size();i++){
int son=d[now][i].to;
if(son==fa||vis[son])continue;
getDis(son,now,length+d[now][i].w);
size[now]+=size[son];
}
}
void calc(int Root){
front=tail=1;
path[1]=0;
cnt.update(0,1);
vis[Root]=true;
for(int i=0;i<d[Root].size();i++){
int son=d[Root][i].to;
if(vis[son])continue;
getDis(son,Root,d[Root][i].w);
for(int j=front+1;j<=tail;j++){
if(k-path[j]>=0)
ans+=cnt.check(k-path[j]);
}
for(int j=front+1;j<=tail;j++)
cnt.update(path[j],1);
front=tail;
}
for(int i=1;i<=tail;i++)cnt.update(path[i],-1);
// for(int i=1;i<=tail;i++)cout<<path[i]<<" ";
// cout<<endl;
// for(int i=1;i<=n;i++)cout<<vis[i]<<" ";
// cout<<endl;
// for(int i=1;i<=n;i++)cout<<size[i]<<" ";
// cout<<endl;
for(int i=0;i<d[Root].size();i++){
int son=d[Root][i].to;
// printf("son %d of %d:\n",son,Root);
if(vis[son])continue;
solve(son,Root,size[son]);
}
return ;
}
void solve(int now,int fa,int total_size){
// printf("now solve into :%d %d %d \n",now,fa,total_size);
root=now;
big_child_size=total_size-1;
find_root(now,fa,total_size);
// printf("root:%d\n",root);
calc(root);
}
void input(){
cin>>n;
for(int i=1;i<=n-1;i++){
cin>>x>>y>>z;
d[x].push_back({y,z});
d[y].push_back({x,z});
}
cin>>k;
solve(1,0,n);
cout<<ans;
}
}T;
int main(){
T.input();
return 0;
}
P5351 Ruri Loves Maschera
对于这道题,我们就和前几题一样先找重心,然后算出重心距离其他点权值的最大值以及路径长度,
接下来,不同于前面的题,我们对这个数组按权值从小到大排序,我们用树状数组记录路径长度为i的路径数目
在枚举的过程中,我们查询树状数组中[l-path[i],r-path[i]]的路径数目
但这可能出现一个问题,有可能有的答案来自同一个子树,这就不符合要求,于是我们减去同一颗子树的贡献即可
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
struct Fenwick_Tree{
int tr[maxn];
void update(int now,int c){
now++;
for(;now<=1e5+1;now+=now&(-now))tr[now]+=c;
return ;
}
int check(int now){
now++;
if(now<0)return 0;
int ans=0;
for(;now;now-=now&(-now))ans+=tr[now];
return ans;
}
}T;
struct node{
int maxx,path;
}a[maxn];
bool cmp(node x,node y){
if(x.maxx==y.maxx)return x.path<y.path;
return x.maxx<y.maxx;
}
void MAX(int &a,int b){
if(a<b)a=b;
}
struct Main_Tree{
struct edge{
int to,w;
};
int n,l,r,x,y,z,biggest,Root,front,st,ans=0;
vector<edge>d[maxn];
int size[maxn];
bool vis[maxn]={false};
void find_root(int now,int fa,int total){
size[now]=1;
int bigger=0;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==fa||vis[Next])continue;
find_root(Next,now,total);
MAX(bigger,size[Next]);
size[now]+=size[Next];
}
MAX(bigger,total-size[now]);
if(bigger<=biggest){
Root=now;
biggest=bigger;
}
return ;
}
void get_Dis(int now,int last,int maxx,int length){
a[++st]={maxx,length};
size[now]=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
get_Dis(Next,now,max(maxx,d[now][i].w),length+1);
size[now]+=size[Next];
}
return ;
}
void calc(int root,int flag,int st_w,int st_dep){
st = 0;
get_Dis(root,root,st_w,st_dep);
sort(a+1,a+st+1,cmp);
for(int i=1,j=1;i<=st;i++){
ans+=(T.check(r-a[i].path)-T.check(l-a[i].path-1))*a[i].maxx*flag;
T.update(a[i].path,1);
}
for(int i=1;i<=st;i++){
T.update(a[i].path,-1);
}
return ;
}
void solve(int now,int total){
biggest=total+1;
find_root(now,now,total);
now=Root;
vis[now]=true;
calc(now,1,0,0);
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(vis[Next])continue;
calc(Next,-1,d[now][i].w,1);
}
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(vis[Next])continue;
solve(Next,size[Next]);
}
}
void input(){
scanf("%lld%lld%lld",&n,&l,&r);
for(int i=1;i<n;i++){
scanf("%lld%lld%lld",&x,&y,&z);
d[x].push_back({y,z});
d[y].push_back({x,z});
}
solve(1,n);
cout<<ans*2<<endl;
}
}Tr;
signed main(){
Tr.input();
return 0;
}
树上黑点路径
题目描述
给定一棵n个点的树,树上有m个黑点,求出一条简单路径,
使得这条简单路径经过的黑点数小于等于k,且路径长度(即路径上的边的权值总和)最大。
输入格式
第一行,n,k,m。0<=n<=200000, 0<=m<=n, 0<=k<=m。
接下来有m行,每行一个整数x,表示结点x是黑色结点。
接下来有n-1行,每行3个整数:u,v,w表示u到v有一条边,边权是w,-10000<=w<=10000
输出格式
一个整数。
思路
这一次我们无法进行容斥,因此我们在找完一个子树的dis之后,我们立刻就在线段树里查询,然后再插入到线段树里
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2*1e5+5;
int dark[maxn],L[maxn],R[maxn],maxx=0;
int n,k,m,x,y,z;
struct edge{
int to,v;
};
vector<edge>d[maxn];
void MAX(int &a,int b){
if(a<b)a=b;
return ;
}
struct Tree{
int tr[maxn*4];
void update(int now,int l,int r,int x,int c,int whi){
if(l>r)return ;
if(l==r&&l==x){
MAX(tr[now],c);
if(whi)tr[now]=0;
return ;
}
int mid=(l+r)/2;
if(x<=mid)update(now*2,l,mid,x,c,whi);
else update(now*2+1,mid+1,r,x,c,whi);
MAX(tr[now],max(tr[now*2],tr[now*2+1]));
if(whi)tr[now]=0;
return ;
}
int check(int now,int l,int r,int x){
if(x<l)return 0;
if(r<=x)return tr[now];
int mid=(l+r)/2;
return max(check(now*2,l,mid,x),check(now*2+1,mid+1,r,x));
}
}T;
struct Main_Tree{
int size[maxn],biggest,Rt,st=0;
int dis1[maxn],dis2[maxn];
bool vis[maxn]={false};
void find_root(int now,int last,int tot){
// cout<<biggest<<endl;
size[now]=1;
int bigger=1;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
find_root(Next,now,tot);
MAX(bigger,size[Next]);
size[now]+=size[Next];
}
MAX(bigger,tot-size[now]);
// cout<<now<<" "<<bigger<<" "<<biggest<<" "<<Rt<<endl;
if(bigger<=biggest){
Rt=now;
biggest=bigger;
}
return ;
}
void getDis(int now,int last,int num,int len){
size[now]=1;
dis1[++st]=num,dis2[st]=len;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(Next==last||vis[Next])continue;
getDis(Next,now,num+dark[Next],len+d[now][i].v);
size[now]+=size[Next];
}
return ;
}
void calc(int now){
// cout<<now<<endl;
int Num=0;
st=0;
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(vis[Next])continue;
L[++Num]=st+1;
getDis(Next,now,dark[Next],d[now][i].v);
R[Num]=st;
for(int j=L[Num];j<=R[Num];j++){
if(k-dark[now]-dis1[j]>=0)
MAX(maxx,dis2[j]+T.check(1,0,n,k-dark[now]-dis1[j]));
}
for(int j=L[Num];j<=R[Num];j++){
T.update(1,0,n,dis1[j],dis2[j],0);
}
// cout<<"now:"<<Num<<" "<<maxx<<endl;
}
// for(int i=1;i<=st;i++)cout<<dis1[i]<<" "<<dis2[i]<<endl;
// cout<<"final:"<<maxx<<endl;
for(int i=1;i<=st;i++)T.update(1,0,n,dis1[i],-1,1);
return ;
}
void solve(int now,int tot){
biggest=tot+1;
// cout<<biggest<<endl;
// cout<<"find root\n";
find_root(now,now,tot);
// cout<<"finish\n";
now=Rt;
vis[now]=true;
calc(now);
for(int i=0;i<d[now].size();i++){
int Next=d[now][i].to;
if(vis[Next])continue;
solve(Next,size[Next]);
}
return ;
}
}Tr;
int main(){
scanf("%d%d%d",&n,&k,&m);
for(int i=1;i<=m;i++){
scanf("%d",&x);
dark[x]=1;
}
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
d[x].push_back({y,z});
d[y].push_back({x,z});
}
// cout<<endl;
Tr.solve(1,n);
cout<<maxx;
return 0;
}

浙公网安备 33010602011771号