题目大意

  有\(n\)种颜色的球,第\(i\)种有\(a_i\)个。设\(m=\sum a_i\)。你要把这\(m\)个小球排成一排。有\(q\)个询问,每次给你一个\(x\),问你有多少种方案使得相邻的小球同色的对数为\(x\)。

  \(n\leq 10000,m\leq 200000\)

题解

  我们考虑把这些小球分段,每段内所有小球颜色相同,但相邻两段的小球颜色可以相同。

  设第\(i\)种颜色有\(b_i\)段,那么分\(j\)段的方案数是\(\frac{(\sum b_i)!}{\sum(bi!)}=\frac{j!}{\sum(bi!)}\)

  那么先DP,设\(f_{i,j}\)为前\(i\)种颜色,分了\(j\)段的方案数\(\div b_i!\)。显然枚举第\(i\)中颜色分\(k\)段得

\[f_{i,j}+=f_{i-1,j-k}\times \binom{a_i-1}{k-1}\times\frac{1}{k!}
\]

  那个组合数是插板法得到的。

  这个DP的时间复杂度是\(O(m^2)\)(因为枚举第\(i\)种颜色时\(k=1\ldots a_i,j=1\ldots s_i\)(\(s\)为\(a\)的前缀和))

  然后这个东西可以分治FFT优化到\(O(m\log m\log n)\)

  这样我们得到了分成\(i\)段的方案数\(g_i=f_{n,i}\times i!\),但相邻两段可能颜色相同。我们还要减掉这种情况。

  就是对于一种实际上分成 \(j\) 段的方案,它在分成 \(i\) 段的方案数中会被计算 \(\binom{m-j}{m-i}\) 次(就是在 \(m-j\) 个间隔中取 \(m-i\) 个)。

  答案 \(ans_i=g_i-\sum_{j<i}ans_j\binom{m-j}{i-j}\)

  可以简单暴力的通过分治FFT优化到\(O(m\log^2 m)\)。但有更好的做法。

  考虑容斥。其实总的\(g_j\)对\(ans_i\)的贡献就是\({(-1)}^{i-j}\binom{m-j}{i-j}\)。直接FFT一次就可以得到答案。

