Processing math: 100%

TopCoder SRM 489 Div1 Lev3:AppleTree

挺优秀的一道题,想出做法时有些惊艳。

题意:

数轴上有D个连续整数刻度,有N棵树要种在这些刻度上,其中第i棵与两旁(如果有的话)相邻的树至少要相距Ri,问方法数。

1N,Ri40

思路:

首先,如果确定了种树的顺序,就确定了相邻树的最小间距。把D减掉最小间距之和,所得的就是“冗余刻度”的数量。

把这个数量分配给N+1段间隙,用插板法可以求出方法数。

所以问题在于,对于每一个L,求出1到N的排列P的数量,满足:

N1i=1max(RPi,RPi+1)=L

注意到,对于使Ri最大的i,它的两侧种的是什么树,不影响这两段间隙的最小长度。

根据套路,这个时候我们应该在i的位置把排列割开并分别处理。

对于一个1到N的子集的长度为l的排列P,定义其代价为:l1i=1max(RPi,RPi+1)

我们先把R数组从小到大排序,接着DP:dp[i][j][k]表示,1到ii个数,组成了j个不相交排列,排列的代价总和为k的方法数。

转移时考虑第i个数在一个排列的两端还是中间,删除之,加上第i个数两侧的空隙长度并转移即可。把树排序的意义在于,排序后第i棵树一定是[1,i]R值最大的那个。

注意到i,j40k1600,故复杂度没有问题。

等等,正确性有问题……

因为长度为1的排列只有一个端点,而在先前的状态中很难区分长度为1和大于1的排列,故无法求出转移时乘的系数。

所以不妨直接考虑从长度为1的那些排列入手,发现……可以用它们合并成更长的排列?

于是想到可以DP状态不变,转移做如下修改:

首先,初始状态dp[0][0][0]=1

其次,允许通过以下方式转移:

1、把i+1单独组成一个长为1的排列

2、任选两个排列,把i放在中间将其连接

3、任选一个排列,把i吸附在其端点旁边

这样把i+1添加进当前的排列集合之后,就可以转移到i=i+1的情况。

对于每一个L,求出1到N的排列P的数量,满足N1i=1max(RPi,RPi+1)=L

而对于每一个L,要求的值就是dp[N][1][L]。Bingo!

代码:

// BEGIN CUT HERE

// END CUT HERE
#line 5 "AppleTrees.cpp"
#include <bits/stdc++.h>
using namespace std;
#define iinf 2000000000
#define linf 1000000000000000000LL
#define ulinf 10000000000000000000ull
#define MOD 1000000007LL
#define lson(v) ((v)<<1)
#define rson(v) (((v)<<1)^1)
#define mpr make_pair
typedef long long LL;
typedef unsigned long long ULL;
typedef unsigned long UL;
typedef unsigned short US;
typedef pair < int , int > pii;
clock_t __stt;
inline void TStart(){__stt=clock();}
inline void TReport(){printf("\nTaken Time : %.3lf sec\n",(double)(clock()-__stt)/CLOCKS_PER_SEC);}
template < typename T > T MIN(T a,T b){return a<b?a:b;}
template < typename T > T MAX(T a,T b){return a>b?a:b;}
template < typename T > T ABS(T a){return a>0?a:(-a);}
template < typename T > void UMIN(T &a,T b){if(b<a) a=b;}
template < typename T > void UMAX(T &a,T b){if(b>a) a=b;}
int n,dp[42][42][1655],fac[100005];
int inv(int val){
	int ret=1,tms=MOD-2;
	while(tms){
		if(tms&1) ret=((LL)ret*(LL)val)%MOD;
		val=((LL)val*(LL)val)%MOD;
		tms>>=1;
	}
	return ret;
}
int C(int n,int m){
	if(n<m) return 0;
	if(!n) return 1;
	int u=fac[n],d=((LL)fac[m]*(LL)fac[n-m])%MOD;
	return ((LL)u*(LL)inv(d))%MOD;
}
class AppleTrees {
	public:
	int theCount(int D, vector <int> r){
		sort(r.begin(),r.end());
		n=(int)r.size();
		int i,j,k,res=0;
		fac[0]=1;
		for(i=1;i<=D;++i){
			fac[i]=((LL)fac[i-1]*(LL)i)%MOD; 
		}
		dp[0][0][0]=1;
		for(i=0;i<n;++i){
			for(j=0;j<=n;++j){
				for(k=0;k<=1620 && k<=D;++k){
					if(!dp[i][j][k]) continue;
					dp[i+1][j+1][k]+=dp[i][j][k];
					dp[i+1][j+1][k]%=MOD;
					dp[i+1][j][k+r[i]]+=((LL)dp[i][j][k]*2LL*(LL)j)%MOD;
					dp[i+1][j][k+r[i]]%=MOD;
					if(j>1){
						dp[i+1][j-1][k+2*r[i]]+=((LL)dp[i][j][k]*(LL)j*(LL)(j-1))%MOD;
						dp[i+1][j-1][k+2*r[i]]%=MOD;
					}
				}
			}
		}
		for(i=1;i<=1620 && i<=D;++i){
			res+=((LL)dp[n][1][i-1]*(LL)C(D-i+n,n))%MOD;
			res%=MOD;
		}
		return res;
	}
};
posted @   TianyiQ  阅读(793)  评论(1)    收藏  举报
编辑推荐:
· 记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历
· 糊涂啊!这个需求居然没想到用时间轮来解决
· 浅谈为什么我讨厌分布式事务
· 在 .NET 中使用内存映射文件构建高性能的进程间通信队列
· 一个 java 空指针异常的解决过程
阅读排行:
· 干翻 Typora!MilkUp:完全免费的桌面端 Markdown 编辑器!
· 那些年我们一起追过的Java技术,现在真的别再追了!
· 记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历
· 从WebApi迁移到Minimal API?有了这个神器,小白也能10分钟搞定!
· 抛开官方库,手撸一个轻量级 MCP 服务端
点击右上角即可分享
微信分享提示