洛谷P3806 点分治1 & POJ1741 Tree & CF161D Distance in Tree

正解:点分治

解题报告:

传送门1! 传送门2! 传送门3!

点分治板子有点多,,,分开写题解的话就显得很空旷,不写又不太好毕竟初学还是要多写下题解便于理解

于是灵巧发挥压行选手习惯,开始压题解(bushi

不扯辣,按顺序港下这几道题QwQ

T1

就港两个方面趴,一个是怎么找重心一个是怎么分治

对于怎么找重心,直接dfs,然后记录每个子树的size,因为重心的定义是sizemaxmin,所以存一下sizemax然后比较一下就好了

然后怎么分治呢

根据点分治的定义,我们就是以重心为根然后分开治理各个子树,最后只要处理的就是经过重心点的路径辣

然后对于经过重心的点的距离(u,v)就=dis(u,root)+dis(root,v)

然后开个桶记录下能构成的长度就可以了

然后对于询问也开个桶存能否达到

这样复杂度就是O(nlogn)的

昂然后说一下,如果有只WA在第五个点的注意一下,就是在查某个询问能否达到的时候,要先判断询问和当前边的长度的大小关系(具体见代码中count函数),不然可能数组越界,但这题很迷的是数组越界不会报RE,而是告诉你,WA辣,就可能比较难找出来,加个判断就好QwQ

然后如果不开桶好像也能做,,,就是复杂度O(n2logn)的

不开询问桶(但是也开了桶记录能构成的长度),只是枚举的不是询问而是点(因为m<=100而n<=1000所以上面那个方法复杂度好看一些),就直接for循环两两枚举点就好

也是能过的QwQ

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register
#define gc getchar()
#define ll long long
#define rp(i,x,y) for(rg ll i=x;i<=y;++i)
#define e(i,x) for(rg ll i=head[x];i;i=edge[i].nxt) const ll N=+,M=+,inf=+;
ll n,m,cnt,ques[M],sz[N],mxsz[N],sum,que[N],head[N],rt,rem[N],dis[N];
bool ext[M],jud[inf],vis[N];
struct ed{ll to,nxt,wei;}edge[N<<]; il ll read()
{
rg char ch=gc;rg ll x=;rg bool y=;
while(ch!='-' && (ch>'' || ch<''))ch=gc;
if(ch=='-')ch=gc,y=;
while(ch>='' && ch<='')x=(x<<)+(x<<)+(ch^''),ch=gc;
return y?x:-x;
}
il void ad(ll x,ll y,ll z){edge[++cnt]=(ed){x,head[y],z};head[y]=cnt;}
il void dfs(ll x,ll fa)
{
sz[x]=;mxsz[x]=;
e(i,x){if(edge[i].to==fa || vis[edge[i].to])continue;dfs(edge[i].to,x);sz[x]+=sz[edge[i].to];mxsz[x]=max(mxsz[x],sz[edge[i].to]);}
mxsz[x]=max(mxsz[x],sum-sz[x]);if(mxsz[x]<mxsz[rt])rt=x;
}
il void dfs2(ll x,ll fa){rem[++rem[]]=dis[x];e(i,x){if(vis[edge[i].to] || edge[i].to==fa)continue;dis[edge[i].to]=dis[x]+edge[i].wei;dfs2(edge[i].to,x);}}
il void count(ll x)
{
cnt=;
e(i,x)
{
if(vis[edge[i].to])continue;
rem[]=;dis[edge[i].to]=edge[i].wei;dfs2(edge[i].to,x);
rp(j,,rem[])rp(k,,m)if(ques[k]>=rem[j])ext[k]|=jud[ques[k]-rem[j]];
rp(j,,rem[])jud[que[++cnt]=rem[j]]=;
}
rp(i,,cnt)jud[que[i]]=;
}
il void solv(ll x)
{
// printf("rt=%lld\n",x);
vis[x]=jud[]=;count(x);
e(i,x){if(vis[edge[i].to])continue;sum=sz[edge[i].to],mxsz[rt=]=inf;dfs(edge[i].to,);solv(rt);}
} int main()
{
n=read();m=read();rp(i,,n-){ll x=read(),y=read(),z=read();ad(x,y,z);ad(y,x,z);}rp(i,,m)ques[i]=read();
mxsz[rt]=sum=n;dfs(,);solv(rt);rp(i,,m)if(ext[i])printf("AYE\n");else printf("NAY\n");
return ;
}

这是$O(nlogn^{2})$的代码QwQ

T2

其实和T1差不多嘛,,,唯一的区别就是它要求的是<=,考虑怎么实现?

开个桶+树状数组(用平衡树什么的当然也能过去,,,)维护一下就好

然后就麻油什么新意了,差不多QwQ

昂然后其实这题也可以不用树状数组,考虑双指针,这个小技巧还挺有意思的有时间专门写下?

然后怎么实现,,,挺简单的看代码趴懒得港辣,two pointer这个东西其实还挺好理解的,只是有时候比较难想到而已,实现也real简单(感jio比树状数组简单实现些!(虽然也跑得慢些,,,QAQ)(加了一堆rg+吸氧之后海星QwQ

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register
#define gc getchar()
#define ll int
#define rp(i,x,y) for(rg ll i=x;i<=y;++i)
#define e(i,x) for(rg ll i=head[x];i;i=edge[i].nxt) const ll N=+;
ll n,m,cnt,sz[N],mxsz[N],sum,head[N],rt,dis[N],as,gdgs,nodecnt,num[N];
bool vis[N];
struct ed{ll to,nxt,wei;}edge[N<<];
struct node{ll dis,bl;}nod[N]; il ll read()
{
rg char ch=gc;rg ll x=;rg bool y=;
while(ch!='-' && (ch>'' || ch<''))ch=gc; if(ch=='-')ch=gc,y=;
while(ch>='' && ch<='')x=(x<<)+(x<<)+(ch^''),ch=gc;
return y?x:-x;
}
il void ad(rg ll x,rg ll y,rg ll z){edge[++cnt]=(ed){x,head[y],z};head[y]=cnt;}
il void dfs(rg ll x,rg ll fa)
{
sz[x]=;mxsz[x]=;
e(i,x){if(edge[i].to==fa || vis[edge[i].to])continue;dfs(edge[i].to,x);sz[x]+=sz[edge[i].to];mxsz[x]=max(mxsz[x],sz[edge[i].to]);}
mxsz[x]=max(mxsz[x],sum-sz[x]);if(mxsz[x]<mxsz[rt])rt=x;
}
il bool cmp(rg node gd,rg node gs){return gd.dis<gs.dis;}
il void dfs2(rg ll x,rg ll fa,rg ll son){nod[++nodecnt]=(node){dis[x],son};++num[son];e(i,x){if(vis[edge[i].to] || edge[i].to==fa)continue;dis[edge[i].to]=dis[x]+edge[i].wei;dfs2(edge[i].to,x,son);}}
il void count(rg ll x)
{
cnt=;nodecnt=;nod[++nodecnt]=(node){,x};
e(i,x){if(vis[edge[i].to])continue;dis[edge[i].to]=edge[i].wei;dfs2(edge[i].to,x,edge[i].to);}sort(nod+,nod+nodecnt+,cmp);ll l=,r=nodecnt;
while(l<r){while(l<r && nod[l].dis+nod[r].dis>gdgs)--num[nod[r--].bl];if(l<r)as+=r-l-num[nod[l].bl],num[nod[++l].bl]--;}
}
il void solv(rg ll x)
{
vis[x]=;count(x);
e(i,x){if(vis[edge[i].to])continue;sum=sz[edge[i].to],mxsz[rt=]=N;dfs(edge[i].to,);solv(rt);}
} int main()
{
n=read();rp(i,,n-){ll x=read(),y=read(),z=read();ad(x,y,z);ad(y,x,z);} gdgs=read();mxsz[rt]=sum=n;dfs(,);solv(rt);printf("%d\n",as); return ;
}

T3

和T1T2都差不多嘛,不要小于等于就更简单辣不用树状数组直接做就好了

没了

然后我发现直接做好像会超时,,,?(也可能是我代码太丑陋辣呜呜呜,,,

所以get一个新方法,用双指针(这个小技巧是真挺有用的欸,,,教练我想学$bushi$

先对dis排序,然后用双指针lr,显然在排序之后能保证它不会跳来跳去而是一直往后走(有点儿像莫队?大概理解一下就好,具体看代码趴QAQ)

这样就从O(n2)降到了O(n)!

对了这题可以用树形dp,,,大概港下

f[x][i]:点x走i步能到达的点的个数

转移也很好想鸭,f[x][i]+=f[edge[x].to][i-1]

最后统计答案有两种,一种直接f[x][k],一种是f[edge[x].to][i-1]*(f[x][K-i]-f[edge[x].to][k-i-1]),即以x为lca的点对,这里注意因为(u,v)与(v,u)算一种,所以第二种要/2

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register
#define gc getchar()
#define ll long long
#define rp(i,x,y) for(rg ll i=x;i<=y;++i)
#define e(i,x) for(rg ll i=head[x];i;i=edge[i].nxt) const ll N=+;
ll n,m,cnt,sz[N],mxsz[N],sum,head[N],rt,dis[N],as,gdgs,nodecnt,num[N];
bool vis[N];
struct ed{ll to,nxt;}edge[N<<];
struct node{ll dis,bl;}nod[N]; il ll read()
{
rg char ch=gc;rg ll x=;rg bool y=;
while(ch!='-' && (ch>'' || ch<''))ch=gc; if(ch=='-')ch=gc,y=;
while(ch>='' && ch<='')x=(x<<)+(x<<)+(ch^''),ch=gc;
return y?x:-x;
}
il void ad(ll x,ll y){edge[++cnt]=(ed){x,head[y]};head[y]=cnt;}
//il void updat(ll x,ll val){while(x<=gdgs)tr[x]+=val,x+=lowbit(x);}
//il ll query(ll x){ll as=0;while(x)as+=tr[x];return as;}
il bool cmp(node gd,node gs){return gd.dis<gs.dis;}
il void dfs(ll x,ll fa)
{
sz[x]=;mxsz[x]=;
e(i,x){if(edge[i].to==fa || vis[edge[i].to])continue;dfs(edge[i].to,x);sz[x]+=sz[edge[i].to];mxsz[x]=max(mxsz[x],sz[edge[i].to]);}
mxsz[x]=max(mxsz[x],sum-sz[x]);if(mxsz[x]<mxsz[rt])rt=x;
}
il void dfs2(ll x,ll fa,ll son){nod[++nodecnt]=(node){dis[x],son};e(i,x){if(vis[edge[i].to] || edge[i].to==fa)continue;dis[edge[i].to]=dis[x]+;dfs2(edge[i].to,x,son);}}
il void count(ll x)
{
cnt=;nodecnt=;nod[++nodecnt]=(node){,x};
e(i,x){if(vis[edge[i].to])continue;dis[edge[i].to]=;dfs2(edge[i].to,x,edge[i].to);}sort(nod+,nod+nodecnt+,cmp);ll l=,r=nodecnt,tmp=r;
while(l<r)
{
if(l== || nod[l].dis!=nod[l-].dis)
{
while(r>tmp)--num[nod[r--].bl];cnt=;
while(l<r && nod[l].dis+nod[r].dis>gdgs)--r;
if(l>=r)break;tmp=r;
while(l<tmp && nod[l].dis+nod[tmp].dis==gdgs)++cnt,++num[nod[tmp--].bl];
}
as+=cnt-num[nod[l].bl];if(tmp==l)--num[nod[++tmp].bl],--cnt;++l;
}
}
il void solv(ll x)
{
vis[x]=;count(x);
e(i,x){if(vis[edge[i].to])continue;sum=sz[edge[i].to],mxsz[rt=]=N;dfs(edge[i].to,);solv(rt);}
} int main()
{
n=read();gdgs=read();rp(i,,n-){ll x=read(),y=read();ad(x,y);ad(y,x);} mxsz[rt]=sum=n;dfs(,);solv(rt);printf("%lld\n",as); return ;
}

改了1h的点分治代码!

上一篇:2018-2019-2 20175236实验二《Java面向对象程序设计》实验报告


下一篇:EDIUS如何实现抠图