2017.10.4 DP 解题报告

Description

一个n*m的01矩阵,每一行给出li和ri
现在给出限制条件,对于每一行
第 1~li列恰好有 1个 1,第 ri~m列恰好有 1个 1。
对于每一列,至多有 1个 1。

【解题报告】

都写在代码里了
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int N=3010;
const int maxn=3000;
const int mod=998244353;

int c[N][N],dp[N][N];
int cj[N];
int sl[N],sr[N],s[N];
int cl[N],cr[N];

int main()
{
    freopen("matrix.in","r",stdin);
    freopen("matrix.out","w",stdout);
    int l,r;
    int n,m;
    for(int i=0;i<N;++i)
    {
        c[i][0]=1;
        for(int j=1;j<=i;++j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    }
    cj[0]=1;for(int i=1;i<N;++i) cj[i]=1ll*cj[i-1]*i%mod;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&l,&r);
        cl[l]++;
        cr[r]++;
    }
    for(int i=1;i<=m;++i) 
    {
        sl[i]=sl[i-1]+cl[i];
        sr[i]=sr[i-1]+cr[i];
        s[i]=sl[i]+sr[i];
    }
    dp[0][0]=1;
    for(int i=1;i<=m;++i)
    {
        int k,c1,c2;
        for(int j=0;j<=sr[i-1];++j)
        {
            //用dp[i][j]表示做到前i列,对于已经跨过的左区间我们已经填了1了,对于碰到的右区间我们已经填了一部分1,但还有j和右区间待定 
            if(!dp[i-1][j]) continue;
            k=i-1-s[i-1]+j;//当前列左边留下的可以填的空 
            if(k<0) continue;
            //在更新到当前列时我们会碰到一些右区间,我们可以选择一个区间填上1,也可以把他们全部待定 
            c1=cl[i]<=k?1ll*c[k][cl[i]]*cj[cl[i]]%mod:0;
            //如果我们填上一个1的之前的待定填法 
            c2=cl[i]<=k+1?1ll*c[k+1][cl[i]]*cj[cl[i]]%mod:0;
            //如果我们全部待定,则对于之前的待定区间,有k+1个空,填上cl(i)个1的方案 
            dp[i][j+cr[i]]=(dp[i][j+cr[i]]+1ll*dp[i-1][j]*c2)%mod;
            //多有新加入的右区间全部待定 
            if(j+cr[i]) dp[i][j+cr[i]-1]=(dp[i][j+cr[i]-1]+1ll*dp[i-1][j]*c1%mod*(j+cr[i]))%mod;
            //填上一个右区间 
        }
    }
    printf("%d",dp[m][0])%mod;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值