Atcoder DP Contest T题解

Atcoder DP Contest T题解,第1张

一道需要一定分析的 dp 题目。


atcoder 题目传送门 & 洛谷题目传送门

更好的阅读体验


Description

给定 n n n 和一个长度为 n − 1 n-1 n1 的字符串(由 ‘<’ 和 ‘>’ 组成),求数列 1 , 2 , . . . , n 1,2,...,n 1,2,...,n 有多少种排列,使得相邻数之间的大小关系与字符串中大于号小于号相符合,答案对 1 0 9 + 7 10^9+7 109+7 取模。


  • 1 ≤ n ≤ 3000 1\le n\le 3000 1n3000

Solution1 analysis

先给出本题的一个重要结论:

对于这一序列的任何一个子序列,在其内部排列时我们只需要关心每个数的大小关系,而不是每个数具体的值。


举例:

序列6,9,11在进行内部排列时,我们可以将其视为1,2,3


所以:

我们只要考虑上一个数是什么,保证满足大小关系放数就行。


这样我们得到了一个显然的 dp 状态:

  • d p i , j dp_{i,j} dpi,j 表示考虑到第 i i i 个位置,前面一个数是前面排列中第 j j j 大(上面已经得出结论,第 j j j 大的数就视为 j j j 参与排列)的可能数。


转移:

分为两种情况:当前的数 > 上一个数 & 当前的数 < 上一个数。


  • 枚举上一个数在之前序列中大小排名 k k k,通过相对大小关系得到转移方程:

