[ACNOI2022]我根本不会计数

98 篇文章 0 订阅
23 篇文章 0 订阅

题目

题目背景
“你的那双眼睛,从没看透 校长 的任何幻术,它抛弃了所有孤独,与同学一起水群,让整个机房的人都摆烂,却唯独不能让他的弟弟颓废!” 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 eT 使得 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,BS1C,DS2,其中 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=T22×103

思路

首先转化为求交集。然而我连直接算 T 1 T_1 T1 的四元划分数都不会…… 😢

只会把条件转化为 A → B A\to B AB C → D C\to D CD 路径不交。然后就不会了。想到的暴力也都很困难……

事实上,首先可以发现 T T T 的四元划分数量就是 ( ∣ T ∣ 4 ) {|T|\choose 4} (4T),因为任取四个叶子,必定只有一种划分……真的可以发现吗…… 😿

然后,将 “存在” 条件转化为 “任意” 条件,配容斥系数。因为可以割掉的边,实际上就是 A → B A\to B AB C → D C\to D CD 之间的路径,是一条链;用 ∣ E ∣ − ∣ V ∣ = 1 |E|-|V|=1 EV=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 23

代码

注意树的读入方式较特殊;未提供原题链接,所以这也并无大碍。

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值