• [JOISC 2020 Day2] 遗迹


    一、题目

    点此看题

    二、解法

    首先理解删除的过程,可以按值域从高到低扫描。可以维护一个堆,每次向堆中加入两个初始值为 \(i\) 的位置,然后取出堆中最大的位置,钦定它为高度 \(i\) 并且可以保留到最后。

    这样还不是特别好做,考虑切换限制主体得到更多性质。我们按位置从后到前扫描,维护一个还未出现过的集合 \(S\),假设现在遇到元素的高度是 \(h_i\),那么我们取出 \(S\)\(\leq h_i\) 的最大值(若不存在视为 \(0\)),作为这个位置最终的值。

    那么从后往前 \(dp\),考虑记录一段连续的被取出的前缀,可以用类似 MEX counting 的状态定义和转移方法。设 \(f[i][j]\) 表示考虑后 \(i\) 个位置,\(S\) 中被取出的最大前缀是 \(j\) 的方案数,设以前的位置有 \(c_0\) 个位置钦定消失,有 \(c_1\) 个位置钦定出现。

    为了方便转移,把相同高度的元素染色(视为不同),最后再除以 \(2^n\) 就得到了答案。

    若当前位置钦定消失,则值肯定不能超过 \(j\),以前钦定出现了用了 \(j\) 个,钦定消失了用了 \(c_0\) 个,可用的位置数量是 \(2j-j-c_0=j-c_0\),所以:\(f[i][j]\leftarrow f[i-1][j]\cdot (j-c_0)\)

    若当前位置钦定出现,分两种情况讨论:

    • 如果最后的值 \(>j+1\),那么延后考虑这个位置的取值:\(f[i][j]\leftarrow f[i-1][j]\)
    • 如果最后的值 \(=j+1\),枚举前缀的变化量 \(k\),那么这个位置的取值有 \(2k-(k-1)=k+1\) 个。选择 \(k-1\) 个元素出现位置的方案数是 \({c_1-j\choose k-1}\),那么:\(f[i][j+k]\leftarrow f[i-1][j]\cdot G[k-1]\cdot (k+1)\cdot {c_1-j\choose k-1}\)

    其中 \(G[i]\) 的含义为可用 \(i\) 种初始高度,要求生成最后 \(i\) 个高度连续元素的方案数。设 \(g[i][j]\) 表示用了 \(i\) 种初始高度,填满了 \(j\) 个位置,那么 \(G[i]=g[i][i]\),转移的限制是:

    • 每种高度只能用最多两次。
    • 要一直满足 \(i\geq j\),即填多了位置是不合法的。

    那么可以写出转移 \(g[i][j]=g[i-1][j]+g[i-1][j-1]\cdot 2j+g[i-1][j-2]\cdot j(j-1)\)

    时间复杂度 \(O(n^3)\)

    #include <cstdio>
    const int M = 1205;
    const int MOD = 1e9+7;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a[M],g[M][M],f[M][M],C[M][M];
    void add(int &x,int y) {x=(x+y)%MOD;}
    signed main()
    {
    	n=read();m=n<<1;
    	for(int i=1;i<=n;i++) a[read()]=1;
    	for(int i=0;i<=m;i++)
    	{
    		C[i][0]=1;
    		for(int j=1;j<=i;j++)
    			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
    	}
    	g[0][0]=1;
    	for(int i=1;i<=n;i++)
    		for(int j=0;j<=i;j++)
    		{
    			g[i][j]=g[i-1][j];
    			if(j>0) add(g[i][j],2*j*g[i-1][j-1]);
    			if(j>1) add(g[i][j],j*(j-1)*g[i-1][j-2]);
    		}
    	f[m+1][0]=1;
    	for(int i=m,c0=0,c1=0;i>=1;i--)
    	{
    		if(a[i])
    		{
    			c1++;
    			for(int j=c0;j<=c1-1;j++)
    			{
    				add(f[i][j],f[i+1][j]);
    				for(int k=1;k<=c1-j;k++)
    					add(f[i][j+k],f[i+1][j]*C[c1-j-1][k-1]
    					%MOD*g[k-1][k-1]%MOD*(k+1));
    			}
    		}
    		else
    		{
    			c0++;
    			for(int j=c0;j<=c1;j++)
    				add(f[i][j],f[i+1][j]*(j-c0+1));
    		}
    	}
    	int ans=f[1][n],inv2=(MOD+1)/2;
    	for(int i=1;i<=n;i++) ans=ans*inv2%MOD;
    	printf("%lld\n",ans);
    }
    
  • 相关阅读:
    2040 打开所有的灯
    1323 删数问题(加强版)
    1087 FBI树
    1030 求先序排列
    1743 矩阵Ⅲ
    svn更新时,出现不知道这样的主机的解决方案
    用jquery或js获取select标签中选中的option值及文本
    html页面中的button按钮会自动提交form表单的问题以及解决方案
    localStorage与sessionStorage的使用和区别
    命令行mvn打包
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16365240.html
Copyright © 2020-2023  润新知