【agc030f】Permutation and Minimum(动态规划)

【agc030f】Permutation and Minimum(动态规划)

题面

atcoder
给定一个长度为\(2n\)的残缺的排列\(A\),定义\(b_i=min\{A_{2i-1},A_{2i}\}\),求有多少种不同的\(b\)

题解

考虑这个\(b\)的取值是两两配对之后求\(min\),所以我们把所有的数按照从大往小处理,这样子就可以在每一对数填好的时候计算贡献。
首先把已经确定的\(b\)直接丢掉。这样子剩下了若干个填了一半和没有填的对。
\(f[i][j][k]\)表示当前考虑到第\(i\)个数,还剩下\(j\)对填了一个数的\((-1,-1)\)对和\(k\)对未匹配的\((x,-1)\)对。
暂时不用考虑对与对之间的顺序关系,最后把由两个\(-1\)组成的配对拿出来乘一个阶乘就好了。
考虑新加入的这个数。它有两种贡献,第一种是和一个填了一半的进行配对,另外一个是自己产生一个填了一半的配对。
分类讨论,考虑它是不是\((x,-1)\)对中的一个\(x\)
如果不是,那么可以考虑填入一个\((-1,-1)\)对中的一个,那么转移到\(f[i][j+1][k]\),要么匹配掉一个\((-1,-1)\)对,变成\(f[i][j-1][k]\),要么匹配一个\((x,-1)\)对,这里有\(k\)种匹配方法,所以乘\(k\)后转移到\(f[i][j][k-1]\)
如果是,那么要么不匹配,转移到\(f[i][j][k+1]\),要么拉一个填了一半的\((-1,-1)\)对过来,转移到\(f[i][j-1][k]\)

#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 1000000007
#define MAX 305
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,m,ans,a[MAX<<1];
int cnt1,cnt2,f[MAX<<1][MAX][MAX];
int S[MAX<<1];
bool vis[MAX<<1],book[MAX<<1];
int main()
{
    n=read();
    for(int i=1;i<=n+n;++i)a[i]=read();
    for(int i=1;i<=n+n;i+=2)
        if(a[i]==-1&&a[i+1]==-1)++cnt1;
        else if(a[i]>0&&a[i+1]>0)vis[a[i]]=vis[a[i+1]]=true;
        else ++cnt2,book[(~a[i])?a[i]:a[i+1]]=true;
    for(int i=n+n;i;--i)if(!vis[i])S[++m]=i;
    f[0][0][0]=1;
    for(int i=1;i<=m;++i)
        for(int j=0;j<=cnt1+cnt2;++j)
            for(int k=0;k<=cnt2;++k)
            {
                if(!f[i-1][j][k])continue;
                if(!book[S[i]])
                {
                    add(f[i][j+1][k],f[i-1][j][k]);
                    if(j)add(f[i][j-1][k],f[i-1][j][k]);
                    if(k)add(f[i][j][k-1],1ll*k*f[i-1][j][k]%MOD);
                }
                else
                {
                    add(f[i][j][k+1],f[i-1][j][k]);
                    if(j)add(f[i][j-1][k],f[i-1][j][k]);
                }
            }
    ans=f[m][0][0];for(int i=1;i<=cnt1;++i)ans=1ll*ans*i%MOD;
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10486411.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值