Atcoder beginner contest(ABC) 133 E题解

Description

给定一个 n n n个节点的树。现在你拥有 k k k种颜色,你要用这些颜色给树上的每个节点染色,使得任何两个距离不大于 2 2 2不同节点所被染的颜色不同。

由于答案可能过大,请将其对 1 0 9 + 7 10^9+7 109+7取模。

Solution

不管各位大佬有多强一眼看穿这题,但是本蒟蒻没有这个本事。于是,我从宏观想,一直向下剖析,最终得到了正解。

于是,这篇题解将会变得格外详细

Part 1

这是经典的染色问题。

a i a_i ai表示第 i i i个节点所能被染的颜色的数量。因为这是一棵树,所以答案就是 ∏ i = 1 n a i ∏_{i=1}^n a_i i=1nai

于是,难点转到了如何正确地得到所有的 a i a_i ai

Part 2

容易发现,一个父节点的任何两个子节点的距离均为2。

为了方便叙述,这里把"父节点"设为 f f f号节点。同时,设我们在 f f f号节点的孩子中,我们第 k k k个填了 s k s_k sk号节点。

在这里插入图片描述

那么显然有 a s 1 = a s 2 + 1 = a s 3 + 2 a_{s_1}=a_{s_2}+1=a_{s_3}+2 as1=as2+1=as3+2

原因如下: 填了 s 1 s_1 s1号节点之后,它使 s 2 s_2 s2 s 3 s_3 s3所能被填的颜色均少了一种;填了 s 2 s_2 s2号节点之后,它使 s 3 s_3 s3能被填的颜色又少了一种。

因此,总有 a s m = a s m + 1 + 1 a_{s_m}=a_{s_{m+1}}+1 asm=asm+1+1。所以只要我们得到 a s 1 a_{s_1} as1的值,那么接下来的 a s 2 , a s 3 … … a_{s_2}, a_{s_3}…… as2,as3的值就全得到啦。

于是,最后的难点转到了求 a s 1 a_{s_1} as1的值。

Part 3

由于 a s 1 a_{s_1} as1是在 f f f号节点的孩子中第一个被填的,所以它不会被其他的 f f f号节点的孩子所影响

但是,它仍然会被与它距离不超过2的祖先所影响。即,如果它有父亲,那么它能填的颜色就少了一种;如果它有爷爷,那么它能填的颜色就又少了一种,注意其父亲的颜色与其爷爷的颜色一定不同。

综上所述,

①当 d e p t h s 1 = 1 depth_{s_1}=1 depths1=1时, a s 1 = k a_{s_1}=k as1=k ( k k k为所能够填的颜色的数量);

②当 d e p t h s 1 = 2 depth_{s_1}=2 depths1=2时,由于它没有爷爷,那么 a s 1 = k − 1 a_{s_1}=k-1 as1=k1

③当 d e p t h s 1 = 3 depth_{s_1}=3 depths1=3时,由于既有爷爷也有父亲,那么 a s 1 = k − 2 a_{s_1}=k-2 as1=k2

得到了 a s 1 a_{s_1} as1的值之后呢,我们使用Part 2的做法就能得到所有 a i a_i ai的值。最后求出 ∏ i = 1 n a i ∏_{i=1}^n a_i i=1nai就可以啦。

注意取模。

评定

①难度: 不会评

②算法: 数学,数论+树形结构+搜索+深度优先搜索,dfs

③时间复杂度: O ( n ) O(n) O(n)

④评价: 这是一道染色问题的入门题,树形结构的普通题,也是一道让我想了 20 20 20分钟的E题(我太弱了)。欢迎各位大佬轻喷哦!

代码详解

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;

那熟悉的几行字,不再解释。

int n,k,cnt=0,ans=1;
int head[200005],depth[100005],a[100005];

struct node
{
	int u,v;
}tmp[100005];

struct edge
{
	int next;
	int to;
}e[200005];

强迫症地存下了每条边,然后用链式前向星存图。

