牛客练习赛71 C 数学考试

12 篇文章 0 订阅

link

题意:求长度为n的排列有多少个 要求满足 m个条件 :pi 表示前pi个数不是1~pi的全排列
m<n<=2000
思路:
1️⃣:考虑用总方案数减去不符合条件的方案数:即对于位置 x 为,x! - (不符合条件)。设F[i]为前i-1个条件满足,第i个条件不满足的方案数,并且我们增加一项p[m+1]=n,那么F[m+1]即为答案
转移方程:F[i]=sigma(F[j]*(j-i)!)(1<=j<i)

第一次遇到这样的dp状态-_- 很奇妙。。 它是通过枚举最后一个不满足条件的位置来得到答案,所以不会减重复。学到了

2️⃣:常规dp,dp[i][j]表示第i个位置以及之前的数最大值为j的方案数,那么对于限制条件i我们只需要使dp[p[i]][p[i]]==0后正常转移即可

#include<bits/stdc++.h>
#define pb push_back
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long ll;
typedef vector<int> VI;
const int mod  = 20000311;
const int inf  = 0x3f3f3f3f;
const int maxn = 2e6  + 4;
const int N    = 405;
//ll qpow(ll x, ll y) {ll ans = 1; x %= mod; assert(y >= 0); while (y) { if (y & 1) ans = ans * x % mod; x = x * x % mod; y >>= 1;} return ans;}
//ctrl + h  ctrl + shift + t
 
 int n,m,p[maxn];
 ll fac[maxn],f[maxn];

int main() {
    //freopen("input.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d",&p[i]);
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    sort(p+1,p+1+m);
    p[++m]=n;
    for(int i=1;i<=m;i++) {
        f[i]=fac[p[i]];
        ll res=0;
        for(int j=1;j<i;j++) res=(res+f[j]*fac[p[i]-p[j]])%mod;
        f[i]=f[i]+mod-res;
        f[i]%=mod; 
    }
    printf("%lld\n",f[m]);

    return 0;
}
#include<bits/stdc++.h>
#define pb push_back
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long ll;
typedef vector<int> VI;
const int mod  = 20000311;
const int inf  = 0x3f3f3f3f;
const int maxn = 2e6  + 4;
const int N    = 405;
//ll qpow(ll x, ll y) {ll ans = 1; x %= mod; assert(y >= 0); while (y) { if (y & 1) ans = ans * x % mod; x = x * x % mod; y >>= 1;} return ans;}
//ctrl + h  ctrl + shift + t
 
 int n,m,d,p[maxn],a[maxn];
 ll fac[maxn],dp[2020][2020],sum[2020];

int main() {
    //freopen("input.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d",&d),a[d]=1;
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    dp[0][0]=1;
    for(int i=1;i<=n;i++) {
        sum[0]=dp[i-1][0];
        for(int j=1;j<=n;j++) sum[j]=(dp[i-1][j]+sum[j-1])%mod;
        for(int j=i;j<=n;j++) {
            dp[i][j]=dp[i-1][j]*(j-i+1)%mod;
            ll k=0;
            if(i>=2) k=sum[j-1]+mod-sum[i-2];
            else k=sum[j-1];
            dp[i][j]+=k;
            dp[i][j]%=mod;
        }
        if(a[i]) dp[i][i]=0;
    }  
    printf("%lld\n",dp[n][n]);

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值