题目
题目背景
“你的那双眼睛,从没看透 校长 的任何幻术,它抛弃了所有孤独,与同学一起水群,让整个机房的人都摆烂,却唯独不能让他的弟弟颓废!”
O
U
Y
E
\sf OUYE
OUYE 的心从未像今天这般强烈地震颤起来!
“此后, O U Y E \sf OUYE OUYE 小队改名为卷爷小队,目标只有一个:摧毁学校!”
题目描述
定义一棵树
T
T
T 的四元划分
{
{
A
B
}
,
{
C
D
}
}
\{\{AB\},\{CD\}\}
{{AB},{CD}} 为,存在一条边
e
∈
T
e\in T
e∈T 使得
T
T
T 去掉
e
e
e 后形成两个连通块
S
1
,
S
2
S_1,S_2
S1,S2 满足
A
,
B
∈
S
1
∧
C
,
D
∈
S
2
A,B\in S_1\land C,D\in S_2
A,B∈S1∧C,D∈S2,其中
A
,
B
,
C
,
D
A,B,C,D
A,B,C,D 是
T
T
T 的四个不同的叶子。注意四元划分是无序的,即
{
{
D
C
}
,
{
A
B
}
}
\{\{DC\},\{AB\}\}
{{DC},{AB}} 被视作与
{
{
A
B
}
,
{
C
D
}
}
\{\{AB\},\{CD\}\}
{{AB},{CD}} 相同。
现给出两棵大小相同的树 T 1 , T 2 T_1,T_2 T1,T2,它们的非叶子节点的度数都是 3 3 3 。请求出它们的四元划分集合的对称差的大小。也即,有多少个四元划分只属于其中一棵树。
数据范围与提示
∣
T
1
∣
=
∣
T
2
∣
⩽
2
×
1
0
3
|T_1|=|T_2|\leqslant 2\times 10^3
∣T1∣=∣T2∣⩽2×103 。
思路
首先转化为求交集。然而我连直接算 T 1 T_1 T1 的四元划分数都不会…… 😢
只会把条件转化为 A → B A\to B A→B 和 C → D C\to D C→D 路径不交。然后就不会了。想到的暴力也都很困难……
事实上,首先可以发现 T T T 的四元划分数量就是 ( ∣ T ∣ 4 ) {|T|\choose 4} (4∣T∣),因为任取四个叶子,必定只有一种划分……真的可以发现吗…… 😿
然后,将 “存在” 条件转化为 “任意” 条件,配容斥系数。因为可以割掉的边,实际上就是 A → B A\to B A→B 和 C → D C\to D C→D 之间的路径,是一条链;用 ∣ E ∣ − ∣ V ∣ = 1 |E|-|V|=1 ∣E∣−∣V∣=1 来计算(这里的 V V V 不包含路径端点)。
所以算 T 1 T_1 T1 和 T 2 T_2 T2 四元划分交集,只需要在 T 1 T_1 T1 上先枚举删边 / / / 删点,得到的连通块进行染色;然后在 T 2 T_2 T2 上枚举删边 / / / 删点,问题相当于在 T 2 ′ T'_2 T2′ 的不同连通块中选不同颜色的点对。由于度数 ⩽ 3 \leqslant 3 ⩽3,只有 3 3 3 种颜色,树形 d p \tt dp dp 求出每种颜色的数量即可。
时间复杂度 O ( n 2 ) \mathcal O(n^2) O(n2),常数约有 2 ∼ 3 2\sim 3 2∼3 。
代码
注意树的读入方式较特殊;未提供原题链接,所以这也并无大碍。
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for loneliness)
#include <cctype> // DDG yydDOG & ZXY yydBUS !!!
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MAXN = 2003;
struct Edge{ int to, nxt; };
Edge e[MAXN<<2]; int head[MAXN<<1], cntEdge;
void addEdge(int a, int b){
e[cntEdge].to = b, e[cntEdge].nxt = head[a];
head[a] = cntEdge ++;
}
# define _go(i,x) for(int i=head[x]; ~i; i=e[i].nxt)
int col[MAXN], cnt[MAXN][3], fa[MAXN];
void dfs(int x, int pre){
memset(cnt[x],0,sizeof(cnt[x])); // 3 colors
if(~col[x]) cnt[x][col[x]] = 1;
_go(i,x) if((i^1) != pre){
fa[e[i].to] = i, dfs(e[i].to,i);
rep0(j,0,3) cnt[x][j] += cnt[e[i].to][j];
}
}
llong calc(const int &n){
dfs(1, fa[1] = -1); // from any node
static llong tmp[3], all; llong res = 0;
rep(i,2,n){ // for each node
memset(tmp,0,sizeof(tmp)), all = 0;
// delete a node: -1
_go(j,i) if((j^1) != fa[i]){
llong nxt = all;
for(int y=e[j].to,k=0; k!=3; ++k){
llong w = llong(cnt[y][k]-1)*cnt[y][k]>>1;
res -= (all-tmp[k])*w, tmp[k] += w, nxt += w;
}
all = nxt; // sum of cnt
}
if(i == 1) continue; // exclude root
llong sum = 0; rep0(k,0,3)
sum += llong(cnt[i][k]-1)*cnt[i][k]>>1;
rep0(k,0,3){ // from father
llong w = cnt[1][k]-cnt[i][k];
w = (w-1)*w>>1, res -= (all-tmp[k])*w;
// delete an edge to its father: +1
llong w2 = cnt[i][k]; w2 = w2*(w2-1)>>1;
res += w*(sum-w2); // not choose k
}
}
return res;
}
void setColor(const int &v, const int &leaf,
const int &n, int x, int pre){
if(x-n <= leaf) col[x-n] = v; // put on first tree
_go(i,x) if((i^1) != pre) setColor(v,leaf,n,e[i].to,i);
}
int main(){
int leaf = readint(), n = (leaf-1)<<1;
memset(head+1,-1,(n<<1)<<2);
rep(id,0,1) for(int i=1,a,b; i!=n; ++i){
a = readint()+id*n, b = readint();
addEdge(a,b+id*n), addEdge(b+id*n,a);
}
memset(col+1,-1,n<<2); llong ans = 0;
rep(i,n+1,n<<1){ // enumerate on second tree
for(int j=head[i],k=0; ~j; j=e[j].nxt)
setColor(k,leaf,n,e[j].to,j), ++ k;
col[i-n] = -1, ans -= calc(n); // delete a node: -1
_go(j,i) if(e[j].to < i){ // delete an edge: +1
setColor(0,leaf,n,e[j].to,j);
setColor(1,leaf,n,i,j^1), ans += calc(n);
}
}
llong nowv = (llong(leaf-1)*leaf*(leaf-2)*(leaf-3)>>3)/3;
printf("%lld\n",(nowv-ans)<<1);
return 0;
}