poj1741 Tree解题报告
来源:互联网 发布:斯大林大战火星人2知乎 编辑:程序博客网 时间:2024/06/06 02:53
题意:给定一棵n个结点的树和边上的权值,树上两点之间的距离为经过边的权值之和,若两点之间距离<=k,则认为是一个合法点对,问有多少个合法点对
(据说是楼教主男人八题之一...做了很久,又参考了网上各种大牛们的代码,终于A掉了,自己真是太弱了。。。)
分析:
由于点有10000个,所以直接枚举两个点是TLE的,本题要用到树的分治(可以参看漆子超大牛09年的论文)。首先我们可以将所有的点对分为两种:
1.经过根节点
2.不经过根节点
对于经过根节点的点对我们可以这样算,先O(n)的计算出每个点到根的距离,存到dep[]数组中,若dep[i]+dep[j] <=k 就认为这是一个合法的点对,总数记为ans
但是,这样会把不经过根的点对算进去,所以,对于根的每个孩子结点,都要若如此处理算出ans1,ans2,ans3...... 然后ans-ans1-ans2-ans3-.........这样横跨根的合法点对就算出来了。
第二种情况怎么办呢?这就是分治了,删去跟结点会形成一个森林,我们可以把森林的每棵树都如上述的进行处理。
大概的思路就是如此,还要两个细节处理要注意:
1.在算dep[i]+dep[j] <=k时,单纯的两两枚举是会TLE的,所以可以先将dep排序 一个左指针L,一个右指针R,R指向的是最后一个满足条件dep[L]+dep[R] <=k的点,这样合法点对增加(R-L+1)个,由于L只增不减,R只减不增,可以在O(n)时间内算出.
2.树的分治,选择合适的根是关键,如果根选得不好,可能分的森林中一棵树结点特别的多,而其他的特别的少,复杂度容易将退化,每次我们要选择树的"重心"作为根来分治,什么是重心呢?简单来说是一棵树中,删去一个点x后,可以变成几棵树,使得这些树中最大的结点数尽量小,这样的一个x就是原树的重心,可以先刷poj1655 这道求重心的题
ayecsz1741Accepted768K188MSC++2624Bc++ 代码:
#include<cstdio>#include<algorithm>using namespace std;const int MAX = 10010;int list[MAX],next[2*MAX],p[MAX*2],c[MAX*2],a[MAX],f[MAX],s[MAX],flag[MAX],dep[MAX];int n,kkk,size,temproot,ans;void init(){ int x,y,z,i,tot=0; ans = 0; for (i = 1; i <= n; i++) { list[i] = 0; flag[i] = true; } for (i = 1; i < n; i++) { scanf("%d%d%d",&x,&y,&z); tot++; next[tot] = list[x]; list[x] = tot; p[tot] = y; c[tot] = z; tot++; next[tot] = list[y]; list[y] = tot; p[tot] = x; c[tot] = z; }}int getmax(int x,int y){ return x>y?x:y;}void findroot(int x,int pre){ int sum = 0,k; k = list[x]; s[x] = 1; f[x] = 0; while (k > 0) { if (p[k] != pre && flag[p[k]]) { findroot(p[k],x); f[x] = getmax(f[x],s[p[k]]); s[x] += s[p[k]]; } k = next[k]; } f[x] = getmax(f[x],size-s[x]); if (f[x]<f[temproot]) temproot = x;}void dfs(int x,int pre){ int k; k = list[x]; a[++a[0]] = dep[x]; while (k > 0) { if (p[k] != pre && flag[p[k]]) { dep[p[k]] = dep[x]+c[k]; dfs(p[k],x); } k = next[k]; }}int calc(int root,int dist){ int l,r,ret; a[0] = 0; dep[root] = dist; dfs(root,0); sort(a+1,a+a[0]+1); l = 1; r = a[0]; ret = 0; while (l < r) { if (a[l]+a[r] <= kkk) { ret = ret + r-l; l++; } else r--; } return ret;}void getsize(int x,int pre){ int k; k = list[x]; size++; while (k > 0) { if (p[k] != pre && flag[p[k]]) { getsize(p[k],x); } k = next[k]; }}void work(int root){ int k; ans = ans + calc(root,0); flag[root] = false; k = list[root]; while (k > 0) { if (flag[p[k]]) { ans = ans - calc(p[k],c[k]); size = 0; getsize(p[k],0); temproot = 0; f[0] = size; findroot(p[k],0); work(temproot); } k = next[k]; }}int main(){ while (scanf("%d%d",&n,&kkk)== 2) { if (n == 0) break; init(); temproot = 0; size = f[0] = n; findroot(1,0); work(temproot); printf("%d\n",ans); } return 0;}PS:
最近还看到一道codechef上的题:
给定一棵n个结点的树,树上两点之间的距离为经过的边数,若两点之间距离为质数,则认为是一个合法点对,问有多少个合法点对?
其实,这题的思路也是树的分治,只是将合法条件换了,我们可以同样处理,将dep[i]保存到根距离为i的结点有多少个
x^dep[1]+x^dep[2]+x^dep[3]+...+x^dep[n]
将其和自身做一遍乘法,就可以得到合法点对的数量,乘法要用FFT来实现
- poj1741 Tree解题报告
- [poj1741]tree 解题报告
- 解题报告:POJ1741 Tree 树上点分治(经典好题)
- poj1741 Tree
- poj1741 Tree
- POJ1741 Tree
- 【poj1741】【Tree】
- [POJ1741]Tree
- POJ1741 Tree
- poj1741 Tree
- poj1741 Tree
- POJ1741 Tree
- 【poj1741】 Tree
- poj1741 Tree
- POJ1741 Tree.
- poj1741 Tree
- 【poj1741】Tree
- poj1741 Tree
- 在做usaco时遇到的c++读入txt
- linux下解压.tar.gz的命令
- artdialog的使用之art.diglog.open 嵌套网页
- A12_ListView & ExpandablelistView
- Redhat系列使用ISO或者光盘制作yum本地安装源
- poj1741 Tree解题报告
- _beginthreadex、CreateThread、AfxBeginThread
- POJ——2429(数论之大整数分解)
- 检测数组最好的办法
- Trie 树
- automake的简单介绍
- win7(x64)安装android studio失败(jdk未找到)
- java环境变量配置
- UpdateData函数的使用