agc005D ~K Perm Counting

本文详细解析了AtCoder竞赛中一道排列计数题目,通过构建二分图并运用动态规划方法,解决了求特定条件下排列总数的问题。文章提供了完整的算法思路及C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

https://agc005.contest.atcoder.jp/tasks/agc005_d

题意简述

求长度为 n n n的排列中 ∀ i , ∣ a i − i ∣ ̸ = k \forall i,|a_i-i|\not= k i,aii̸=k的排列总数。

题解

假设满足 ∣ a i − i ∣ = k |a_i-i|=k aii=k的位置的数量至少有 j j j个的排列总数为 g j g_j gj。那么答案就是
∑ i = 0 n ( − 1 ) i g i ( n − i ) ! \sum_{i=0}^n (-1)^ig_i (n-i)! i=0n(1)igi(ni)!
现在的问题就是怎么求 g i g_i gi。建一个二分图,如果两个点满足:

  • 分别在二分图的两侧
  • 假设编号为 i , j i,j i,j,满足 ∣ i − j ∣ = k |i-j|=k ij=k

那么就连一条边。这张图上匹配为 i i i的方法的总数就是 g i g_i gi

那么 g i g_i gi?容易发现,这张图是由多条链构成的,将这几条链拼在一起,然后在钦定一些地方不能有匹配的边,这张二分图的匹配数量为 i i i的方案数可以很方便的dp求出。

代码

#include <cstdio>

int read()
{
  int x=0,f=1;
  char ch=getchar();
  while((ch<'0')||(ch>'9'))
    {
      if(ch=='-')
        {
          f=-f;
        }
      ch=getchar();
    }
  while((ch>='0')&&(ch<='9'))
    {
      x=x*10+ch-'0';
      ch=getchar();
    }
  return x*f;
}

const int maxn=2000;
const int mod=924844033;

int n,k,t,vis[maxn+10][2],a[maxn*2+10],f[maxn*2+10][maxn+10][2],g[maxn+10],ans,fac[maxn+10];

int main()
{
  n=read();
  k=read();
  for(int i=1; i<=n; ++i)
    {
      for(int j=0; j<2; ++j)
        {
          if(!vis[i][j])
            {
              int len=0;
              for(int x=i,y=j; x<=n; x+=k,y^=1)
                {
                  vis[x][y]=1;
                  ++len;
                }
              t+=len;
              a[t+1]=1;
            }
        }
    }
  f[1][0][0]=1;
  for(int i=2; i<=t; ++i)
    {
      f[i][0][0]=f[i-1][0][0]+f[i-1][0][1];
      if(f[i][0][0]>=mod)
        {
          f[i][0][0]-=mod;
        }
      for(int j=1; j<=n; ++j)
        {
          f[i][j][0]=f[i-1][j][0]+f[i-1][j][1];
          if(f[i][j][0]>=mod)
            {
              f[i][j][0]-=mod;
            }
          if(!a[i])
            {
              f[i][j][1]=f[i-1][j-1][0];
            }
        }
    }
  for(int i=0; i<=n; ++i)
    {
      g[i]=f[t][i][0]+f[t][i][1];
      if(g[i]>=mod)
        {
          g[i]-=mod;
        }
    }
  fac[0]=1;
  for(int i=1; i<=n; ++i)
    {
      fac[i]=1ll*fac[i-1]*i%mod;
    }
  for(int i=0; i<=n; ++i)
    {
      ans=(ans+1ll*((i&1)?(mod-1):1)*g[i]%mod*fac[n-i])%mod;
    }
  printf("%d\n",ans);
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值