洛谷 P4233 射命丸文的笔记 ntt

题目背景

(七)再见,地底世界的朋友们

在地灵殿住了许多天了呢。

这些日子里,觉分享了很多旧地狱的故事。

此次地底旅行,可以说是非常充实了。

虽然仍旧有些不舍,不过人类总是要见太阳的,再说这样麻烦觉姐姐招待我们也有些过意不去呢。

那么,和觉,恋,阿燐,阿空,以及其他宠物们说再见吧。

……

旧地狱的街市,依旧飘着雪。

已经能看到溶洞了。

环境又变得幽闭起来。

诶,前面不是山女吗?

“啊,你们要回地面了吗,玩的怎样?”

“很开心呢,对了,剩下的问题已经解决了”

我们向山女解释了从荷取那里听到的方法。

“谢谢!”

“不客气,那么再见了~”

世界一片白茫茫的…

阳光是那么的刺眼,以至于几分钟后我们才能睁开眼睛看清楚地面的景色。

沿着魔法森林中的小路向神社走去,这次的旅行也在我们的脚步声中走向了尾声。

前方的地面上忽然出现了一页破损的笔记。

捡起来一看,发现是从文文的笔记本上脱落下来的。

射命丸文,作为(不靠谱的)新闻记者,观察到最近地灵殿里的宠物们偶尔会互相打架,于是将每场决斗的胜负关系写在了她的笔记本上。刚刚捡起来的这页笔记,上面就记录着几场“单循环赛”。

每场循环赛被抽象成一张竞赛图,其中顶点代表参加循环赛的宠物,从顶点 u u 指向顶点v的边代表在一场比赛中宠物 u u 战胜了宠物v

观察到这页笔记上所有的竞赛图中都至少存在一条经过所有顶点的回路,我们猜想文文只会记录这样的循环赛。

可能是因为文文不清楚宠物们谁能打过谁,于是在那页笔记的最下面留下了一个这样的问题…

(见题目描述)

这最后一个问题,就留给你来解决啦。

博丽大结界,已经在我们身后了。

希望这次地底旅行,能给你留下美好的记忆~

(全文完)

题目描述

如果一个竞赛图含有哈密顿回路,则称这张竞赛图为值得记录的

从所有含有nn个顶点(顶点互不相同)的,值得记录的竞赛图中等概率随机选取一个

求选取的竞赛图中哈密顿回路数量的期望值

由于答案可能过大/丢失精度,只需要输出答案除以 998244353 998244353 的余数

即:设答案为 qp q p ,则你需要输出一个整数 x x ,满足pxq mod 998244353 0x<998244353 0 ⩽ x < 998244353 ,可以证明恰好存在一个这样的 x x
若不存在这样的竞赛图输出-1

输入输出格式

输入格式:
一行一个正整数nn
输出格式:
n行,第 i i 行一个数字,代表输入为i时的答案

输入输出样例

输入样例#1:
4
输出样例#1:
1
-1
1
1
说明

样例解释:

n=1 n = 1 时只有一种满足条件的竞赛图,就是一个点

n=2 n = 2 时竞赛图中只有一条边,不能形成哈密顿回路

n=3 n = 3 时有两种满足条件的竞赛图,分别为1->2->3->1和1->3->2->1,都只有1条哈密顿回路,随机取出后期望值为1

n=4 n = 4 时有很多种满足条件的的竞赛图,这里写不下了,但是所有满足条件的竞赛图都是同构的,所以随机取出后期望值为1

数据范围:

测试点1~3中 n7 n ⩽ 7
测试点4~6中 n10 n ⩽ 10
测试点7~10中 n1000 n ⩽ 1000
测试点11~16中 n10000 n ⩽ 10000
测试点17~25中 n100000 n ⩽ 100000
数据有梯度,每个测试点4分

为防止卡常,最后两个点开2s时限

分析:
考虑每一条哈密顿回路,显然在回路上的边已经定向,而其他的边都有两种选择,即为 (n1)!2n(n1)2n ( n − 1 ) ! ∗ 2 n ∗ ( n − 1 ) 2 − n
于是就要求有哈密顿回路的路径数了,其实就是有强连通分量的竞赛图个数。
f(n)=2n(n1)2 f ( n ) = 2 n ∗ ( n − 1 ) 2 g(n) g ( n ) n n 个点有强连通分量的竞赛图个数,有

