题意

\(n\) 张卡牌 \(m\) 种颜色,询问有多少种本质不同的序列满足相邻颜色相同的位置数量等于 \(k\)。

分析

首先本质不同不好直接处理,可以将同种颜色的卡牌看作是不相同的,求出答案后除以 \(\prod {a_i!}\) 即可。

如果我们能够得到一个至少存在 \(k\) 个魔术对的排列数,就可以容斥了。

考虑单独处理每种颜色, 枚举一个颜色 \(i\),计算这种颜色至少有 \(j\) 对的方案总数。

可以选择 \(j\) 张牌保证这些牌一定跟在某张牌的后面,这样就可以形成 \(\geq j\) 个满足要求的魔术对了。

然后做一个背包。

定义状态 \(f_{i,j}\) 表示处理了前 \(i\) 种颜色,至少存在 \(j\) 对魔术对的排列数。

对于 \(f_{m,j}\),剩下的 \(n-j\) 张牌可以任意排列。背包是一个卷积的形式可以 NTT 优化。

合并背包的过程可以用堆维护,每次合并堆顶最小的两个背包数组,复杂度类似启发式合并。

所以总时间复杂度为 \(O(n\log^2n)\)。

技巧:在计算方案出现困难的时候考虑容斥简化条件(类似反演题目的思想)

