CF1408G Clusterization Counting(计数)(动态规划+Kruskal重构树)

洛谷题目传送门

题目描述

There are n computers in the company network. They are numbered from 1 to n .

For each pair of two computers 1≤i<j≤n you know the value ai,j​ : the difficulty of sending data between computers i and j . All values ai,j​ for i<j are different.

You want to separate all computers into kk sets A1​,A2​,…,Ak​ , such that the following conditions are satisfied:

  • for each computer 1≤i≤n there is exactly one set Aj​ , such that i∈Aj​ ;
  • for each two pairs of computers (s,f) and (x,y) (s=f , x=y ), such that s , f , x are from the same set but x and y are from different sets, as,f​<ax,y​ .

For each 1≤k≤n find the number of ways to divide computers into k groups, such that all required conditions are satisfied. These values can be large, so you need to find them by modulo 998244353 .

输入格式

The first line contains a single integer n ( 1≤n≤1500 ): the number of computers.

The i-th of the next n lines contains n integers ai,1​,ai,2​,…,ai,n​ ( 0≤ai,j​≤2n(n−1)​ ).

It is guaranteed that:

  • for all 1≤i≤n ai,i​=0 ;
  • for all 1≤i<j≤n ai,j​>0 ;
  • for all 1≤i<j≤n ai,j​=aj,i​ ;
  • all ai,j​ for i<j are different.

输出格式

Print n integers: the k -th of them should be equal to the number of possible ways to divide computers into k groups, such that all required conditions are satisfied, modulo 998244353 .

题意翻译

  • 给定 n 个点的带权无向完全图,点 i,j 之间的权值为 ai,j​,权值是一个 1∼2n(n−1)​ 的排列。
  • 计数把原图划分成 k 个组的方案数,满足:
    • 对于任意的 (s, f), (x, y),其中 s,f,x 同组,y 与 x 不同组 (s=f,x=y),as,f​<ax,y​,即(对于每个组)组间边大于组内边。
  • 输出一行 n 个数,对于 k∈[1,n] 求出答案,对 998244353 取模。
  • 1≤n≤1500

输入输出样例

输入 #1

4
0 3 4 6
3 0 2 1
4 2 0 5
6 1 5 0

输出 #1

1 0 1 1

输入 #2

7
0 1 18 15 19 12 21
1 0 16 13 17 20 14
18 16 0 2 7 10 9
15 13 2 0 6 8 11
19 17 7 6 0 4 5
12 20 10 8 4 0 3
21 14 9 11 5 3 0

输出 #2

1 1 2 3 4 3 1

说明/提示

Here are all possible ways to separate all computers into 4 groups in the second example:

  • {1,2},{3,4},{5},{6,7} ;
  • {1},{2},{3,4},{5,6,7} ;
  • {1,2},{3},{4},{5,6,7} .

题意简介

        给定一个带权无向完全图,要求把它划分成 k 个组,并对任意一个组来说,这个组与另一个组的任意一条连边都大于任何一条组之间的边,求方案数

解题思路

        由题意可知每个组都是一个完全图,因此一个组符合条件,当且仅当他所有的边加完之后(也就是成为一个完全图之后),组间边 > 组内边

        看到这种有边权限制的联通块,不难想到 Kruskal 重构树

        按从小到大把边排序之后建出 Kruskal 重构树,然后可以得出

        对于重构树上的任意两个点来说,只要这两个点的子树没有重合部分,那么这两个点的子树上的叶子结点所组成的组一定是符合条件的。

        因此,问题就变成了在重构树上选k个节点,他们的子树不相交,且他们能覆盖所有叶子结点的方案数。

        这就是一个DP问题了

首先遍历一遍重构树,把叶节点变成一个序列,然后把重构树上的节点所对应的区间的 L 和 R存起来,具体来说可以在每个 R 上建立一个vector,然后把所有以这点为R的区间的L存进去

        (认真理解一下)

设 f[i][j]表示在序列上的第 i 个点,已经分成 j 个区间的方案数

那么 枚举一下最后分的一个区间的右端点,则

f[i][j]=f[i][j]+f[L[k]][j-1](L[k],为右端点为i的重构树区间的左端点)

代码如下

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N =3e3+700;
const LL mod = 998244353;
struct node
{
	LL x,y,v;
}e[N*N];
LL a[1500][1500],t,L[N],R[N],n,m,fa[N],flag[N],siz[N],cntE[N];
LL f[1500][1500];
LL cnt=0,tot=0;
LL lson[N],rson[N];
vector<LL> Set[N];
void add(LL x,LL y,LL v)
{
	e[++t].x=x;
	e[t].y=y;
	e[t].v=v;
}
LL Find(LL x)
{
	if(fa[x]==x) return x;
	return fa[x]=Find(fa[x]);
}
bool cmp(node x,node y)
{
	return x.v<y.v;
}
void pre_work()
{
	for(LL i=1;i<=n;i++)
	{
		flag[i]=0;  //flag表示以i为根节点的子树是不是已经构成完全图
		fa[i]=i;
		siz[i]=1; 
		cntE[i]=0; //cntE是以i为根的子树的边数和
		Set[i].push_back(i); //叶节点的左端点也是自己
	} 
}
void calc(LL x)
{
	++cntE[x];
	if(cntE[x]==(siz[x]*(siz[x]-1)/2))
	flag[x]=1;
}
void Mark(LL x) //计算左右端点
{
	if(x<=n)
	{
		++tot;
		L[x]=tot;
		R[x]=tot;
		return;
	}
	L[x]=n+1;
	Mark(lson[x]);
	L[x]=min(L[x],L[lson[x]]);
	Mark(rson[x]);
	L[x]=min(L[x],L[lson[x]]);
	R[x]=tot;
	if(flag[x]) 
	{
		Set[R[x]].push_back(L[x]);
	}
}
inline LL read()
{
	LL ans=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9'){ans=(ans<<1)+(ans<<3)+(ch^48); ch=getchar();}
	return ans*f;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	n=read();
	m=n*n;
	for(LL i=1;i<=n;i++)
	{
		for(LL j=1;j<=n;j++)
		{
			LL v=read();
			a[i][j]=v;
			if(i<j)
			add(i,j,v);
		}
	}
	sort(e+1,e+t+1,cmp);
	cnt=n;
	pre_work();
	for(LL i=1;i<=t;i++)
	{
		LL x=e[i].x,y=e[i].y;
		LL fx=Find(x),fy=Find(y);
		if(fx!=fy)
		{
			++cnt;
			lson[cnt]=fx;
			rson[cnt]=fy;
			fa[fx]=cnt;
			fa[fy]=cnt;
			fa[cnt]=cnt;
			siz[cnt]=siz[fx]+siz[fy];
			cntE[cnt]=cntE[fx]+cntE[fy];
			calc(cnt);
		}
		else calc(fx);
	}
	Mark(cnt);
	f[0][0]=1;
	for(LL i=1;i<=n;i++)
	{
		for(LL k=1;k<=n;k++)
		{
			for(LL v=0;v<Set[i].size();v++)
			{				
				LL j=Set[i][v];
				f[i][k]=(f[i][k]+f[j-1][k-1])%mod;

			}
		}
	}
	for(LL i=1;i<=n;i++)
	printf("%lld ",f[n][i]);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值