【XSY2744】信仰圣光 分治FFT 多项式exp 容斥原理
题目描述
有一个\(n\)个元素的置换,你要选择\(k\)个元素,问有多少种方案满足:对于每个轮换,你都选择了其中的一个元素。
对\(998244353\)取模。
\(k\leq n\leq 152501\)
题解
吐槽
为什么一道FFT题要把\(n\)设为\(150000\)?
解法一
先把轮换拆出来。
直接DP。
设\(f_{i,j}\)为前\(i\)个轮换选择了\(j\)个元素,且每个轮换都选择了至少一个元素的方案数。
\]
时间复杂度为\(O(n^2)\),因为枚举的是第\(i\)组和前\(i-1\)组的配对,而任意两个元素之间最多被配对一次。
可以分治FFT做到\(O(n\log^2 n)\)
解法二
考虑容斥。
设\(m\)为轮换个数。
枚举有哪些轮换\(S\)中可能有被选中的元素,容斥系数就是\({(-1)}^{m-|S|}\)(\(sum\)为这些轮换的大小总和):
或者枚举哪些轮换\(S\)中没有被选中的元素,容斥系数就是\({(-1)}^{|S|}\):
s&=\sum_{S}{(-1)}^{m-|S|}\binom{sum}{k}\\
s&=\sum_{S}{(-1)}^{|S|}\binom{n-sum}{k}\\
\end{align}
\]
现在我们要对于每一个\(i\),计算\(f_i=\sum_{S,sum=i}{(-1)}^{|S|}\)。
构造生成函数\(A_i(x)=1-x^{a_i}\),那么\(F(x)=\prod_{i=1}^mA_i(x)\)。
直接做还是\(O(n\log^2n)\)的。我们需要一些优化。
F(x)&=\prod_{i=1}^m1-x^{a_i}\\
\ln(F(x))&=\sum_{i=1}^n\ln(1-x^{a_i})\\
\ln(F(x))&=\sum_{i=1}^n\sum_{j=a_i}-\frac{x^{ja_i}}{j}
\end{align}
\]
那么可以在\(O(n\log n)\)内算出\(\ln(F(x))\),然后\(\exp\)一下。
时间复杂度:\(O(n\log n)\)
由于常数过大,所以要用下面那条式子(因为只用计算到\(x^{n-k}\))。
解法一
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<utility>
#include<iostream>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
void open(const char *s)
{
#ifndef ONLINE_JUDGE
char str[100];
sprintf(str,"%s.in",s);
freopen(str,"r",stdin);
sprintf(str,"%s.out",s);
freopen(str,"w",stdout);
#endif
}
int rd()
{
int s=0,c;
while((c=getchar())<'0'||c>'9');
s=c-'0';
while((c=getchar())>='0'&&c<='9')
s=s*10+c-'0';
return s;
}
const int p=998244353;
const int g=3;
ll fp(ll a,ll b)
{
ll s=1;
for(;b;b>>=1,a=a*a%p)
if(b&1)
s=s*a%p;
return s;
}
ll inv[200010];
ll fac[200010];
ll ifac[200010];
int a[200010];
int n,m,k;
int c[200010];
int b[200010];
ll getc(int x,int y)
{
return fac[x]*ifac[y]%p*ifac[x-y]%p;
}
ll *f[500010];
int len[500010];
int cnt;
int a1[600010];
int a2[600010];
int rev[600010];
void ntt(int *a,int n,int t)
{
for(int i=1;i<n;i++)
{
rev[i]=(rev[i>>1]>>1)|(i&1?n>>1:0);
if(i>rev[i])
swap(a[i],a[rev[i]]);
}
for(int i=2;i<=n;i<<=1)
{
int wn=fp(g,(p-1)/i*(t==1?1:i-1));
for(int j=0;j<n;j+=i)
{
int w=1;
for(int k=j;k<j+i/2;k++)
{
int u=a[k];
int v=(ll)a[k+i/2]*w%p;
a[k]=(u+v)%p;
a[k+i/2]=(u-v)%p;
w=(ll)w*wn%p;
}
}
}
if(t==-1)
{
int inv=fp(n,p-2);
for(int i=0;i<n;i++)
a[i]=(ll)a[i]*inv%p;
}
}
void solve(int &now,int l,int r)
{
now=++cnt;
if(l==r)
{
len[now]=min(a[l],k);
f[now]=new ll[len[now]+1];
f[now][0]=0;
for(int i=1;i<=len[now];i++)
f[now][i]=ifac[i]*ifac[a[l]-i]%p;
return;
}
int ls,rs,mid=(l+r)>>1;
solve(ls,l,mid);
solve(rs,mid+1,r);
len[now]=min(len[ls]+len[rs],k);
f[now]=new ll[len[now]+1];
int v=1;
while(v<=len[ls]+len[rs])
v<<=1;
for(int i=0;i<v;i++)
a1[i]=(i<=len[ls]?f[ls][i]:0);
for(int i=0;i<v;i++)
a2[i]=(i<=len[rs]?f[rs][i]:0);
ntt(a1,v,1);
ntt(a2,v,1);
for(int i=0;i<v;i++)
a1[i]=(ll)a1[i]*a2[i]%p;
ntt(a1,v,-1);
for(int i=0;i<=len[now];i++)
f[now][i]=a1[i];
delete [] f[ls];
delete [] f[rs];
}
void solve()
{
// scanf("%d%d",&n,&k);
n=rd();
k=rd();
for(int i=1;i<=n;i++)
c[i]=rd();
// scanf("%d",&c[i]);
if(k==n)
{
printf("1\n");
return;
}
m=0;
cnt=0;
memset(b,0,sizeof b);
memset(a,0,sizeof a);
for(int i=1;i<=n;i++)
if(!b[i])
{
m++;
for(int j=i;!b[j];j=c[j])
{
b[j]=1;
a[m]++;
}
}
if(k<m)
{
printf("0\n");
return;
}
int rt;
solve(rt,1,m);
ll ans=f[rt][k];
ans=ans*fp(getc(n,k),p-2)%p;
for(int i=1;i<=m;i++)
ans=ans*fac[a[i]]%p;
ans=(ans+p)%p;
printf("%lld\n",ans);
}
int main()
{
open("a");
inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
for(int i=2;i<=200000;i++)
{
inv[i]=-p/i*inv[p%i]%p;
fac[i]=fac[i-1]*i%p;
ifac[i]=ifac[i-1]*inv[i]%p;
}
int t;
// scanf("%d",&t);
t=rd();
while(t--)
solve();
return 0;
}
解法二
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<utility>
#include<iostream>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int rd()
{
int s=0,c;
while((c=getchar())<'0'||c>'9');
s=c-'0';
while((c=getchar())>='0'&&c<='9')
s=s*10+c-'0';
return s;
}
void open(const char *s)
{
#ifndef ONLINE_JUDGE
char str[100];
sprintf(str,"%s.in",s);
freopen(str,"r",stdin);
sprintf(str,"%s.out",s);
freopen(str,"w",stdout);
#endif
}
const int p=998244353;
const int g=3;
ll fp(ll a,ll b)
{
ll s=1;
for(;b;b>>=1,a=a*a%p)
if(b&1)
s=s*a%p;
return s;
}
ll inv[300010];
ll fac[300010];
ll ifac[300010];
namespace ntt
{
int rev[600000];
void ntt(int *a,int n,int t)
{
for(int i=1;i<n;i++)
{
rev[i]=(rev[i>>1]>>1)|(i&1?n>>1:0);
if(i>rev[i])
swap(a[i],a[rev[i]]);
}
for(int i=2;i<=n;i<<=1)
{
int wn=fp(g,(p-1)/i*(t==1?1:i-1));
for(int j=0;j<n;j+=i)
{
int w=1;
for(int k=j;k<j+i/2;k++)
{
int u=a[k];
int v=(ll)a[k+i/2]*w%p;
a[k]=(u+v)%p;
a[k+i/2]=(u-v)%p;
w=(ll)w*wn%p;
}
}
}
if(t==-1)
{
int inv=fp(n,p-2);
for(int i=0;i<n;i++)
a[i]=(ll)a[i]*inv%p;
}
}
void getinv(int *a,int *b,int n)
{
if(n==1)
{
b[0]=fp(a[0],p-2);
return;
}
getinv(a,b,n>>1);
static int a1[600000],a2[600000];
for(int i=0;i<n;i++)
a1[i]=a[i];
for(int i=n;i<n<<1;i++)
a1[i]=0;
for(int i=0;i<n>>1;i++)
a2[i]=b[i];
for(int i=n>>1;i<n<<1;i++)
a2[i]=0;
ntt(a1,n<<1,1);
ntt(a2,n<<1,1);
for(int i=0;i<n<<1;i++)
a1[i]=a2[i]*(2-(ll)a1[i]*a2[i]%p)%p;
ntt(a1,n<<1,-1);
for(int i=0;i<n;i++)
b[i]=a1[i];
}
void getln(int *a,int *b,int n)
{
static int a1[600000],a2[600000];
for(int i=1;i<n;i++)
a1[i-1]=(ll)a[i]*i%p;
a1[n-1]=0;
getinv(a,a2,n);
for(int i=n;i<n<<1;i++)
a1[i]=a2[i]=0;
ntt(a1,n<<1,1);
ntt(a2,n<<1,1);
for(int i=0;i<n<<1;i++)
a1[i]=(ll)a1[i]*a2[i]%p;
ntt(a1,n<<1,-1);
for(int i=1;i<n;i++)
b[i]=(ll)a1[i-1]*inv[i]%p;
b[0]=0;
}
void getexp(int *a,int *b,int n)
{
if(n==1)
{
b[0]=1;
return;
}
getexp(a,b,n>>1);
static int a1[600000],a2[600000],a3[600000];
for(int i=n>>1;i<n;i++)
b[i]=0;
getln(b,a3,n);
for(int i=0;i<n>>1;i++)
{
a1[i]=b[i];
a2[i]=(a[i+(n>>1)]-a3[i+(n>>1)])%p;
}
for(int i=n>>1;i<n;i++)
a1[i]=a2[i]=0;
ntt(a1,n,1);
ntt(a2,n,1);
for(int i=0;i<n;i++)
a1[i]=(ll)a1[i]*a2[i]%p;
ntt(a1,n,-1);
for(int i=0;i<n>>1;i++)
b[i+(n>>1)]=a1[i];
}
}
int a[200010];
int n,m,k;
int c[200010];
int b[200010];
int cnt;
ll ans;
int d[300010];
int s[300010];
int f[300010];
ll getc(int x,int y)
{
if(y>x||y<0)
return 0;
return fac[x]*ifac[y]%p*ifac[x-y]%p;
}
void dfs(int x,int y,int v)
{
if(x>m)
{
ans=(ans+v*getc(y,k))%p;
return;
}
dfs(x+1,y,v);
dfs(x+1,y+a[x],-v);
}
void solve()
{
// scanf("%d%d",&n,&k);
n=rd();
k=rd();
for(int i=1;i<=n;i++)
c[i]=rd();
// scanf("%d",&c[i]);
if(k==n)
{
printf("1\n");
return;
}
m=0;
cnt=0;
memset(b,0,sizeof b);
memset(a,0,sizeof a);
for(int i=1;i<=n;i++)
if(!b[i])
{
m++;
for(int j=i;!b[j];j=c[j])
{
b[j]=1;
a[m]++;
}
}
if(k<m)
{
printf("0\n");
return;
}
memset(d,0,sizeof d);
memset(s,0,sizeof s);
for(int i=1;i<=m;i++)
d[a[i]]++;
for(int i=1;i<=n;i++)
if(d[i])
for(int j=1;i*j<=n;j++)
s[i*j]=(s[i*j]-inv[j]*d[i])%p;
int l=1;
while(l<=n-k)
l<<=1;
s[0]=1;
ntt::getexp(s,f,l);
ans=0;
for(int i=0;i<=n-k;i++)
ans=(ans+f[i]*getc(n-i,k))%p;
// ans=(ans+f[i]*getc(i,k))%p;
ans=ans*fp(getc(n,k),p-2)%p;
// if(m&1)
// ans=-ans;
ans=(ans+p)%p;
printf("%lld\n",ans);
}
int main()
{
open("a");
inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
for(int i=2;i<=300000;i++)
{
inv[i]=-p/i*inv[p%i]%p;
fac[i]=fac[i-1]*i%p;
ifac[i]=ifac[i-1]*inv[i]%p;
}
int t;
// scanf("%d",&t);
t=rd();
while(t--)
solve();
return 0;
}
【XSY2744】信仰圣光 分治FFT 多项式exp 容斥原理的更多相关文章
- 【XSY2887】【GDOI2018】小学生图论题 分治FFT 多项式exp
题目描述 在一个 \(n\) 个点的有向图中,编号从 \(1\) 到 \(n\),任意两个点之间都有且仅有一条有向边.现在已知一些单向的简单路径(路径上任意两点各不相同),例如 \(2\to 4\to ...
- BZOJ5119 生成树计数(prufer+生成函数+分治FFT+多项式exp)
https://www.luogu.org/problemnew/solution/P4002 神树的题解写的很清楚了.稍微补充: 1.[x^i]ln(A(ax))=a^i[x^i]ln(A(x)), ...
- 【BZOJ3456】轩辕朗的城市规划 无向连通图计数 CDQ分治 FFT 多项式求逆 多项式ln
题解 分治FFT 设\(f_i\)为\(i\)个点组成的无向图个数,\(g_i\)为\(i\)个点组成的无向连通图个数 经过简单的推导(枚举\(1\)所在的连通块大小),有: \[ f_i=2^{\f ...
- hdu 5730 Shell Necklace [分治fft | 多项式求逆]
hdu 5730 Shell Necklace 题意:求递推式\(f_n = \sum_{i=1}^n a_i f_{n-i}\),模313 多么优秀的模板题 可以用分治fft,也可以多项式求逆 分治 ...
- bzoj 3456 城市规划——分治FFT / 多项式求逆 / 多项式求ln
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3456 分治FFT: 设 dp[ i ] 表示 i 个点时连通的方案数. 考虑算补集:连通的方 ...
- 洛谷 4721 【模板】分治 FFT——分治FFT / 多项式求逆
题目:https://www.luogu.org/problemnew/show/P4721 分治FFT:https://www.cnblogs.com/bztMinamoto/p/9749557.h ...
- bzoj 3456 城市规划 —— 分治FFT / 多项式求逆 / 指数型生成函数(多项式求ln)
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3456 首先考虑DP做法,正难则反,考虑所有情况减去不连通的情况: 而不连通的情况就是那个经典 ...
- 洛谷 P4721 [模板]分治FFT —— 分治FFT / 多项式求逆
题目:https://www.luogu.org/problemnew/show/P4721 分治做法,考虑左边对右边的贡献即可: 注意最大用到的 a 的项也不过是 a[r-l] ,所以 NTT 可以 ...
- [模板] 多项式: 乘法/求逆/分治fft/微积分/ln/exp/幂
多项式 代码 const int nsz=(int)4e5+50; const ll nmod=998244353,g=3,ginv=332748118ll; //basic math ll qp(l ...
随机推荐
- 小谈UAT(验收测试)
验收测试人员的测试任务: 1.验收人员是提出需求的人员,所以对需求最为熟悉,最主要测试功能的遗漏或者多余2.系统测试人员重点在测试功能的正确性和非功能的符合性,当然也希望验收人员测试功能的正确性3.因 ...
- MyEclipse和eclipse的区别
对于新手来说,MyEclipse和eclipse来说的区别可能就是MyEclipse比eclipse多了my,MyEclipse主要为JavaEE开发,而Eclipse主要为Java开发..那么MyE ...
- PS滤镜给城市夜空照片添加满天星
原图 一.新建空白图层. 二.填充黑色(编辑→填充). 三.转换为智能对象. 四.添加杂色(滤镜→杂色→添加杂色). 五.使用高斯模糊(滤镜→模糊→高斯模糊). 六.如果你想再次修改模糊效果,可双击该 ...
- Django之路由分发反向解析
Django路由分发|反向解析 当一个Django中有多个app时,路由会有很多,将这些路由都写在与项目同名的文件夹下就会显得很多,很乱.并且在协同开发的时候容易出现相同的命名,当项目合并后就会出现路 ...
- rest framwork 小试身手
models.py from django.db import models class Course(models.Model): """ 课程表 "&quo ...
- JDK8 的FullGC 之 metaspace
JDK8 的FullGC 之 metaspace - 简书https://www.jianshu.com/p/1a0b4bf8d498
- telnet总结
telnet是经常使用的客户端链接工具,总结一下常用的telnet的使用方法 1) 连接 telnet //链接swoole 2)退出当前连接 ctrl + ] 回车 3)查看常用的一些命令 ? 回车 ...
- Html5使用canvas作图线宽很粗
自己使用canvas画图是碰到的问题,在这里记录一下.我把lineWidth设置为1,但是很粗,而且发虚.代码如下: <script type="text/javascript&quo ...
- 重构客户注册-基于ActiveMQ实现短信验证码生产者
重构目标:将bos_fore项目中的CustomerAction作为短信消息生产者,将消息发给ActiveMQ,创建一个单独的SMS项目,作为短信息的消费者,从ActiveMQ获取短信消息,调用第三方 ...
- python爬虫之MongoDB测试环境安装
一. 下载 从http://www.mongodb.org/downloads地址中下载:mongodb-linux-x86_64-2.4.11.tar 二. 安装 1>设置mongoDB ...