d p i , j = { ∑ k = 1 j − 1 d p i − 1 , k s i − 1 = ′ < ′ ∑ k = j i − 1 d p i − 1 , k s i − 1 = ′ > ′ dp_{i,j}=\begin{cases}\sum\limits_{k=1}^{j-1}dp_{i-1,k}&s_{i-1}='<'\sum\limits_{k=j}^{i-1}dp_{i-1,k}&s_{i-1}='>'\end{cases} dpi,j=k=1j1dpi1,kk=ji1dpi1,ksi1=<si1=>

这样转移是 O ( n ) O(n) O(n) 的,需要优化。


观察转移方程不难发现,可以对 d p i − 1 dp_{i-1} dpi1 记录前缀和,然后用前缀和进行转移。


这当然是可以的,但是有代码量更少更好写的做法:

s i − 1 = ′ < ′ s_{i-1}='<' si1=< 举例,我们比较 d p i , j − 1 dp_{i,j-1} dpi,j1 d p i , j dp_{i,j} dpi,j 发现可以用他们的差递推,从 d p i , j − 1 dp_{i,j-1} dpi,j1 d p i , j dp_{i,j} dpi,j 转移 。


s i − 1 = ′ > ′ s_{i-1}='>' si1=> 同理,从 d p i , j + 1 dp_{i,j+1} dpi,j+1 d p i , j dp_{i,j} dpi,j 转移。


  • 这样,我们就有了新的转移方程:

d p i , j = { d p i , j − 1 + d p i − 1 , j − 1 s i − 1 = ′ < ′ d p i , j + 1 + d p i − 1 , j s i − 1 = ′ > ′ dp_{i,j}=\begin{cases}dp_{i,j-1}+dp_{i-1,j-1}&s_{i-1}='<'\dp_{i,j+1}+dp_{i-1,j}&s_{i-1}='>'\end{cases} dpi,j={dpi,j1+dpi1,j1dpi,j+1+dpi1,jsi1=<si1=>

这样的转移是 O ( 1 ) O(1) O(1) 的。


时间复杂度: O ( n 2 ) O(n^2) O(n2)


空间复杂度: O ( n 2 ) O(n^2) O(n2)(如果用滚动数组可以降到 O ( n ) O(n) O(n),但这一题空间足够了)。


code
#include 
#define ll long long
#define pb push_back
#define pii pair
#define mp make_pair
#define F first
#define S second
using namespace std;
const ll MOD=1e9+7;//取模
int n;
string s;
ll dp[3005][3005],ans;
int main()
{
	cin>>n>>s; 
	dp[1][1]=1ll;//这里数组下标从1开始
	for(int i=2;i<=n;i++)
	{//按上面公式进行递推转移
		if (s[i-2]=='<')
			for(int j=1;j<=i;j++)
				dp[i][j]=(dp[i][j-1]+dp[i-1][j-1])%MOD;
		else
			for(int j=i-1;j>=1;j--)
				dp[i][j]=(dp[i][j+1]+dp[i-1][j])%MOD;
	}
	for(int j=1;j<=n;j++)
		ans=(ans+dp[n][j])%MOD;//答案为dp[n]总和
	cout<

Solution2

emmm,这个方法是在 atcoder 现场排行榜上看大神写的,思维难度比较大,代码也不是很好写。


大概是常人的思维想不到罢。


记录下序列中每一个递增序列的长度,通过排列组合做。


  • 状态: d p i , j dp_{i,j} dpi,j 表示考虑到第 i i i 个递增序列,上一个序列长度为 j j j 的可能数。


  • 转移:预处理阶乘及阶乘逆元,当前递增序列长度为 x x x


d p i + 1 , j + x ← d p i + 1 , j + x − d p i , j dp_{i+1,j+x}\leftarrow dp_{i+1,j+x}-dp_{i,j} dpi+1,j+xdpi+1,j+xdpi,j

d p i + 1 , x ← d p i + 1 , x + d p i , j j ! dp_{i+1,x}\leftarrow dp_{i+1,x}+\frac {dp_{i,j}}{j!} dpi+1,xdpi+1,x+j!dpi,j

最后答案为 ∑ j = 0 n ( d p n , j ÷ A n j ) × n ! \sum\limits_{j=0}^n(dp_{n,j}\div A_n^j)\times n! j=0n(dpn,j÷Anj)×n!


时间复杂度: O ( n 2 ) O(n^2) O(n2)


空间复杂度:滚动数组 O ( n ) O(n) O(n)


code
#include 
#include 
#include 
#include 
using namespace std;

const int mod = 1000000007;

int N; char S[3030];
vector<int> z;//记录每个递增序列长度
//inv为逆元,fact为阶乘,ifact为阶乘逆元
long long inv[3030] = {0,1}, fact[3030] = {1,1}, ifact[3030] = {1,1};
long long prv[3030],nxt[3030];//滚动的dp数组

int main()
{
	scanf ("%d",&N);
	scanf ("%s",S);
	int s = 1;
	for (int i=0;i<N-1;i++){//储存递增序列
		if (S[i] == '<') s++;
		else{
			z.push_back(s);
			s = 1;
		}
	}
	z.push_back(s);
	for (int i=2;i<=N;i++){
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
		fact[i] = fact[i-1] * i % mod;
		ifact[i] = ifact[i-1] * inv[i] % mod;
	}//递推预处理阶乘和逆元
	prv[z.back()] = 1;
	z.pop_back();
	reverse(z.begin(),z.end());
	for (int x : z){
		for (int j=0;j<=N;j++) nxt[j] = 0;
		for (int j=0;j<=N;j++) if (prv[j]){//转移
			nxt[j+x] = (nxt[j+x] + mod - prv[j]) % mod;
			nxt[x] = (nxt[x] + prv[j] * ifact[j]) % mod;
		}
		for (int j=0;j<=N;j++) prv[j] = nxt[j];
	}
	long long sum = 0;
	for (int j=0;j<=N;j++) sum = (sum + prv[j] * ifact[j]) % mod;//求答案
	printf ("%lld\n",sum*fact[N]%mod);
	return 0;
}

好吧其实对这个解法我也是似懂非懂。


这个解法代码量大了一些,其实是走了弯路。


还是建议大家用上面那个方法。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/569935.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-04-09
下一篇2022-04-09

发表评论

登录后才能评论

评论列表(0条)