[JOISC 2020 Day2] 遗迹
一、题目
二、解法
首先理解删除的过程,可以按值域从高到低扫描。可以维护一个堆,每次向堆中加入两个初始值为 的位置,然后取出堆中最大的位置,钦定它为高度 并且可以保留到最后。
这样还不是特别好做,考虑切换限制主体得到更多性质。我们按位置从后到前扫描,维护一个还未出现过的集合 ,假设现在遇到元素的高度是 ,那么我们取出 中 的最大值(若不存在视为 ),作为这个位置最终的值。
那么从后往前 ,考虑记录一段连续的被取出的前缀,可以用类似 MEX counting 的状态定义和转移方法。设 表示考虑后 个位置, 中被取出的最大前缀是 的方案数,设以前的位置有 个位置钦定消失,有 个位置钦定出现。
为了方便转移,把相同高度的元素染色(视为不同),最后再除以 就得到了答案。
若当前位置钦定消失,则值肯定不能超过 ,以前钦定出现了用了 个,钦定消失了用了 个,可用的位置数量是 ,所以:
若当前位置钦定出现,分两种情况讨论:
- 如果最后的值 ,那么延后考虑这个位置的取值:
- 如果最后的值 ,枚举前缀的变化量 ,那么这个位置的取值有 个。选择 个元素出现位置的方案数是 ,那么:
其中 的含义为可用 种初始高度,要求生成最后 个高度连续元素的方案数。设 表示用了 种初始高度,填满了 个位置,那么 ,转移的限制是:
- 每种高度只能用最多两次。
- 要一直满足 ,即填多了位置是不合法的。
那么可以写出转移
时间复杂度
#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);
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
2021-06-11 Educational Codeforces Round 110 (Rated for Div. 2)