代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define pb push_back
typedef long long LL;
typedef pair<int,int> pii;
#define mp make_pair
inline int gi(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
return x*f;
}
template<typename T>inline bool Max(T &a,T b){return a<b?a=b,1:0;}
template<typename T>inline bool Min(T &a,T b){return b<a?a=b,1:0;}
const int N=1e5 + 7,mod=998244353;
int n,m,k,K,len,lst,sz[N<<1],a[N];
LL invfac[N],inv[N],fac[N];
vector<LL>v[N<<1];
priority_queue<pii,vector<pii>,greater<pii> >Q;
void add(LL &a,LL b){a+=b;if(a>=mod) a-=mod;}
LL Pow(LL a,LL b){
LL res=1ll;
for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
return res;
}
LL Inv(LL a){return Pow(a,mod-2);}
LL C(int n,int m){
return fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
void ntt(vector<LL> &c,int n,int f){
for(int i=0,j=0;i<n;++i){
if(i<j) swap(c[i],c[j]);
for(int k=n>>1;(j^=k)<k;k>>=1);
}
for(int i=1;i<n;i<<=1){
LL wn=Pow(3,(mod-1)/(i<<1));
if(f==-1) wn=Inv(wn);
for(int j=0;j<n;j+=i<<1){
LL w=1;
for(int k=0;k<i;++k,w=w*wn%mod){
LL x=c[j+k],y=w*c[j+k+i]%mod;
c[j+k]=(x+y)%mod;
c[j+k+i]=(x-y+mod)%mod;
}
}
}
if(f==-1){LL inv=Inv(n);for(int i=0;i<n;++i) c[i]=c[i]*inv%mod;}
}
int main(){
m=gi(),n=gi(),K=gi();
fac[0]=invfac[0]=inv[1]=1;
rep(i,1,n){
if(i^1) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
fac[i]=fac[i-1]*i%mod;
invfac[i]=invfac[i-1]*inv[i]%mod;
}
rep(c,1,m){
sz[c]=a[c]=gi();int x=a[c];
Q.push(mp(sz[c],c));
v[c].resize(x+7);
rep(i,0,x-1) v[c][i]=C(x,i)*fac[x-1]%mod*invfac[x-i-1]%mod;
}
while(!Q.empty()){
int x=Q.top().second;Q.pop();
if(Q.empty()) { lst=x; break; }
int y=Q.top().second;Q.pop();
for(len=1;len<=sz[x]+sz[y];len<<=1);
v[x].resize(len+7);
v[y].resize(len+7);
ntt(v[x],len,1);
ntt(v[y],len,1);
for(int i=0;i<len;++i) v[x][i]=v[x][i]*v[y][i]%mod;
ntt(v[x],len,-1); sz[x]+=sz[y];
v[y].clear();
Q.push(mp(sz[x],x));
}
LL ans=0;
rep(i,K,n-1)
add(ans,(i-K&1?mod-1:1)*v[lst][i]%mod*C(i,K)%mod*fac[n-i]%mod);
rep(i,1,m) ans=ans*invfac[a[i]]%mod;
printf("%lld\n",ans);
return 0;
}

LOJ#6503.「雅礼集训 2018 Day4」Magic[容斥+NTT+启发式合并]的更多相关文章

  1. Loj #6503. 「雅礼集训 2018 Day4」Magic

    Loj #6503. 「雅礼集训 2018 Day4」Magic 题目描述 前进!前进!不择手段地前进!--托马斯 · 维德 魔法纪元元年. 1453 年 5 月 3 日 16 时,高维碎片接触地球. ...

  2. LOJ6503. 「雅礼集训 2018 Day4」Magic(容斥原理+NTT)

    题目链接 https://loj.ac/problem/6503 题解 题中要求本质不同的序列数量,不太好搞.我们考虑给相同颜色的牌加上编号,这样所有牌都不相同.那么如果我们求出了答案,只需要将答案除 ...

  3. Loj#6503-「雅礼集训 2018 Day4」Magic【分治NTT】

    正题 题目链接:https://loj.ac/p/6503 题目大意 \(n\)张卡\(m\)种,第\(i\)种卡有\(a_i\)张,求所有排列中有\(k\)对相邻且相同的卡牌. \(1\leq n\ ...

  4. 【loj#6503.】「雅礼集训 2018 Day4」Magic(生成函数+容斥)

    题面 传送门 题解 复杂度比较迷啊-- 以下以\(n\)表示颜色总数,\(m\)表示总的卡牌数 严格\(k\)对比较难算,我们考虑容斥 首先有\(i\)对就代表整个序列被分成了\(m-i\)块互不相同 ...

  5. 2018.10.27 loj#6035. 「雅礼集训 2017 Day4」洗衣服(贪心+堆)

    传送门 显然的贪心题啊...考试没调出来10pts滚了妙的一啊 直接分别用堆贪心出洗完第iii件衣服需要的最少时间和晾完第iii件衣服需要的最少时间. 我们设第一个算出来的数组是aaa,第二个是bbb ...

  6. LOJ #6509. 「雅礼集训 2018 Day7」C

    神仙题 LOJ #6509 题意 给定一棵树,点权为0/1,每次随机一个点(可能和之前所在点相同)走到该点并将其点权异或上1 求期望的移动距离使得所有点点权相同 题解 根本不会解方程 容易发现如果一个 ...

  7. loj 6037 「雅礼集训 2017 Day4」猜数列 - 动态规划

    题目传送门 传送门 题目大意 有一个位置数列,给定$n$条线索,每条线索从某一个位置开始,一直向左或者向右走,每遇到一个还没有在线索中出现的数就将它加入线索,问最小的可能的数列长度. 依次从左到右考虑 ...

  8. Loj 6036 「雅礼集训 2017 Day4」编码 - 2-sat

    题目传送门 唯一的传送门 题目大意 给定$n$个串,每个串只包含 ' .问是否可能任意两个不同的串不满足一个是另一个的前缀. 2-sat的是显然的. 枚举每个通配符填0还是1,然后插入Trie树. 对 ...

  9. LOJ #6037.「雅礼集训 2017 Day4」猜数列 状压dp

    这个题的搜索可以打到48分…… #include <cstdio> #include <cstring> #include <algorithm> ; bool m ...

随机推荐

  1. .NET(C#)如何遍历Dictionary

    我们知道.NET中的Dictionary是键/值对的集合,使用起来也是比较方便,Dictionary也可以用KeyValuePair来迭代遍历,具体如下: using System; using Sy ...

  2. poj_3253 Fence Repair

    Fence Repair Description Farmer John wants to repair a small length of the fence around the pasture. ...

  3. 【MySQL 5.7 Reference Manual】15.4.2 Change Buffer(变更缓冲)

    15.4.2 Change Buffer(变更缓冲)   The change buffer is a special data structure that caches changes to se ...

  4. django直接操作MySQL,中文乱码

    sql_insert = '''insert into sinfors_hvvms values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)''' c ...

  5. Eclipse无法自动编译生成class文件

    在Maven项目中,通常eclipse生成的class文件都会在target/classes文件夹下,但是有时候由于各种原因,classes下没有生成class文件,导致项目启动失败 . 大部分cla ...

  6. 通过javascript添加一行

    <html><head> <title>添加新的行</title></head><body> <div onclick=& ...

  7. pt-heartbeat工具监控MySQL复制延迟

    pt-heartbeat工作原理: 1,在主库上的某个数据库A中创建一张heartbeat表,按照一定的时间频率更新该表的字段(把时间更新进去). 2,从主库连接到从上的这个数据库A中检查复制的时间记 ...

  8. spark任务调度和资源分配

    Spark调度模式 FIFO和FAIR     Spark中的调度模式主要有两种:FIFO和FAIR.    默认情况下Spark的调度模式是FIFO(先进先出),谁先提交谁先执行,后面的任务需要等待 ...

  9. python3中启动 uiautomator2viewer时报错解决办法

    事先说明:我电脑里同时了安装了 python2和python3,启动对应版本时分别带上版本号,如何设置共存请参考:python2与python3共存 打开命令窗口,启动weditor : python ...

  10. ubuntu16.04忘记密码解决方案

    主要解决通过sudo apt-get install安装mysql时输入密码时输错导致安装成功后,无法登陆MySQL. 之前遇到这种问题时,我个人比较喜欢通过卸载并重新安装解决,后来觉得这个虽然可以解 ...