点分治
算法学习
- 先在原树上通过siz的递归找根(因为还要算子树外的大小,所以还需要求一下整颗子树大小
- 通过遍历子树求出来每个节点的深度,一边统计答案一边计算
- 用vis打在根上的标记来分割子树,递归求解
但他的代码中有一些错误(第一篇)
一个是在计算前没有先计算子树大小,另一个是没有在修改 \(ex\) 函数时判断是否越界(ex是我的函数)
建议看我的代码的注释(P3806 )
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,K=1e7+5,inf=1e9+5;
struct edge{
int v,w;
};
vector<edge>b[N];
vector<int>query,del;
int n,root,mn,tot,m;
int dis[N],dp[N],vis[N],siz[N],ans[N];
bool ex[K];
void getsiz(int x,int f){
siz[x]=1;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getsiz(i.v,x);
siz[x]+=siz[i.v];
}
}
void getroot(int x,int f,int sum){
siz[x]=1;dp[x]=0;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getroot(i.v,x,sum);
siz[x]+=siz[i.v];
dp[x]=max(dp[x],siz[i.v]);
}
dp[x]=max(dp[x],sum-siz[x]);
if(dp[x]<mn) root=x,mn=dp[x];
}
void getdis(int x,int w,int f){
dis[++tot]=w;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getdis(i.v,w+i.w,x);
}
}
void figue(int x){
for(auto i:b[x]){
if(vis[i.v]) continue;
tot=0;
getdis(i.v,i.w,x);//每一个子树单独枚举,并且深度已经是w了,并且从i.v开始调用,记得改fa
for(int j=1;j<=tot;j++){
int cnt=0;
for(int k=0;k<query.size();k++){
if(query[k]>=dis[j]) ans[k]|=ex[query[k]-dis[j]];//先判断防止越界
}
}
for(int j=1;j<=tot;j++){
if(dis[j]<K) ex[dis[j]]=1,del.push_back(dis[j]);//防止越界
}
}
for(int k:del) ex[k]=0;
del.clear();
}
void solve(int x){
ex[0]=1;//路径正好经过root
vis[x]=1;
figue(x);
for(auto i:b[x]){
if(vis[i.v]) continue;//加了一个vis的判断
mn=inf;
getsiz(i.v,x);//因为之前并不知道子树的大小,所以要提前计算
getroot(i.v,x,siz[i.v]);//传当前子树大小
solve(root);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
b[u].push_back({v,w});
b[v].push_back({u,w});
}
for(int i=1;i<=m;i++){
int k;
scanf("%d",&k);
query.push_back(k);
}
mn=inf;
getroot(1,0,n);
solve(root);
for(int i=0;i<m;i++){
if(ans[i]) printf("AYE\n");
else printf("NAY\n");
}
}
T1:
trick:
判断一个序列中两个数的加和有多少小于k,可以枚举每一个数,然后判断有多少数小于它与k的差,树状数组维护即可
或者是二分?但这道题是动态的,我怕二分会炸
注这道题洛谷原题w是可以等于0的,所以要用支持修改0的树状数组
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+5,inf=1e9+5,K=2e4+5;
struct edge{
int v,w;
};
vector<edge>b[N];
vector<int>del;
int n,root,mn,tot,k,ans;
int dis[N],dp[N],vis[N],siz[N],tr[K];
int lowbit(int x){
return x&(-x);
}
void add(int x,int z){
if(x==0){//可能为0
tr[0]+=z;
return;
}
for(;x<K;x+=lowbit(x)){
tr[x]+=z;
}
}
int ask(int x){
int res=0;
for(;x;x-=lowbit(x)){
res+=tr[x];
}
return res+tr[0];
}
void getsiz(int x,int f){
siz[x]=1;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getsiz(i.v,x);
siz[x]+=siz[i.v];
}
}
void getroot(int x,int f,int sum){
siz[x]=1;dp[x]=0;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getroot(i.v,x,sum);
siz[x]+=siz[i.v];
dp[x]=max(dp[x],siz[i.v]);
}
dp[x]=max(dp[x],sum-siz[x]);
if(dp[x]<mn) root=x,mn=dp[x];
}
void getdis(int x,int w,int f){
dis[++tot]=w;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getdis(i.v,w+i.w,x);
}
}
void figue(int x){
// printf("root=%d\n",x);
for(auto i:b[x]){
if(vis[i.v]) continue;
tot=0;
getdis(i.v,i.w,x);//每一个子树单独枚举,并且深度已经是w了,并且从i.v开始调用,记得改fa
for(int j=1;j<=tot;j++){
if(k>=dis[j]) ans+=ask(k-dis[j]);
}
for(int j=1;j<=tot;j++){
// printf("%d %d\n",dis[j],k);
if(dis[j]<=k){
// printf("%d ",dis[j]);
ans++;//计算以重心为终点的贡献
add(dis[j],1);
del.push_back(dis[j]);
}
}
// printf("\n");
}
for(int j:del) add(j,-1);
del.clear();
}
void solve(int x){
vis[x]=1;
figue(x);
for(auto i:b[x]){
if(vis[i.v]) continue;//加了一个vis的判断
mn=inf;
getsiz(i.v,x);//因为之前并不知道子树的大小,所以要提前计算
getroot(i.v,x,siz[i.v]);//传当前子树大小
solve(root);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
b[u].push_back({v,w});
b[v].push_back({u,w});
}
scanf("%d",&k);
mn=inf;
getroot(1,0,n);
solve(root);
printf("%d",ans);
}
T2:
水题,直接秒了
考虑怎样求一个集合内两个数加和为3的倍数的个数即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5,inf=1e9+5;
struct edge{
int v,w;
};
vector<edge>b[N];
vector<int>del;
int n,root,mn,tot,k,ans1,ans2,m0,m1,m2,cnt;
int dis[N],dp[N],vis[N],siz[N];
void getsiz(int x,int f){
siz[x]=1;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getsiz(i.v,x);
siz[x]+=siz[i.v];
}
}
void getroot(int x,int f,int sum){
siz[x]=1;dp[x]=0;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getroot(i.v,x,sum);
siz[x]+=siz[i.v];
dp[x]=max(dp[x],siz[i.v]);
}
dp[x]=max(dp[x],sum-siz[x]);
if(dp[x]<mn) root=x,mn=dp[x];
}
void getdis(int x,int w,int f){
dis[++tot]=w;
for(auto i:b[x]){
if(vis[i.v]||i.v==f) continue;
getdis(i.v,w+i.w,x);
}
}
void change(int x,int z){
if(x%3==0) m0+=z;
if(x%3==1) m1+=z;
if(x%3==2) m2+=z;
}
void update(int x){
if(x%3==0) ans2+=m0+1;//单独成路径
if(x%3==1) ans2+=m2;
if(x%3==2) ans2+=m1;
}
void figue(int x){
cnt=0;
for(auto i:b[x]){
if(vis[i.v]) continue;
tot=0;
getdis(i.v,i.w,x);//每一个子树单独枚举,并且深度已经是w了,并且从i.v开始调用,记得改fa
for(int i=1;i<=tot;i++){
ans1+=cnt+1;//单独和与之前的都可以组合
update(dis[i]);
}
for(int i=1;i<=tot;i++){
cnt++;
change(dis[i],1);
}
}
m0=m1=m2=0;
}
void solve(int x){
vis[x]=1;
figue(x);
for(auto i:b[x]){
if(vis[i.v]) continue;//加了一个vis的判断
mn=inf;
getsiz(i.v,x);//因为之前并不知道子树的大小,所以要提前计算
getroot(i.v,x,siz[i.v]);//传当前子树大小
solve(root);
}
}
int gcd(int x,int y){
if(!y) return x;
return gcd(y,x%y);
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
b[u].push_back({v,w});
b[v].push_back({u,w});
}
mn=inf;
getroot(1,0,n);
solve(root);
ans1=ans1*2+n;
ans2=ans2*2+n;
int g=gcd(ans1,ans2);
printf("%d/%d",ans2/g,ans1/g);
}

浙公网安备 33010602011771号