\[\begin{align}
ans_{k->i}&=\sum_{j=k}^{i-1}{(-1)^{j-k}}\binom{m-k}{j-k}\binom{m-j}{i-j}\\
&=\sum_{j=k}^{i-1}{(-1)^{j-k}}\frac{(m-k)!(m-j)!}{(j-k)!(m-j)!(i-j)!(m-i)!}\\
&=\sum_{j=k}^{i-1}{(-1)^{j-k}}\frac{(m-k)!}{(j-k)!(i-j)!(m-i)!}\\
&=\frac{(m-k)!}{(m-i)!(i-k)!}\sum_{j=k}^{i-1}{(-1)^{j-k}}\frac{(i-k)!}{(i-j)!(j-k)!}\\
&=\binom{m-k}{i-k}\sum_{j=k}^{i-1}{(-1)^{j-k}}\binom{i-k}{j-k}\\
&=\binom{m-k}{i-k}{(-1)}^{i-k}
\end{align}
\]

  那么相邻的小球同色的对数为\(x\)的答案就是\(ans_{m-x}\)。

  时间复杂度:\(O(m\log m\log n+q)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<cmath>
#include<functional>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void sort(int &a,int &b)
{
if(a>b)
swap(a,b);
}
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');
do
{
s=s*10+c-'0';
}
while((c=getchar())>='0'&&c<='9');
return s;
}
void put(int x)
{
if(!x)
{
putchar('0');
return;
}
static int c[20];
int t=0;
while(x)
{
c[++t]=x%10;
x/=10;
}
while(t)
putchar(c[t--]+'0');
}
int upmin(int &a,int b)
{
if(b<a)
{
a=b;
return 1;
}
return 0;
}
int upmax(int &a,int b)
{
if(b>a)
{
a=b;
return 1;
}
return 0;
}
const int p=998244353;
int fp(int a,int b)
{
int s=1;
for(;b;b>>=1,a=1ll*a*a%p)
if(b&1)
s=1ll*s*a%p;
return s;
}
int inv[600010];
int fac[600010];
int ifac[600010];
namespace ntt
{
const int g=3;
int rev[600010];
int w1[600010];
int w2[600010];
int n;
void init(int m)
{
n=1;
while(n<=m)
n<<=1;
int i;
rev[0]=0;
for(i=1;i<n;i++)
rev[i]=(rev[i>>1]>>1)|(i&1?n>>1:0);
for(i=1;i<=n;i<<=1)
{
w1[i]=fp(g,(p-1)/i);
w2[i]=fp(w1[i],p-2);
}
}
void ntt(int *a,int t)
{
int i,j,k;
int u,v,w,wn;
for(i=0;i<n;i++)
if(rev[i]<i)
swap(a[i],a[rev[i]]);
for(i=2;i<=n;i<<=1)
{
wn=(t==1?w1[i]:w2[i]);
for(j=0;j<n;j+=i)
{
w=1;
for(k=j;k<j+i/2;k++)
{
u=a[k];
v=1ll*a[k+i/2]*w%p;
a[k]=(u+v)%p;
a[k+i/2]=(u-v)%p;
w=1ll*w*wn%p;
}
}
}
if(t==-1)
{
int inv=fp(n,p-2);
for(i=0;i<n;i++)
a[i]=1ll*a[i]*inv%p;
}
}
};
int g[600010];
int h[600010];
int ans[600010];
int a[600010];
int s[600010];
int n,m;
void add(int &a,int b)
{
a=(a+b)%p;
}
typedef vector<int> vec;
vec mul(vec &a,vec &b)
{
static int c[600010],d[600010];
int n1=a.size()-1;
int n2=b.size()-1;
int m=n1+n2+1;
ntt::init(m);
int i;
for(i=0;i<=n1;i++)
c[i]=a[i];
for(i=n1+1;i<ntt::n;i++)
c[i]=0;
for(i=0;i<=n2;i++)
d[i]=b[i];
for(i=n2+1;i<ntt::n;i++)
d[i]=0;
ntt::ntt(c,1);
ntt::ntt(d,1);
for(i=0;i<ntt::n;i++)
c[i]=1ll*c[i]*d[i]%p;
ntt::ntt(c,-1);
vec s(n1+n2+1);
for(i=1;i<=n1+n2;i++)
s[i]=c[i];
return s;
}
vec solve(int l,int r)
{
if(l==r)
{
vec s(a[l]+1);
int i;
for(i=1;i<=a[l];i++)
s[i]=1ll*ifac[i-1]*ifac[i]%p*ifac[a[l]-i]%p;
return s;
}
int mid=(l+r)>>1;
vec s1=solve(l,mid);
vec s2=solve(mid+1,r);
return mul(s1,s2);
}
int c[600010];
int d[600010];
priority_queue<pii,vector<pii>,greater<pii> > q;
void gao()
{
int i;
c[0]=0;
for(i=1;i<=m;i++)
c[i]=g[i];
for(i=0;i<=m;i++)
{
d[i]=ifac[i];
if(i&1)
d[i]=-d[i];
}
ntt::init(2*m);
for(i=m+1;i<ntt::n;i++)
c[i]=d[i]=0;
ntt::ntt(c,1);
ntt::ntt(d,1);
for(i=0;i<ntt::n;i++)
c[i]=1ll*c[i]*d[i]%p;
ntt::ntt(c,-1);
for(i=1;i<=m;i++)
g[i]=c[i];
}
int t=0;
vec f[20010];
int main()
{
open("c");
scanf("%d",&n);
int i;
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];
}
m=s[n];
inv[0]=inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
for(i=2;i<=m;i++)
{
inv[i]=-1ll*p/i*inv[p%i]%p;
#ifndef ONLINE_JUDGE
inv[i]=(inv[i]+p)%p;
#endif
fac[i]=1ll*fac[i-1]*i%p;
ifac[i]=1ll*ifac[i-1]*inv[i]%p;
}
// f[0][0]=1;
int times=1;
for(i=1;i<=n;i++)
times=1ll*times*fac[a[i]-1]%p;
// for(i=1;i<=n;i++)
// {
// times=times*fac[a[i]-1]%p;
// for(j=1;j<=s[i];j++)
// {
// for(k=1;k<=a[i]&&k<=j;k++)
// add(f[i][j],f[i-1][j-k]*ifac[k-1]%p*ifac[a[i]-k]%p*ifac[k]%p);
//// add(f[i][j],f[i-1][j-k]*c(a[i]-1,k-1)%p*ifac[k]%p);
//// f[i][j]=f[i][j]*fac[a[i]-1]%p;
// }
// }
int j;
for(i=1;i<=n;i++)
{
f[i].resize(a[i]+1);
for(j=1;j<=a[i];j++)
f[i][j]=1ll*ifac[j-1]*ifac[j]%p*ifac[a[i]-j]%p;
q.push(pii(a[i],i));
}
t=n;
for(i=1;i<n;i++)
{
int n1=q.top().first;
int x=q.top().second;
q.pop();
int n2=q.top().first;
int y=q.top().second;
q.pop();
f[++t]=mul(f[x],f[y]);
f[x].clear();
f[y].clear();
q.push(pii(n1+n2+1,t));
}
vec ss=f[t];
// vec ss=solve(1,n);
for(i=1;i<=m;i++)
g[i]=1ll*ss[i]*fac[i]%p*times%p;
#ifndef ONLINE_JUDGE
for(i=1;i<=m;i++)
add(g[i],p);
#endif
// g[i]=f[n][i]*fac[i]%p*times%p;
for(i=1;i<=m;i++)
g[i]=1ll*g[i]*fac[m-i]%p;
gao();
for(i=1;i<=m;i++)
{
g[i]=1ll*g[i]*ifac[m-i]%p;
add(g[i],p);
}
// for(i=1;i<=m;i++)
// {
// for(j=1;j<i;j++)
// add(ans[i],h[j]%p*ifac[i-j]%p);
// ans[i]=-ans[i]*ifac[m-i]%p;
// ans[i]=(ans[i]+g[i])%p;
// add(ans[i],-ans[j]*c(m-j,i-j));
// add(ans[i],p);
// h[i]=ans[i]*fac[m-i]%p;
// }
int q;
int x;
scanf("%d",&q);
while(q--)
{
scanf("%d",&x);
printf("%lld\n",g[m-x]);
}
return 0;
}

