基本思想:把树分割成几个子树,从而快速解决一些问题(如树上距离小于等于k的点对数)。关键是如何取分割树,不难想到,如果能够找到一个点,这个点下面的各个子树的大小都一样,肯定是个最优点,也叫重心。当然都一样是理想情况,在实际中只要保证重心下面的子树中最大的尺寸是最小的即可。
模板题:poj 1741 树中距离小于等于k的对数
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e5+5;
bool vis[maxn];//vis:是否已经是被割的点
int dis[maxn],dep[maxn],head[maxn],maxsz[maxn],sz[maxn];
int cnt,tot,G,ans,k,n;
//dep:到重心距离,maxsz:最大子树尺寸,sz:子树尺寸
struct node
{
int v,w,nxt;
}edge[maxn];
void addedge(int u,int v,int w)
{
edge[++cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
void getG(int x,int fa)//找重心
{
maxsz[x]=0;
sz[x]=1;
for(int i=head[x];~i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa||vis[v])continue;
getG(v,x);
sz[x]+=sz[v];
maxsz[x]=max(maxsz[x],sz[x]);
}
maxsz[x]=max(maxsz[x],tot-sz[x]);//sz[x]很小时,另一边可能会比较大
G=maxsz[x]<maxsz[G]?x:G;
}
void getDis(int x,int fa)
{
dis[++dis[0]]=dep[x];//dis[0],用来计数,不用额外声明一个变量了
for(int i=head[x];~i;i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(v==fa||vis[v])continue;
dep[v]=dep[x]+w;
getDis(v,x);
}
}
int cal(int g,int pre)
{
dis[0]=0;
dep[g]=pre;
getDis(g,-1);
sort(dis+1,dis+1+dis[0]);
int ret=0;
for(int l=1,r=dis[0];l<r;)//类似二分
{
if(dis[l]+dis[r]<=k)ret+=r-l,l++;
else r--;
}
return ret;
}
void divide(int g)
{
ans+=cal(g,0);
vis[g]=true;
for(int i=head[g];~i;i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(vis[v])continue;
ans-=cal(v,w);//可能会有两个点在同一棵子树中,这些要排除
tot=maxsz[0]=sz[v];
G=0;
getG(v,-1);
divide(G);
}
}
void init()//初始化
{
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
tot=maxsz[0]=n;
ans=dis[0]=cnt=G=0;
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
if(n==0&&k==0)break;
init();
int x,y,w;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&w);
addedge(x,y,w);
addedge(y,x,w);
}
getG(1,-1);
divide(G);
printf("%d\n",ans);
}
return 0;
}
题意:给出一棵树,添加一条边,使得图中的桥的数量范围为[l,r]
思路:一条长度为n的链中有n个桥,环中是没有桥的。给长度为n的链的两端加边,则减少了n个桥。可以算出可以减少桥的数量范围为[n-1-r,n-1-l],记R=n-1-l,L=n-1-r。因为长度都是整数,所以可以算出长度小于等于R的点对数和长度小于等于L-1的点对数,最后答案就是前者减去后者。直接套用模板即可。