g(n)=f(n)i=1n1g(i)f(ni)(ni)


g(n)n!=f(n)n!i=1n1g(i)i!f(ni)(ni)! g ( n ) n ! = f ( n ) n ! − ∑ i = 1 n − 1 g ( i ) i ! ∗ f ( n − i ) ( n − i ) !

F(n)=f(n)n! F ( n ) = f ( n ) n ! G(n)=g(n)n! G ( n ) = g ( n ) n ! ,即 G=FFG G = F − F ∗ G ,即 G=FF+1 G = F F + 1

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=3e5+7;
const LL mod=998244353;
const LL G=3;

using namespace std;

LL f[maxn],g[maxn],inv[maxn],c[maxn],x[maxn],y[maxn],w[maxn],r[maxn],jc[maxn];
LL n,len,ans;

LL power(LL x,LL y)
{
    if (y==0) return 1;
    if (y==1) return x;
    LL c=power(x,y/2);
    c=(c*c)%mod;
    if (y%2) c=(c*x)%mod;
    return c;
}

void ntt(LL *a,LL f)
{
    for (LL i=0;i<len;i++)
    {
        if (i<r[i]) swap(a[i],a[r[i]]);
    }
    w[0]=1;
    for (LL i=2;i<=len;i*=2)
    {
        LL wn;
        if (f==1) wn=power(G,(mod-1)/i);
             else wn=power(G,(mod-1)-(mod-1)/i);
        for (LL j=i/2;j>=0;j-=2) w[j]=w[j/2];
        for (LL j=1;j<i/2;j+=2) w[j]=(w[j-1]*wn)%mod;
        for (LL j=0;j<len;j+=i)
        {
            for (LL k=0;k<i/2;k++)
            {
                LL u=a[j+k],v=a[j+k+i/2]*w[k]%mod;
                a[j+k]=(u+v)%mod;
                a[j+k+i/2]=(u+mod-v)%mod;
            }
        }
    }
    if (f==-1)
    {
        LL inv=power(len,mod-2);
        for (LL i=0;i<len;i++) a[i]=(a[i]*inv)%mod;
    }
}

void NTT(LL *a,LL *b,LL *c,LL n,LL m)
{
    len=1;
    while (len<=n+m) len<<=1;
    LL k=trunc(log(len+0.5)/log(2));
    for (LL i=0;i<len;i++)
    {
        r[i]=(r[i>>1]>>1)|((i&1)<<(k-1));
    }
    for (LL i=0;i<len;i++)
    {
        if (i<n) x[i]=a[i]; 
            else x[i]=0;
        if (i<m) y[i]=b[i];
            else y[i]=0;
    }
    ntt(x,1); ntt(y,1);
    for (LL i=0;i<len;i++) c[i]=x[i]*y[i]%mod;
    ntt(c,-1);
}

void getinv(LL *f,LL *g,LL k)
{
    if (k==1)
    {
        g[0]=power(f[0],mod-2);
        return;
    }
    LL mid=(k+1)/2;
    getinv(f,g,mid);
    NTT(f,g,c,n,mid);
    c[0]=(2+mod-c[0])%mod;
    for (LL i=1;i<n+mid;i++) c[i]=(mod-c[i])%mod;
    NTT(c,g,g,n+mid,mid);
}

int main()
{
    scanf("%lld",&n);
    jc[0]=1;    
    for (LL i=1;i<=n;i++)
    {
        jc[i]=(jc[i-1]*i)%mod;
        f[i]=g[i]=power(2,i*(i-1)/2)*power(jc[i],mod-2)%mod;
    }
    g[0]=1;
    n++;
    getinv(g,inv,n);        
    NTT(f,inv,f,n,n);   
    printf("1\n");
    for (LL i=2;i<n;i++)
    {
        f[i]=f[i]*jc[i]%mod;
        if (!f[i]) printf("-1\n");
        else
        {
            ans=jc[i-1]*power(2,i*(i-1)/2-i)%mod*power(f[i],mod-2)%mod;
            printf("%lld\n",ans);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值