AGC005 D ~K Perm Counting

17 篇文章 0 订阅

题意

问N有多少个排列满足,对于i∈[1,n],有 ∣ a i − i ∣ ̸ = K |a_i-i|\not=K aii̸=K
答案对924844033取模

题解

如果我们算出至少i个位置不满足这个条件的方案数(令之为f(i)),那我们就可以通过容斥来实现这道题
那么f(i)怎么求呢
我们把位置看成点,位置上的数也看成点,把那些不满足条件的关系看做边,那么就成了一个二分图
如图是5 2
如图是5 2
如果我们把他拉直会怎样
在这里插入图片描述
其实就成了几条链
其实也就是说,只有那些%k相同的彼此间会有影响
我们可以通过一个dp,分开计算每条链匹配i条边的方案数,然后再加起来
不过更巧妙的办法是把这些看做一条长链,只是那些原来的链的终止节点不能被匹配,然后答案直接读取末尾的值
也即是说,定义 d ( i , j , 0 / 1 ) d(i,j,0/1) d(i,j,0/1)表示前i个,有j个不满足条件,当前节点是否选择
d ( i + 1 , j , 0 ) = d ( i , j , 0 ) + d ( i , j , 1 ) d(i+1,j,0)=d(i,j,0)+d(i,j,1) d(i+1,j,0)=d(i,j,0)+d(i,j,1)
d ( i + 1 , j + 1 , 1 ) = d ( i , j , 0 ) d(i+1,j+1,1)=d(i,j,0) d(i+1,j+1,1)=d(i,j,0)(注意此处转移时判断是否是两条链的交界处(即是否真的可以连边))
那么,我们要求的f(i)就是 ( d ( 2 ∗ n , i , 0 ) + d ( 2 ∗ n , i , 1 ) ) ∗ ( n − i ) ! (d(2*n,i,0)+d(2*n,i,1))*(n-i)! (d(2n,i,0)+d(2n,i,1))(ni)!
然后容斥

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mod=924844033;
const int N=4005;
int n,k;
bool vis[N][2];
bool ed[N];
int tot;
int d[N][N][2];
int f[N];
int fact[N];
int main()
{
    //freopen("captain.in","r",stdin);
    //freopen("captain.out","w",stdout);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        for(int j=0;j<=1;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++;
                tot+=len;
                ed[tot]=1;
            }
    ed[0]=1;
    d[0][0][0]=1;
    for(int i=0;i<2*n;i++)
        for(int j=0;j<=n;j++){
            d[i+1][j][0]=(d[i][j][0]+d[i][j][1])%mod;
            if(!ed[i])
                d[i+1][j+1][1]=d[i][j][0];
        }
    for(int i=0;i<=n;i++)
        f[i]=(d[2*n][i][0]+d[2*n][i][1])%mod;
    fact[0]=1;
    for(int i=1;i<=n;i++)
        fact[i]=1ll*fact[i-1]*i%mod;
    int ans=0;
    for(int i=0,j=1;i<=n;i++,j=-j)
        ans=((1ll*ans+1ll*j*fact[n-i]*f[i]%mod)%mod+mod)%mod;
    printf("%d\n",ans);
}

这里还有 O ( N l o g N ) O(NlogN) O(NlogN)的做法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值