Poj 1741 Tree
据说是传说中的男人八题之一,其实是点分治的练手题......不过第一次写调起来还是比较恶心的。
话说QZC的论文我是完全没有看懂,上网找了一份代码看看才有点思路。
树分治算法大致要分成几个过程:
预处理->分治{分割,计算答案,递归处理子树}就是这样了。
但实际写起来却要写各种dfs和bfs,但这种东西点双边双写多了就不会出什么错了,我在find计算答案那块调了好久,后来发现dis的下标应该是队列的标记,而不是某个点。
我写的点分治,主过程是solve,递归地solve下去:每次先choose一个重心,然后计算这颗树里所有答案(就是计算出每个点到根的距离然后判断即可),然后减去路径不经过根的答案(就是在儿子里find),然后把根断掉,以后就再也不会用到这个根了,然后递归地处理根的所有子树就行了。
Tree
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #define inf 2147483647 7 #define maxn 20000 8 using namespace std; 9 10 struct et 11 { 12 int s,t,val,next; 13 }e[maxn]; 14 int fir[maxn],fa[maxn],dis[maxn],size[maxn],q[maxn]; 15 bool ex[maxn]; 16 int n,m,tot,num; 17 long long ans; 18 19 void dfs(int now)//dfs初始化树的信息 20 { 21 size[now]=1; 22 for (int j=fir[now];j;j=e[j].next) 23 { 24 int k=e[j].t; 25 if (k!=fa[now]) 26 { 27 fa[k]=now; 28 dfs(k); 29 size[now]+=size[k]; 30 } 31 } 32 } 33 34 void choose(int &rot)//找到当前子树的重心并以之为根 35 { 36 int now,mx=0; 37 for (int j=fir[rot];j;j=e[j].next) 38 { 39 int k=e[j].t; 40 if (ex[k]&&size[k]>mx&&k!=rot)//ex防止走出当前子树,下同 41 { 42 mx=size[k];//记录rot的儿子里size最大的 43 now=k; 44 } 45 } 46 if (mx>num/2)//找到重心,而且它的size一定比rot大,于是修改根 47 { 48 size[rot]-=size[now]; 49 fa[rot]=now; fa[now]=0; 50 size[now]=num; 51 rot=now; 52 choose(rot); 53 } 54 } 55 56 int find(int rot,int dist)//计算以rot为根的答案 57 { 58 int head=0,tail=1; 59 q[1]=rot; dis[1]=dist; 60 while (head<tail)//广搜计算距离 61 { 62 int now=q[++head]; 63 for (int j=fir[now];j;j=e[j].next) 64 { 65 int k=e[j].t; 66 if (ex[k]&&fa[now]!=k)//判断不是父向边 67 { 68 q[++tail]=k; 69 dis[tail]=dis[head]+e[j].val; 70 } 71 } 72 } 73 sort(dis+1,dis+tail+1);//log(n)排序 74 int sum=0; 75 int j=tail; 76 for (int i=1;i<=tail;i++)//O(n)累加答案 77 { 78 while (dis[i]+dis[j]>m&&j>i) j--; 79 if (j>i) sum+=(j-i); 80 } 81 return sum; 82 } 83 84 void solve(int rot) 85 { 86 if (num==1) return; 87 choose(rot); 88 ans+=find(rot,0); 89 for (int j=fir[rot];j;j=e[j].next)//减去所有不过根的答案 90 { 91 int k=e[j].t; 92 if (ex[k]) ans-=find(k,e[j].val); 93 } 94 ex[rot]=0;//切掉rot,把树分成若干子树 95 for (int j=fir[rot];j;j=e[j].next)//分治子树 96 { 97 int k=e[j].t; 98 if (ex[k]) 99 { 100 num=size[k];//num在这层已经没用了,所以可以开全局 101 solve(k); 102 } 103 } 104 } 105 106 void add(int x,int y,int z) 107 { 108 e[++tot].s=x; e[tot].t=y; e[tot].val=z; e[tot].next=fir[x]; fir[x]=tot; 109 e[++tot].s=y; e[tot].t=x; e[tot].val=z; e[tot].next=fir[y]; fir[y]=tot; 110 } 111 112 int main() 113 { 114 scanf("%d%d",&n,&m); 115 while (n+m) 116 { 117 ans=0;tot=0; 118 memset(ex,1,sizeof(ex)); 119 memset(fir,0,sizeof(fir)); 120 memset(dis,0,sizeof(dis)); 121 int x,y,z; 122 for (int i=1;i<n;i++) 123 { 124 scanf("%d%d%d",&x,&y,&z); 125 add(x,y,z); 126 } 127 int rot=1; 128 dfs(rot); 129 num=n; 130 solve(rot); 131 printf("%I64d\n",ans); 132 scanf("%d%d",&n,&m); 133 } 134 }
最近突然发现dev的单步还是挺好用的,以后不用guide这个恶心的东西了。
AC without art, no better than WA !