SPOJ3734 PERIODNI(笛卡尔树+组合+DP)

SPOJ3734 PERIODNI

洛谷题目传送门
给定一个N列的表格,每列的高度各不相同,但底部对齐,然后向表格中填入K个相同的数,填写时要求不能有两个数在同一列,或同一行,下图中b是错误的填写,a是正确的填写,因为两个a虽然在同一行,但它们中间的表格断开。

输出所有填写方案数对1 000 000 007的余数。

解题思路

这道题是加在笛卡尔树作业里的,那就是和笛卡尔树有关的
先考虑一种简化情况
如果是正方形,填n个数,那么第一行有n种,第二行有(n-1)种
一共有 n ! n! n!种填法
如果是矩形,长n,宽m,填k个
在这里插入图片描述
那么可以想成选k行,选k列,然后转化为情况一
方案数 C n k C m k k ! C_n^kC_m^kk! CnkCmkk!
现在考虑这个题
建出笛卡尔树,设 F [ i ] [ j ] F[i][j] F[i][j]表示在 i i i的子树中放置 j j j个数的方案数(不包含自己)
f [ j ] f[j] f[j]表示在 i i i的子树中放 j j j个数的方案数(不包含自己)
f [ i ] = ∑ j = 0 s i z [ l s o n [ x ] ] F [ l s o n [ x ] ] [ j ] × F [ r s o n [ x ] ] [ i − j ] f[i]=\sum_{j=0}^{siz[lson[x]]}F[lson[x]][j]\times F[rson[x]][i-j] f[i]=j=0siz[lson[x]]F[lson[x]][j]×F[rson[x]][ij]
进而表示出 F [ i ] [ j ] F[i][j] F[i][j]
F [ i ] [ j ] = ∑ k = 0 H f [ j − k ] × C H k × C W − ( j − k ) k × k ! F[i][j]=\sum_{k=0}^H f[j-k]\times C_H^k\times C_{W-(j-k)}^{k}\times k! F[i][j]=k=0Hf[jk]×CHk×CW(jk)k×k!
j j j是放置的总数, k k k是在当前矩形放的数字
f [ j − k ] f[j-k] f[jk]表示在子树中(不含自己)放 j − k j-k jk个数字
然后再下面的矩形中,高度是 H H H,宽度原本是 W W W,在子树中占据了 j − k j-k jk
所以剩下了 W − ( j − k ) W-(j-k) W(jk)列,再转化为上面的矩形方案数

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod =1e9+7;
const int N = 720;
const int V = 1e6+7;
LL f[N][N];
int lson[N],rson[N];
int W[N];
int n,a[N];
stack<int> q;
int rot;
void build()
{
	for(int i=1;i<=n;i++)
	{
		while(!q.empty()&&a[q.top()]>a[i]) 
		{
			lson[i]=q.top();
			q.pop();
		}
		if(!q.empty()) rson[q.top()]=i;
		q.push(i);
 	}
 	while(!q.empty())
 	{
 		rot=q.top();
 		q.pop();
	 }
}
LL add(LL a,LL b)
{
	return (a+b)%mod;
}
LL mul(LL a,LL b)
{
	return 1ll*a*b%mod;
} 
LL Pow(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res=mul(res,a);
		a=mul(a,a);
		b>>=1;
	}
	return res;
}
LL Jc[V];
LL Ny[V];
void pre(int n)
{
	Jc[0]=1;
	for(int i=1;i<=n+1;i++)
	Jc[i]=mul(Jc[i-1],i);
	Ny[n+1]=Pow(Jc[n+1],mod-2)%mod;
	for(int i=n;i>=1;i--)
	Ny[i]=mul(Ny[i+1],i+1);
	Ny[0]=1;
}
LL C(int n,int m)
{
	if(n<m) return 0;
	return mul(Jc[n],mul(Ny[m],Ny[n-m]));
} 
LL dp[N];
int k;
void trans(int x,int h)
{
	int H=a[x]-h;
	W[x]=1;
	if(lson[x]) trans(lson[x],a[x]);
	if(rson[x]) trans(rson[x],a[x]);
	W[x]=W[lson[x]]+W[rson[x]]+1;
	memset(dp,0,sizeof(dp));
	for(int i=0;i<=W[lson[x]];i++)
	{
		for(int j=0;j<=W[rson[x]];j++)
		{
			if(i+j>k) break;
			dp[i+j]=add(dp[i+j],mul(f[lson[x]][i],f[rson[x]][j]));
		}
	}
	for(int i=0;i<=W[x];i++)
	{
		for(int j=0;j<=H&&j<=i;j++)
		{
			if(i>k) break;
			f[x][i]=add(f[x][i],mul(Jc[j],mul(dp[i-j],mul(C(H,j),C(W[x]-(i-j),j)))));
		}
	}
}
int main()
{
	freopen("periodni.in","r",stdin);
	freopen("periodni.out","w",stdout);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	pre(520);
	build();
	f[0][0]=1;
	trans(rot,0);
	cout<<f[rot][k];
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
洛谷的SPOJ需要注册一个SPOJ账号并进行绑定才能进行交题。您可以按照以下步骤进行注册: 1. 打开洛谷网站(https://www.luogu.com.cn/)并登录您的洛谷账号。 2. 在网站顶部导航栏中找到“题库”选项,将鼠标悬停在上面,然后选择“SPOJ”。 3. 在SPOJ页面上,您会看到一个提示,要求您注册SPOJ账号并进行绑定。点击提示中的链接,将会跳转到SPOJ注册页面。 4. 在SPOJ注册页面上,按照要求填写您的用户名、密码和邮箱等信息,并完成注册。 5. 注册完成后,返回洛谷网站,再次进入SPOJ页面。您会看到一个输入框,要求您输入刚刚注册的SPOJ用户名。输入用户名后,点击“绑定”按钮即可完成绑定。 现在您已经成功注册并绑定了SPOJ账号,可以开始在洛谷的SPOJ题库上刷题了。祝您顺利完成编程练习!\[1\]\[2\] #### 引用[.reference_title] - *1* *3* [(洛谷入门系列,适合洛谷新用户)洛谷功能全解](https://blog.csdn.net/rrc12345/article/details/122500057)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [luogu p7492 序列](https://blog.csdn.net/zhu_yin233/article/details/122051384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值