注意这里要初始化 a n s = 1 ans=1 ans=1,因为要做乘法。

inline void add_edge(int u,int v)
{
	cnt++;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}

加边函数~

inline void dfs(int now,int fath)
{
	depth[now]=depth[fath]+1;
	for (int i=head[now];i;i=e[i].next)
	{
		if (e[i].to!=fath)  dfs(e[i].to,now);
	}
}

求出每个点的深度,以便分类讨论。

inline void dfs2(int now,int fath)
{
	if (depth[now]==2)  a[now]--;
	else if (depth[now]>=3)  a[now]-=2;
	
	int flag=0,last;
	for (int i=head[now];i;i=e[i].next)
	{
		if (e[i].to!=fath)
		{
			if (flag==0)
			{
				last=a[e[i].to];
				flag=1;
				continue;
			}
			a[e[i].to]=last-1;
			last=a[e[i].to];
		}
	}
	for (int i=head[now];i;i=e[i].next)
	{
		if (e[i].to!=fath)  dfs2(e[i].to,now);
	}
}

求出所有 a i a_i ai的值。

signed main()
{
	cin>>n>>k;
	for (int i=1;i<n;i++)  cin>>tmp[i].u>>tmp[i].v;
	for (int i=1;i<=n;i++)  a[i]=k;
	for (int i=n;i>=1;i--)
	{
		add_edge(tmp[i].u,tmp[i].v);
		add_edge(tmp[i].v,tmp[i].u);
	}
	dfs(1,0);
	dfs2(1,0);
	
	for (int i=1;i<=n;i++)
	{
		int gx=max(a[i],0ll);
		ans=(ans*gx)%mod;
	}
	cout<<ans<<endl;
	
	return 0;
}

注意:

①加两次边,否则WA;

②一边乘一边取模,否则溢出;

③个人认为可能会出现节点没有颜色可涂,这样该节点能涂的颜色就是0或负数,所以说 m a x ( a i , 0 ) max(a_i,0) max(ai,0)格外重要。不要以为不会出现这样的情况——当该树只有两层且 n n n小于 k k k。这时应该输出0而不是一个负数。

主函数总归还是亲切的,相信大家能理解吧。

最后放上高清无码的代码~

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;

int n,k,cnt=0,ans=1;
int head[200005],depth[100005],a[100005];

struct node
{
	int u,v;
}tmp[100005];

struct edge
{
	int next;
	int to;
}e[200005];

inline void add_edge(int u,int v)
{
	cnt++;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}

inline void dfs(int now,int fath)
{
	depth[now]=depth[fath]+1;
	for (int i=head[now];i;i=e[i].next)
	{
		if (e[i].to!=fath)  dfs(e[i].to,now);
	}
}

inline void dfs2(int now,int fath)
{
	if (depth[now]==2)  a[now]--;
	else if (depth[now]>=3)  a[now]-=2;
	
	int flag=0,last;
	for (int i=head[now];i;i=e[i].next)
	{
		if (e[i].to!=fath)
		{
			if (flag==0)
			{
				last=a[e[i].to];
				flag=1;
				continue;
			}
			a[e[i].to]=last-1;
			last=a[e[i].to];
		}
	}
	for (int i=head[now];i;i=e[i].next)
	{
		if (e[i].to!=fath)  dfs2(e[i].to,now);
	}
}

signed main()
{
	cin>>n>>k;
	for (int i=1;i<n;i++)  cin>>tmp[i].u>>tmp[i].v;
	for (int i=1;i<=n;i++)  a[i]=k;
	for (int i=n;i>=1;i--)
	{
		add_edge(tmp[i].u,tmp[i].v);
		add_edge(tmp[i].v,tmp[i].u);
	}
	dfs(1,0);
	dfs2(1,0);
	
	for (int i=1;i<=n;i++)
	{
		int gx=max(a[i],0ll);
		ans=(ans*gx)%mod;
	}
	cout<<ans<<endl;
	
	return 0;
}

撒花✿✿ヽ(°▽°)ノ✿撒花

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值