题意:给出一棵有N个结点的树,问在这棵树中找3点使得这3个之间不存在简单路径的方法有多少种(3 <= N <= 10^5)?
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4705
——>>一大段时间误解了题意,WA和RE一片。。。
简单路径:指这3个点之间的欧拉通路(走完这3个点而不走回头路)。
在队友的启发下,用了反向求解:求出这棵树中找3个结点使之存在简单路径的方法有多少种,再用总取法数减去这个值。
设d[i]表示以i为根的子树中找3个结点使之存在简单路径的方法数,f[i]表示以i为根的子树中找2个结点使之存在祖先关系的方法数。
对于一个结点x,要求d[x]:
1、用这个结点,则
1)在其子树中找2个存在祖先关系的结点:d[x] += f[v[e]];(v[e]为x的子结点)
2)在其子树中找2个不存在祖先关系的结点(任意两个直接孩子的结点数的积):
long long sum = 0, xsum = 0;
for(int e = head[x]; e != -1; e = nxt[e]) if(v[e] != fa){
sum += cnt[v[e]];
xsum += cnt[v[e]] * cnt[v[e]];
}
d[x] += (sum * sum - xsum) / 2;
2、不用这个结点,则
1)在单个直接子树中找3个存在简单路径的点:d[x] += d[v[e]];
2)在1个直接子树中找2个存在祖先关系的点,再从另1个直接子树中取1点:d[x] += f[v[e]] * (cnt[x] - cnt[v[e]] - 1);
以C++提交~
#include <cstdio>
#include <cstring>
#pragma comment(linker, "/STACK:16777216")
using namespace std;
const int maxn = 100000 + 10;
int N, head[maxn], nxt[maxn<<1], u[maxn<<1], v[maxn<<1], ecnt;
long long d[maxn], cnt[maxn], f[maxn];
void init(){
ecnt = 0;
memset(head, -1, sizeof(head));
for(int i = 0; i <= N; i++) cnt[i] = 1;
}
void addEdge(int uu, int vv){
u[ecnt] = uu;
v[ecnt] = vv;
nxt[ecnt] = head[uu];
head[uu] = ecnt;
ecnt++;
}
void dfs(int x, int fa){ //求结点数和有2点存在祖先关系的种数f
f[x] = 0;
for(int e = head[x]; e != -1; e = nxt[e]) if(v[e] != fa){
dfs(v[e], x);
cnt[x] += cnt[v[e]];
f[x] += f[v[e]];
}
if(cnt[x] >= 2) f[x] += (cnt[x]-1);
}
void DFS(int x, int fa){ //求3点存在简单路的种数d
d[x] = 0;
for(int e = head[x]; e != -1; e = nxt[e]) if(v[e] != fa){
DFS(v[e], x);
d[x] += f[v[e]]; //用根,取2
d[x] += f[v[e]] * (cnt[x] - cnt[v[e]] - 1); //不用根,取2+1
d[x] += d[v[e]]; //不用根,取3
}
if(cnt[x] >= 3){
long long sum = 0, xsum = 0;
for(int e = head[x]; e != -1; e = nxt[e]) if(v[e] != fa){
sum += cnt[v[e]];
xsum += cnt[v[e]] * cnt[v[e]];
}
d[x] += (sum * sum - xsum) / 2; //用根,取1+1
}
}
int main()
{
int uu, vv;
while(scanf("%d", &N) == 1){
init();
for(int i = 0; i < N-1; i++){
scanf("%d%d", &uu, &vv);
addEdge(uu, vv);
addEdge(vv, uu);
}
dfs(1, -1);
DFS(1, -1);
printf("%I64d\n", cnt[1] * (cnt[1]-1) *(cnt[1]-2) / 6 - d[1]);
}
return 0;
}