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

    题意

    有一个 (2 N) 个数的序列 (A),从 (1)(2 N) 标号。你要把 (1 sim 2 N) 这些数填进去,使它形成一个排列。

    但是已经有一些位置强制填了特定的数了,输入时会给出。

    最后令长度为 (N) 的序列 (B) 为:令 (B_i = min{A_{2 i - 1}, A_{2 i}})

    询问所有方案中能得到的不同的 (B) 的数量。

    (1 le N le 300)

    思路

    我们把((A_{2i},A_{2i-1}))称作第(i)对数。显然,一对数只有至少一个数为-1,才会对答案有贡献

    接着就是这些有答案贡献的对填空,将没有填的数字从大到小排序来考虑

    为什么是从大到小填?假设从小到大填,假设填了一个数对((i,-1)),你会发现这个数对无论填(i+1)还是(i+2),实际上方案数并没有增加,而我们会重复计算,具有后效性。但从大到小并没有这种担忧,可以始终保证答案不重不漏

    (f(i,j,k))表示当前考虑到第(i)个数,还剩(j)个只填了1个数的对(不包含被限定一位的对),称作((-1,a)),(k)个原本就限定了一位的对,称作((-1,x))

    考虑前(i-1)个数已经讨论,现在考虑第(i)个数,进行分类讨论

    如果它是((-1,x))中的(x)(也就是被限定的数字),那么它可以不匹配,(f(i-1,j,k))转移到(f(i,j,k+1)),也可以匹配与一个填了一个数的((-1,a))匹配,那么(f(i,j,k))转移到(f(i,j-1,k))

    如果不是,那么它可以不匹配,(f(i-1,j-1,k))转移到(f(i,j,k));也可以填入((-1,a))(f(i-1,j+1,k))转移到(f(i,j,k));还可以填入((-1,x)),因为任意(x)的位置固定,所以会有(k+1)种填入方案,((k+1) imes f(i-1,j,k+1))转移到(f(i,j,k))

    然后我们就讨论完了,初状态(f(0,0,0)=1)

    又因为原本都是-1的数对是无序的,所以最终答案还得乘上原本都是-1的数对个数的阶乘

    感悟

    从这一题中我们可以发现,设计状态转移方程的时候,不一定要以“如何计算出某一个状态入手”,也可以换一个思路,思考“这一个状态可以更新哪些后面的状态”,根据具体情况来选择更加简单,顺畅的思路

    代码

    #include<bits/stdc++.h>
    using namespace std;
    int const MAXN=610,MOD=1e9+7;
    int n,cnt1,cnt2,m,ans=1;
    int a[MAXN],vis[MAXN],f[MAXN][MAXN][MAXN];
    void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
    int main(){
    	scanf("%d",&n);n*=2;
    	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    	for(int i=1;i<=n;i+=2){
    		if(a[i]>-1 &&a[i+1]>-1){
    			vis[a[i]]=vis[a[i+1]]=2;
    		}else if(a[i]<0 && a[i+1]<0){
    			cnt1++;
    		}else{
    			cnt2++;
    			if(a[i]>-1)vis[a[i]]=1;
    			if(a[i+1]>-1)vis[a[i+1]]=1;
    		}
    	}
    	for(int i=n;i>=1;i--){
    		if(vis[i]<2)a[++m]=i;
    	}
    	f[0][0][0]=1;
    	for(int i=1;i<=m;i++){
    		for(int j=0;j<=n;j++){
    			for(int k=0;k<=n;k++){
    				if(vis[a[i]]==1){
    					if(k)add(f[i][j][k],f[i-1][j][k-1]);
    					add(f[i][j][k],f[i-1][j+1][k]);
    				}else{
    					if(j)add(f[i][j][k],f[i-1][j-1][k]);
    					add(f[i][j][k],f[i-1][j+1][k]);
    					add(f[i][j][k],1ll*f[i-1][j][k+1]*(k+1)%MOD);
    				}
    			}
    		}
    	}
    	ans=f[m][0][0];
    	for(int i=1;i<=cnt1;i++)ans=1ll*ans*i%MOD;
    	printf("%d
    ",ans%MOD);
    	return 0;
    }
    

    参考资料

    yyb的博客

    万神的博客

  • 相关阅读:
    markdown转HTML,目录生成
    schedule与scheduleAtFixedRate之Timer源码分析
    rocketmq刷盘过程
    rocketmq消息存储概述
    Cassandra修改集群名称
    Cassandra读写性能测试
    rocketmq--push消费过程
    rocketmq消费负载均衡--push消费为例
    go反射实例
    JUnit4参数的使用
  • 原文地址:https://www.cnblogs.com/fpjo/p/13958625.html
Copyright © 2011-2022 走看看