zoukankan      html  css  js  c++  java
  • [AGC030F] Permutation and Minimum

    CXXXIX.[AGC030F] Permutation and Minimum

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

    为方便,我们将 \(n\) 乘二。

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

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

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

    我们考虑分情况从 \(f_{i,j,k}\) 转移到 \(f_{i+1,j',k'}\)

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

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

    则此时显然其已被唯一确定,直接划归 \(j\) 类。故此种情形唯一可行转移是 \(f_{i,j,k}\rightarrow f_{i+1,j+1,k}\)

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

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

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

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

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

    则依照定义,其应划归 \(k\) 类,留待以后处理。故 \(f_{i,j,k}\rightarrow f_{i+1,j,k+1}\)

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

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

    则此时与 \(k\) 类中哪个东西匹配根本不影响,因为每一对的最小值已经被确定了。因此若 \(k\) 非零,则有 \(f_{i,j,k}\rightarrow f_{i,j+2,k-1}\)

    情形2.2. 作为 \(i-j-k\) 类。

    则直接划归即可,因为方案数已在1.2.1中被计算。故 \(f_{i,j,k}\rightarrow f_{i+1,j,k}\)

    情形2.3. 作为 \(k\) 类。

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

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

    显然转移 \(O(1)\);于是复杂度 \(O(n^3)\) 解决。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int mod=1e9+7;
    int n,a[610],p[610],f[2][610][610],sig[610],mat[610],sur[610];
    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];
    	
    //	for(int i=1;i<=n;i++)printf("%d ",sur[i]);puts("");	
    //	for(int i=1;i<=n;i++)printf("%d ",mat[i]);puts("");
    //	for(int i=1;i<=n;i++)printf("%d ",p[i]);puts("");
    	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(j+k!=i)continue;
    //			printf("%d %d %d:%d\n",i,j,k,f[i&1][j][k]);
    			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;//we left i+1 unmatched, which should be considered as k.
    //				printf("I-J-K:%d\n",i-j-k);
    				(f[!(i&1)][j+2][k]+=1ll*(i-j-k)*f[i&1][j][k]%mod)%=mod;//we match something unsure position
    			}else{
    				if(k)(f[!(i&1)][j+2][k-1]+=f[i&1][j][k])%=mod;//we match something of sure position
    				(f[!(i&1)][j][k]+=f[i&1][j][k])%=mod;//match it with an element in [i+2,n]
    				(f[!(i&1)][j][k+1]+=1ll*(n-(j+sur[i])-k*2-sig[i]*2)/2*f[i&1][j][k]%mod)%=mod;//put it in any empty space.
    			}
    		}	
    	}
    	printf("%d\n",f[n&1][n][0]);
    	return 0;
    }
    

  • 相关阅读:
    CSS 对浏览器的兼容性技巧总结
    后台拿webshell的方法总结
    WEBSHELL权限提升技巧
    学习linq处理数据 遥远的青苹果
    在asp.net中怎么导出excel表
    SQL提取数据库记录按字的笔画排序
    主板前置音频线怎么接
    Oracle导入导出
    DevExpress GridControl界面汉化(摘自王铭)
    ASP.NET中如何防范SQL注入式攻击
  • 原文地址:https://www.cnblogs.com/Troverld/p/14601602.html
Copyright © 2011-2022 走看看