Atcoder AGC 043 D题解

一道思维含量很大,代码量很小的紫题。赛时AC人数较少,本蒟蒻也并没有做出来……

写个题解总结一下。

Description

给定一种生成长度为 3 n 3n 3n的排列的方法:先随意生成一个排列,然后把这个排列划分为 n n n块,每块 3 3 3个数, 1 1 1个指针。刚开始每个指针指向的是该段的开头,每次找到所有指针指向的数中最小的那个,把这个数放到这个序列的末尾并将这个指针向右移动一位。如果一个指针出了它原先所在的块,就删除该指针。当所有指针都不存在后,生成完成。

求存在多少个长度为 3 n 3n 3n的排列是可以按照上述方式生成的。注意将答案对 m o d mod mod取模。

Solution

众所周知, A G C AGC AGC D D D题往上的神仙题往往是先挖性质,然后用各种技巧(如莫反, FFT, FWT)推式子,最后用数据结构优化。

我们看完本题的第一反应是——我们先挖性质。即,我们要找到一个可生成的排列的充分必要条件。

首先,我们自己先对于几个排列模拟一下。可以发现,我们最终生成的序列中,可能存在递减的段。即,有一个小的数被卡在了大的数的后面,只有这个大的数出来,它才能出来。

但是这个大的数为什么会出来?是因为它是在所有指针指向的数中最小的。那么必然,紧接着出来的那个小的数只能紧接着它后面出来。大概证明一下,假设这个大的数为 x x x,小的数为 y y y,当 x x x成为最小的出队后,指针指向了 y y y,此时 y y y一定成为了指针指向的数中最小的 ( y < x ) (y<x) (y<x),那么 y y y就出队了——即 x x x y y y必然在同一个块中。

所以,对于生成的序列中每一个递减的段的长度都不能超过 3 3 3,因为初始所有块的长度只有 3 3 3。这是一个必要的条件。

但是充分吗?你再写几个序列,就发现自己被 H a c k Hack Hack了。千万不要像填数游戏那样,挖一个性质就走人,最终发现这个条件不是充分的。

另外一个性质是什么呢?我们继续模拟,可以发现: 假设长度为 2 2 2的递减段有 x x x个,长度为 1 1 1的递减段有 y y y个,那么肯定有 x ≤ y x≤y xy 3 ∣ ( y − x ) 3|(y-x) 3(yx)

为什么呢?因为,对于 x x x个长度为 2 2 2的递减段,它们肯定两两出现在同一个块中。而一个块中的另一个数,就是 y y y个长度为 1 1 1的递减段之一。 y y y是绝对不能少的,否则就拼不完;如果 y y y多的话,可以把 y y y个长度为 1 1 1的段摆在别的块中。

3 ∣ ( y − x ) 3|(y-x) 3(yx)又是为什么呢?因为,我们对于多出来的 y − x y-x yx个长度为 1 1 1的段,我们必须把这些长度为 1 1 1的段摆完,而每三个这些段必须出现在同一个块中,否则就另外需要别的长度为 2 2 2的段或别的长度为 1 1 1的段去填充——而长度为 2 2 2 x x x个段已经摆完了,长度为 1 1 1的段也摆完了;那么,这种序列就无法生成了。

把这两个性质结合起来,就得到了可生成序列的两个充分必要条件:
①任意递减段的长度不超过 3 3 3
x ≤ y x≤y xy 3 ∣ ( y − x ) 3|(y-x) 3(yx),这里 x x x为长度为 2 2 2的段的数量, y y y为长度为 1 1 1的段的数量。

现在这道题成为了一道纯粹的计数题。


根据CSP-S 2019 D2T1(Emiya今天的饭)的经验,我们可以采用差值 d p dp dp,并压缩状态的维度。

状态设计: d p i , j dp_{i,j} dpi,j: 目前考虑了前 i i i个数, j j j y − x y-x yx的值。
状态转移:
①如果这是一个长度为 1 1 1的段,那么 d p i , j = d p i − 1 , j − 1 dp_{i,j}=dp_{i-1,j-1} dpi,j=dpi1,j1

②如果这是一个长度为 2 2 2的段,那么 d p i , j = d p i − 2 , j + 1 × ( i − 1 ) dp_{i,j}=dp_{i-2,j+1}×(i-1) dpi,j=dpi2,j+1×(i1)
这里为啥要乘上一个 ( i − 1 ) (i-1) (i1)呢?因为,我们已经安排好了前 i − 2 i-2 i2个数,而第 i − 1 i-1 i1个数又不得不是最大的(这是一个长度为 2 2 2的递减段),只有 1 1 1种安排方式(选最大的数);所以,第 i i i个数有 i − 1 i-1 i1种安排方式,要乘上 i − 1 i-1 i1

③如果这是一个长度为 3 3 3的段,那么 d p i , j = d p i − 3 , j × ( i − 1 ) × ( i − 2 ) dp_{i,j}=dp_{i-3,j}×(i-1)×(i-2) dpi,j=dpi3,j×(i1)×(i2)
状态转移的证明同②。

由于 y − x y-x yx的值可能为负数,我们可以将其统一加上一个大一点的数来防止 R E RE RE。最终答案为 ∑ i = 0 3 n d p 3 n , i [ i   m o d   3 = 0 ] \sum_{i=0}^{3n} dp_{3n,i}[i\ mod\ 3=0] i=03ndp3n,i[i mod 3=0]

时间复杂度 O ( n 2 ) O(n^2) O(n2)。注意取模即可。


思考一下,我们用什么方法解决了这道神仙题

首先,通过打表或思考我们找出了充分必要条件(两个重要性质);接着,我们使用差值 d p dp dp压缩空间与时间,最终在 O ( n 2 ) O(n^2) O(n2)的时间复杂度内求出了答案。

这题巧妙运用了多种技巧,在 A G C AGC AGC D D D题中,虽然偏简单,但是仍然是一道不可多得的好题,全场有且仅有 120 120 120人做出来。我没做出来,我太菜了

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxl=3005;

int n,mod,x,ans=0;
int dp[maxl*3][6*maxl];//dp[i][j]:i numbers, j为段1-段2 

signed main()
{
    
    
	cin>>n>>mod;
	x=3*n;
	dp[0][x]=1;
	for (int i=0;i<3*n;i++)
	{
    
    
		for (int j=-3*n;j<=(3*n);j++)
		{
    
    
			dp[i+1][j+1+x]=(dp[i+1][j+1+x]+dp[i][j+x])%mod;
			dp[i+2][j-1+x]=(dp[i+2][j-1+x]+dp[i][j+x]*(i+1))%mod;
			dp[i+3][j+x]=(dp[i+3][j+x]+dp[i][j+x]*(i+1)%mod*(i+2))%mod;
		}
	}
	for (int i=0;i<=n;i++)  ans=(ans+dp[3*n][i*3+x])%mod;
	cout<<ans<<endl;
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/109308335