[AGC030F] Permutation and Minimum 题解

Permutation and Minimum

看到 300 的数据范围,再加上计数题,很容易就往计数 DP 方向去想。

为方便,我们将 n 乘二。

因为是两个位置取 min,于是我们便想到从小往大把每个数填入序列。于是DP数组第一维的意义便出来了:当前已经填入了前 i 小个数。

考虑当前填入一个数。这个数有两种可能:一是与比它小的数匹配,此时最小值已经确定了,是那个更小的数,而更小数已经被填入序列,所以当前数与哪个比它小的数匹配根本不影响。二是与比它大的数匹配——此时最小值不确定,因此与不同的比它大的数在不同位置匹配会有不同结果。

于是我们便依次设出了剩余两维的意义:前 i 位中有 j 个东西是确定的(指与它配对的东西以及这一对数所在的位置都被确定,不论这确定的对是后来填出的还是初始序列中就已经给出的;若初始序列中只给出了一半的数,不算作此类),k 个东西是固定的(指其所在的位置固定,但与其配对的东西尚未被确定,显然此种情形下与其匹配的东西应是一个未在原序列中出现的东西),则剩余 ijk 个东西匹配了原序列中出现的东西,且该另一半必比 i 大。(注意这里我们把确定和固定这两个词黑体了,因为我们接下来还要多次使用它们)

我们考虑分情况从 fi,j,k 转移到 fi+1,j,k

情形1. 若数 i+1 在序列中被给出了:

情形1.1. 若数 i+1 在序列中被给出了,且其配对也被给出了:

则此时显然其已被唯一确定,直接划归 j 类。故此种情形唯一可行转移是 fi,j,kfi+1,j+1,k

情形1.2. 若数 i+1 在序列中被给出,但其配对未被给出:

有两种情形:其与比它小的东西匹配,或者与比它大的东西匹配。

情形1.2.1. 与比它小的东西匹配。

则依照定义,其应与 ijk 中某个东西匹配,且与其中不同东西匹配有影响(因为匹配的另一半是此段的最小值)。匹配完后 j 类多出了两个。则转移是 fi,j,k×(ijk)fi+1,j+2,k

情形1.2.2. 与比它大的东西匹配。

则依照定义,其应划归 k 类,留待以后处理。故 fi,j,kfi+1,j,k+1

情形2. 若数 i+1 在序列中未被给出:

情形2.1. 作为 k 类中某个东西的另一半。

则此时与 k 类中哪个东西匹配根本不影响,因为每一对的最小值已经被确定了。因此若 k 非零,则有 fi,j,kfi,j+2,k1

情形2.2. 作为 ijk 类。

则直接划归即可,因为方案数已在 1.2.1 中被计算。故 fi,j,kfi+1,j,k

情形2.3. 作为 k 类。

依照定义,k 类中元素的位置都是固定的。故 i+1 应被填到一个空余区间里。考虑计算此种空余区间的数量。

显然,总序列中,若我们设 suri 表示填入前 i 个数后序列中剩余的确定的数(即原序列中给出的已经确定好的数对),则目前一共有 j+suri 个数已经确定。k 类中已有的东西每个都占掉了 2 个格子,故还得减去 2k;再令 sigi 表示填入前 i 个数后剩余的固定的数(即原序列中给出的确定好一半的数对),显然其也各占去 2 个格子;又因为同一个区间里两个数的顺序无影响,所以还得除以 2。所以我们最终得到了空余区间的数量为 n(j+suri)2k2sigi2。若设其为 spr,则有 fi,j,k×sprfi+1,j,k+1

显然转移 O(1);于是复杂度 O(n3) 解决。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int N=610;
int n,a[N],p[N],f[2][N][N];
int sig[N],mat[N],sur[N];
int main(){
    scanf("%d",&n);
	n<<=1;
    for(int i=1;i<=n;i++) mat[i]=((i-1)^1)+1;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),p[a[i]]=i;
    for(int i=1;i<=n;i++) if(a[i]!=-1&&a[mat[i]]==-1) sig[a[i]-1]++;
    for(int i=n;i>=0;i--) sig[i]+=sig[i+1];
    for(int i=1;i<=n;i++) if(a[i]!=-1&&a[mat[i]]!=-1) sur[a[i]-1]++;
    for(int i=n;i>=0;i--) sur[i]+=sur[i+1];
    f[0][0][0]=1;
    for(int i=0;i<n;i++){
        memset(f[!(i&1)],0,sizeof(f[!(i&1)]));
        for(int j=0;j<=i;j++){
			for(int k=0;j+k<=i;k++){
            	if(n-(j+sur[i])-k*2-sig[i]*2<0)continue;
            	if(!f[i&1][j][k])continue;
            	if(p[i+1]){
                	if(a[mat[p[i+1]]]!=-1){f[!(i&1)][j+1][k]=f[i&1][j][k];continue;}
                	(f[!(i&1)][j][k+1]+=f[i&1][j][k])%=mod;
                	(f[!(i&1)][j+2][k]+=1ll*(i-j-k)*f[i&1][j][k]%mod)%=mod;
            	}else{
                	if(k)(f[!(i&1)][j+2][k-1]+=f[i&1][j][k])%=mod;
                	(f[!(i&1)][j][k]+=f[i&1][j][k])%=mod;
                	(f[!(i&1)][j][k+1]+=1ll*(n-(j+sur[i])-k*2-sig[i]*2)/2*f[i&1][j][k]%mod)%=mod;
            	}
        	}   
        }
    }
    printf("%d\n",f[n&1][n][0]);
    return 0;
}
posted @   xuantianhao  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示