ICPC2021上海B-Strange_Permutations(分治NTT)

题目链接:B-Strange_Permutations_第 46 届 ICPC 国际大学生程序设计竞赛亚洲区域赛(上海) (nowcoder.com)

Q i + 1 ≠ P Q i Q_{i+1}\ne P_{Q_i} Qi+1=PQi

从题意我们可以得出对于 Q Q Q排列的元素,元素” i i i‘’后面不能是‘’ P i P_i Pi‘’

然后就不知道怎么办了qwq,发现不了其它性质。而且和之前做的不太一样。它必须要恰好构成一条排列“链”的形式,不能走着走着变成环。无法想到咋求方案数,咋多项式做。。。

考虑把排列 Q Q Q看作一个边集 ( Q 1 , Q 2 ) , ( Q 2 , Q 3 ) , . . . , ( Q n − 1 , Q n ) (Q_1,Q_2),(Q_2,Q_3),...,(Q_{n-1},Q_n) (Q1,Q2),(Q2,Q3),...,(Qn1,Qn)

而排列 P P P相当于图中不存在边集 ( 1 , P 1 ) , ( 2 , P 2 ) . . . ( n , P n ) (1,P_1),(2,P_2)...(n,P_n) (1,P1),(2,P2)...(n,Pn)

也就是我们要对于 1 1 1~ n n n的完全图并去掉了 ( 1 , P 1 ) , ( 2 , P 2 ) , . . . ( n , P n ) (1,P_1),(2,P_2),...(n,P_n) (1,P1),(2,P2),...(n,Pn)边集后的图,找到一条曼哈顿路径,代表 Q Q Q的一种排列。求这样的曼哈顿路径数量

考虑如何求数量。可以容斥

如果没有去掉边的话,即固定选了0条边,有 n ! n! n!种选法。 ( n 0 ) × n ! \binom{n}{0}\times n! (0n)×n!

固定选了1条边,相当于捆绑法,两个点贴在一起,有 ( n − 1 ) ! (n-1)! (n1)!种选法。 ( n 1 ) × ( n − 1 ) ! \binom{n}{1}\times (n-1)! (1n)×(n1)!

固定选了2条边,。。不对劲了。要保证选的边是曼哈顿路径上的边才行。。。

寄了。

我们还需要考虑“容斥固定选的边”是否可行的情况。显然 ( 2 , 2 ) (2,2) (2,2)这种边集以及 ( 1 , 2 ) , ( 2 , 1 ) (1,2),(2,1) (1,2),(2,1)这种边集是不可能选到的

由于 P P P是一个排列,相当于若干个环,而曼哈顿路径就是要保证无环的情况。我们可以考虑看每个环中选边的情况!这样就能保证不形成环!

对于 P P P中一个点(边)数为 k k k的环。我们可以选 0 0 0 k − 1 k-1 k1条边。因为选 k k k条边就形成环了。每个环都这么选择,显然是互不影响,互相独立的。

对于单独一个环:选了 i i i条边的方案数是 ( k i ) \binom{k}{i} (ik)

我们建成生成函数 f ( x ) = ∑ i = 0 k − 1 ( k i ) x i f(x)=\sum\limits _{i=0}^{k-1}\binom{k}{i}x^i f(x)=i=0k1(ik)xi

多个环的情况相当于 F ( x ) = ∏ f ( x ) F(x)=\prod f(x) F(x)=f(x),可以分治NTT解决

对于 [ x i ] [x^i] [xi]的系数,表示已经固定选了 i i i条边的方式。我们排列方案还没考虑清楚。我们每连接一条边,相当于把两个点集合并成一个全新的点集。如没有连边时为 n ! n! n!,连接一条边时为 ( n − 1 ) ! (n-1)! (n1)!,连接两条边时为 ( n − 2 ) ! (n-2)! (n2)!……所以已经固定选了 i i i条边,排列方案数为 ( n − i ) ! (n-i)! (ni)!

