●观光(17.12.02多校联测题目)

题目:

e0b8be2d624e0993a5d561b81eab1eb7

题解:

DP,方案数统计,(神奇)。

题意不再赘述。
但不得不说,做这种dp真的好难受啊。

我们做如下考虑:
首先,起点只有出度,终点只有入度,其它点的出入度均为 1(这个结论好像没啥明显的用处哈)。

如果把 K位置作为终点,那么序列被分为了两段 :[1,K-1] 和 [k+1,N]
那么如何把左右两边"有机地"和终点结合起来呢?
假设,
在左边按照某种方式,用到所有点,连接出了 X 条终点方向指向 K(即指向右边)的链。
在右边按照某种方式,用到所有点,连接出了 Y 条终点方向指向 K(即指向左边)的链。

然后按照一条左边的链,一条右边的链的方式(即交替),最后连上终点 K的方案数为
= X! * Y!
另外,要想能够交替成功,X和 Y的差值不能超过 1
 
所以由上面的这个东西得到启发,我们只需要维护出
L[K-1][j] 表示 用到[1,K-1]的区间里的所有点,连接出 j条终点方向指向 K(即指向右边)的链 的方案数。
R[K+1][j] 表示 用到[K+1,N]的区间里的所有点,连接出 j条终点方向指向 K(即指向左边)的链 的方案数。
那么 就可以得出 K为终点时的方案数了。
ans[K]=sigma { L[K-1][j]*R[K+1][j+1] * j! * (j+1)!
                       +L[K-1][j+1]*R[K+1][j] * (j+1)! * j!
                        +2*L[K-1][j]*R[K+1][j] * j! *j! } (0<=j<=N)

接下来便是要 DP求出 L[i][j],R[i][j]数组。
以左边为例,先枚举 i(代表用到 [1,i]里的所有点),再枚举 j(表示连接出 j条链)
如果 S[i]=='<',那么有如下转移:
    L[i][j]+=(j+1)*j*L[i-1][j+1]     用'<'合并两条链(把'<'放在新形成的链中间)
   L[i][j]+=L[i][j]+j*L[i-1][j]        把'<'接在已有的链首
如果 S[i]=='>',那么。。。
    L[i][j]+=L[i-1][j-1]                 由'>'产生新的链尾(即产生一条新的链)
    L[i][j]+=j*L[i-1][j]                  把'>'接在已有的链尾

右边 R的处理大同小异,反着来就好了。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 1005
#define _ %mod
using namespace std;
const int mod=1000000007;
char S[MAXN];
int L[MAXN][MAXN],R[MAXN][MAXN],fac[MAXN];
int N;
int main()
{
	scanf("%d",&N); scanf("%s",S+1);
	if(N==1) {printf("1"); return 0;} fac[0]=1; 
	for(int i=1;i<=N;i++) fac[i]=1ll*i*fac[i-1]_;
	L[0][0]=1; R[N+1][0]=1;
	for(int i=1;i<=N;i++)//处理左侧 
		for(int j=1;j<=N;j++){
			if(S[i]=='<')	
				L[i][j]=(1ll*L[i][j]+1ll*(j+1)*j*L[i-1][j+1])_,//用'<'合并两条链(把'<'放在链中间)
				L[i][j]=(1ll*L[i][j]+1ll*j*L[i-1][j])_;//把'<'接在链首
			else
				L[i][j]=(1ll*L[i][j]+1ll*L[i-1][j-1])_,//由'>'产生新的链尾
				L[i][j]=(1ll*L[i][j]+1ll*j*L[i-1][j])_;//把'>'接在链尾 
		}
	for(int i=N;i>=1;i--)//处理右侧 
		for(int j=1;j<=N;j++){
			if(S[i]=='>')
				R[i][j]=(1ll*R[i][j]+1ll*(j+1)*j*R[i+1][j+1])_,//用'>'合并两条链(把'<'放在链中间)
				R[i][j]=(1ll*R[i][j]+1ll*j*R[i+1][j])_;//把'>'接在链首
			else
				R[i][j]=(1ll*R[i][j]+1ll*R[i+1][j-1])_,//由'<'产生新的链尾
				R[i][j]=(1ll*R[i][j]+1ll*j*R[i+1][j])_;//把'<'接在链尾
		}
	for(int k=1,ans;k<=N;k++){//计算以 K为终点时的方案数 
		ans=0;
		for(int j=0;j<=N;j++)
			ans=(1ll*ans+1ll*L[k-1][j]_*fac[j]_*R[k+1][j+1]_*fac[j+1]_)_,
			ans=(1ll*ans+1ll*L[k-1][j+1]_*fac[j+1]_*R[k+1][j]_*fac[j]_)_,
			ans=(1ll*ans+2ll*L[k-1][j]_*fac[j]_*R[k+1][j]_*fac[j]_)_;
		printf("%d
",ans);
	}
	return 0;
}

原文地址:https://www.cnblogs.com/zj75211/p/7954465.html