【XSY2666】排列问题 DP 容斥原理 分治FFT的更多相关文章

  1. [Codeforces 553E]Kyoya and Train(期望DP+Floyd+分治FFT)

    [Codeforces 553E]Kyoya and Train(期望DP+Floyd+分治FFT) 题面 给出一个\(n\)个点\(m\)条边的有向图(可能有环),走每条边需要支付一个价格\(c_i ...

  2. [题解] Atcoder ABC 213 H Stroll DP,分治FFT

    题目 令\(dp_{i,j}\)表示从点1到达点i,路径长度为j的方案数.转移为\(dp_{i,j}=\sum_{(i,v,w)\in E}dp_{v,j-w}p_{i,v,w}\). 显然只能从长度 ...

  3. HDU5730 Shell Necklace(DP + CDQ分治 + FFT)

    题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5730 Description Perhaps the sea‘s definition of ...

  4. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  5. 【BZOJ5119】【CTT2017】生成树计数 DP 分治FFT 斯特林数

    CTT=清华集训 题目大意 有\(n\)个点,点权为\(a_i\),你要连接一条边,使该图变成一颗树. 对于一种连边方案\(T\),设第\(i\)个点的度数为\(d_i\),那么这棵树的价值为: \[ ...

  6. 【XSY2744】信仰圣光 分治FFT 多项式exp 容斥原理

    题目描述 有一个\(n\)个元素的置换,你要选择\(k\)个元素,问有多少种方案满足:对于每个轮换,你都选择了其中的一个元素. 对\(998244353\)取模. \(k\leq n\leq 1525 ...

  7. bzoj 3456 城市规划——分治FFT / 多项式求逆 / 多项式求ln

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3456 分治FFT: 设 dp[ i ] 表示 i 个点时连通的方案数. 考虑算补集:连通的方 ...

  8. Tsinsen A1493 城市规划(DP + CDQ分治 + NTT)

    题目 Source http://www.tsinsen.com/A1493 Description 刚刚解决完电力网络的问题, 阿狸又被领导的任务给难住了. 刚才说过, 阿狸的国家有n个城市, 现在 ...

  9. BNUOJ 51279[组队活动 Large](cdq分治+FFT)

    传送门 大意:ACM校队一共有n名队员,从1到n标号,现在n名队员要组成若干支队伍,每支队伍至多有m名队员,求一共有多少种不同的组队方案.两个组队方案被视为不同的,当且仅当存在至少一名队员在两种方案中 ...

随机推荐

  1. SuperMap iClient 如何使用 WMTS 地图服务(转载)

    原文链接: WMTS服务初步理解与读取 https://blog.csdn.net/supermapsupport/article/details/76806670 SuperMap iClient ...

  2. jupyter notebook安装、登录

    pip install jupyter 提示pip需要升级(本人装的是anaconda) 输入:python -m pip install --upgrade pip 安装完成. 运行jupyter ...

  3. 让自定义view宽高成比例显示

    有时候我们自定义一个View,比如ImageView,我们需要让它宽高按照一定的比例显示,例如在ImageView在GridView中显示,GridView设置了3列,由于ImageVIew的宽度会根 ...

  4. leetcode-48.旋转图像

    leetcode-48.旋转图像 point: 数组 题意 给定一个 n × n 的二维矩阵表示一个图像. 将图像顺时针旋转 90 度. 说明: 你必须在原地旋转图像,这意味着你需要直接修改输入的二维 ...

  5. vue父子组件之间传值

    vue父子组件进行传值 vue中的父子组件,什么是父组件什么是子组件呢?就跟html标签一样,谁包裹着谁谁就是父组件,被包裹的元素就是子组件. 父组件向子组件传值 下面用的script引入的方式,那种 ...

  6. 【学习】Linux Shell脚本实例之一

    1.程序流程控制实例 程序流程控制,实际上就是改变程序的执行顺序.程序在执行过程中若流程被改变,就可能导致输出不同,因此利用这一特性就能够实现程序执行结果的控制.程序流程控制可分为“选择”和“循环”这 ...

  7. SM4加密算法实现Java和C#相互加密解密

    SM4加密算法实现Java和C#相互加密解密 近期由于项目需要使用SM4对数据进行加密,然后传给Java后台,Java后台使用的也是SM4的加密算法但是就是解密不正确,经过一步步调试发现Java中好多 ...

  8. Python Docker 查看私有仓库镜像【转】

    文章来源:python Docker 查看私有仓库镜像 pip 安装: # 首先安装epel扩展源: yum -y install epel-release # 更新完成之后,就可安装pip: yum ...

  9. kubernetes-整体概述和架构

    1.Kubernetes是什么 Kubernetes是一个轻便的和可扩展的开源平台,用于管理容器化应用和服务.通过Kubernetes能够进行应用的自动化部署和扩缩容.在Kubernetes中,会将组 ...

  10. 数据库的设计:深入理解 Realm 的多线程处理机制

    你已经阅读过 Realm 关于线程的基础知识.你已经知道了在处理多线程的时候你不需要关心太多东西了,因为强大的 Realm 会帮你处理好这些,但是你还是很想知道更多细节…… 你想知道在 Realm 的 ...