答案即为 ∑ i = 0 n ( − 1 ) i ( n − i ) ! [ x i ] F ( x ) \sum\limits _{i=0}^n (-1)^i(n-i)![x^i]F(x) i=0n(1)i(ni)![xi]F(x)

#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
// #define debug(x) cout<<"[debug]"#x<<"="<<x<<endl
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
const double eps=1e-8;
const int INF=0x3f3f3f3f;

const int N=100005,M=150005;
int p[N];
bool st[N];
typedef vector<ll> Poly;
vector<Poly> f;
const int mod=998244353,G=3,Gi=332748118;
int bit,tot;
int rev[M];
ll qmi(ll a,ll b,ll p)
{
    ll res=1;
    while(b)
    {
        if(b&1)
        res=res*a%p;

        a=a*a%p;
        b>>=1;
    }
    return res;
}
void NTT(Poly &a,int inv)
{
    for(int i=0;i<tot;i++)
        if(i<rev[i]) swap(a[i],a[rev[i]]);
 
    for(int mid=1;mid<tot;mid*=2)
    {
        ll w1=qmi(inv==1?G:Gi,(mod-1)/(2*mid),mod);
        for(int i=0;i<tot;i+=mid*2)
        {
            ll wk=1;
            for(int j=0;j<mid;j++,wk=wk*w1%mod)
            {
                ll x=a[i+j];
                ll y=wk*a[i+j+mid]%mod;
                a[i+j]=(x+y)%mod;
                a[i+j+mid]=(x-y+mod)%mod;
            }
        }
    }
 
    if(inv==-1)//就不用后面除了
    {
        ll intot=qmi(tot,mod-2,mod);
        for(int i=0;i<tot;i++)
        {
            a[i]=a[i]*intot%mod;
        }
    }
}
Poly mul(Poly a,Poly b)//deg是系数的数量,所以有0~deg-1次项
{
    int deg=(int)a.size()+b.size()-1;
    bit=0;
    while((1<<bit)<deg) bit++;//至少要系数的数量
    tot=1<<bit; //系数项为0~tot-1
    for(int i=0;i<tot;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));

    Poly c(tot);
    a.resize(tot),b.resize(tot);
    NTT(a,1),NTT(b,1);
    for(int i=0;i<tot;i++) c[i]=a[i]*b[i]%mod;
    NTT(c,-1);
    c.resize(deg);
    return c;
}
int fact[N],infact[N];
void init(int n)
{
    fact[0]=1;
    for(int i=1;i<=n;i++)
    {
        fact[i]=1ll*fact[i-1]*i%mod;
    }

    infact[n]=qmi(fact[n],mod-2,mod);
    for(int i=n-1;i>=0;i--)
    {
        infact[i]=1ll*infact[i+1]*(i+1)%mod;
    }
}
int C(int n,int m)
{
    // if(n<m) return 0;
    return 1ll*fact[n]*infact[m]%mod*infact[n-m]%mod;
}
Poly cdq_ntt(int l,int r)
{
    if(l==r) return f[l];
    int mid=l+r>>1;
    return mul(cdq_ntt(l,mid),cdq_ntt(mid+1,r));
}
int main()
{
    init(N-1);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&p[i]);

    vector<int> h;//统计每个环的点(边)数
    for(int i=1;i<=n;i++)
    {
        if(st[i]) continue;

        int jishu=1;
        st[i]=true;
        int now=p[i];
        while(now!=i)
        {
            jishu++;
            st[now]=true;
            now=p[now];
        }
        h.push_back(jishu);
    }

    f.resize(h.size());
    for(int i=0;i<f.size();i++)
    {
        f[i].resize(h[i]);
        for(int j=0;j<h[i];j++)
        {
            f[i][j]=C(h[i],j);
        }
    }

    Poly F=cdq_ntt(0,f.size()-1);

    ll res=0;
    for(int i=0;i<F.size();i++)
    {
        ll ans=fact[n-i]*F[i]%mod;
        if(i%2) res=(res-ans+mod)%mod;
        else res=(res+ans)%mod;
    }
    printf("%lld\n",res);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值