【ZOJ 3874】Permutation Graph

本文深入解析了如何运用NTT(快速傅立叶变换)与CDQ分治策略解决特定竞赛题目的技巧。通过分析题目的核心特征,作者详细介绍了将连续下表转化为方案数计算的过程,利用动态规划dp[i]和卷积操作实现优化。文中还提及了NTT模板的补充计划以及直接计算复杂度的优化方法——CDQ分治。最后,提供了完整的代码实现,以便读者理解和实践。

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

浙江省赛题。当时赛后听说是NTT+CDQ震惊了两个词一个都没有听说过。

现在突然想起来这个题,回来一看也并不是那么的不可做。比赛的时候还在打表找规律233~

首先可以想到,因为逆序对都要连一条边,因此所有的对于任意一个部分都是下表连续的,否则答案就为0。

若下表连续的,则可以想到答案只与长度有关。

不妨设dp[i]为长度为i的连续下表的方案数,则可以得到

dp[i] = i! - sigma(dp[i - k] * k!),其中 1<= k < i

简单解释一下就是说,若1-i连续,则必须全是在同一个块中

考虑不合法的情况,不妨设最后一个块为k个连续的数,则需要减去的方案为dp[i - k] * k!

后面是卷积,可以用ntt计算。

说起来NTT的模板还没有补,有空补。

再有如果直接一项一项地算也是n^2logn的复杂度爆炸。

因此用cdq分治来优化一下就好了。

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

#define lson l,mid
#define rson mid + 1,r
#define root 1,n
#define Mid int mid = (l + r) >> 1

const int N = 1 << 18;
const int mod = 786433;
const int G = 10;

int dp[N];
int fac[N];
int x1[N << 1],x2[N << 1],tmp[N << 1];
int tn;
int Pow(int a,int b){
    int res = 1;
    while(b){
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}
int rev(int x){
    int res = 0;
    for(int i = 0 ; i < tn ; i ++){
        if(x & 1) res += 1 << tn - i - 1;
        x >>= 1;
    }
    return res;
}
void ntt(int A[],int n,int op){
    for(int i = 0 ; i < n ; i ++) tmp[ rev(i) ] = A[i];
    for(int i = 0 ; i < n ; i ++) A[i] = tmp[i];
    for(int i = 1 ; (1 << i) <= n ; i ++){
        int m = 1 << i;
        int wn;
        if(op == 1) wn = Pow(G,(mod - 1) / m);
        else wn = Pow(G,(mod - 1) - (mod - 1) / m);
        for(int k = 0 ; k < n ; k += m){
            int w = 1,u,t;
            for(int j = 0 ; j < m / 2 ; j ++){
                u = A[k + j];
                t = 1ll * w * A[k + j + m / 2] % mod;
                A[k + j] = (u + t) % mod;
                A[k + j + m / 2] = (u - t + mod) % mod;
                w = 1ll * w * wn % mod;
            }
        }
    }
    if(op == -1){
        int Inv = Pow(n,mod - 2);
        for(int i = 0 ; i < n ; i ++)
            A[i] = 1ll * A[i] * Inv % mod;
    }
}
void cdq(int l,int r){
    if(l > r) return;
    if(l == r){
        dp[l] = (dp[l] + fac[l]) % mod;
        return;
    }
    Mid;
    cdq(lson);
    int len = 1;tn = 0;
    while(len < (mid - l + 1) * 2)
        len <<= 1,tn ++;
    for(int i = l ; i <= mid ; i ++) x1[i - l] = dp[i];
    for(int i = mid - l + 1 ; i < len ; i ++) x1[i] = 0;
    for(int i = 0 ; i < len ; i ++) x2[i] = fac[i];
    ntt(x1,len,1);
    ntt(x2,len,1);
    for(int i = 0 ; i < len ; i ++)
        x1[i] = 1ll * x1[i] * x2[i] % mod;
    ntt(x1,len,-1);
    for(int i = mid + 1 ; i <= r ; i ++){
        dp[i] += (mod - x1[i - l]) % mod;
        dp[i] %= mod;
    }
    cdq(rson);
}
void init(){
    int n = 100002;
    fac[0] = 1;
    for(int i = 1 ; i <= n ; i ++)
        fac[i] = 1ll * fac[i - 1] * i % mod;
    cdq(root);
}
void solve(){
    int n,m;
    scanf("%d%d",&n,&m);
    int c,l,r,x;
    int ret = 1;
    for(int i = 1 ; i <= m ; i ++){
        l = n + 1,r = -1;
        scanf("%d",&c);
        for(int j = 0 ; j < c ; j ++){
            scanf("%d",&x);
            l = min(l,x);
            r = max(r,x);
        }
        ret = 1ll * ret * dp[c] % mod;
        if(r - l + 1 != c) ret = 0;
    }
    printf("%d\n",ret);
}
int main()
{
    init();
    int _;
    scanf("%d",&_);
    while(_--) solve();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值