很好的推柿子题
题目链接
考虑二项式反演.
我们令\(g(i)\)表示至少有\(i\)对魔法对.
那么很显然\(ans=\sum_{i=k}^n(-1)^{i-k}C_i^kg(i)\)
首先,我们让每张牌都带标号,这样会好算很多.
最后答案乘上\(\frac{1}{\prod a[i]!}\)即可.
现在我们可以愉快地\(DP\)了.
考虑用\(f[i][j]\)表示前\(i\)个颜色,已经有了\(j\)对魔法对的方案.
那么我们枚举一个\(k\),\(f[i][j]=\sum_{k=0}^{a[i]-1}f[i-1][j-k]X\)
其中\(X\)指我们要转移的东西.
考虑这个东西是什么.
我们有\(k\)对魔法对,那么肯定有一个\(C_{a[i]}^k\)的方案.(带标号)
其次有\(k\)张牌作为魔法对的第二张牌(废话).
那么剩下的\(a[i]-k\)张牌作为第一张牌,显然没有位置限制.
我们先不去考虑这\(a[i]-k\)张牌的贡献,而是最后一起算.
这\(k\)张牌,第一张牌有\(a[i]-k\)种放法,第二张牌有\(a[i]-k+1\)中放法...
于是这个\(X=C_{a[i]}^k\frac{(a[i]-1)!}{(a[i]-k-1)!}\)
我们这样只是算出了第二张牌的方法.而第一张牌是没有限制的,因此如果有\(i\)个魔法对答案要乘上\((n-i)!\)
这样是\(O(nm)\)的,可以获得\(64\)分的好成绩.
每次转移相当于乘上一个多项式.
我们用分治+\(NTT\)优化一下就好了.
代码如下
#include
#include
#include
#include
#include
#include
#define N (131110)
#define P (998244353)
#define inf (0x7f7f7f7f)
#define rg register int
#define Label puts("NAIVE")
#define spa print(' ')
#define ent print('\n')
#define rand() (((rand())<<(15))^(rand()))
typedef long double ld;
typedef long long LL;
typedef unsigned long long ull;
using namespace std;
inline char read(){
static const int IN_LEN=1000000;
static char buf[IN_LEN],*s,*t;
return (s==t?t=(s=buf)+fread(buf,1,IN_LEN,stdin),(s==t?-1:*s++):*s++);
}
template
inline void read(T &x){
static bool iosig;
static char c;
for(iosig=false,c=read();!isdigit(c);c=read()){
if(c=='-')iosig=true;
if(c==-1)return;
}
for(x=0;isdigit(c);c=read())x=((x+(x<<2))<<1)+(c^'0');
if(iosig)x=-x;
}
inline char readchar(){
static char c;
for(c=read();!isalpha(c);c=read())
if(c==-1)return 0;
return c;
}
const int OUT_LEN = 10000000;
char obuf[OUT_LEN],*ooh=obuf;
inline void print(char c) {
if(ooh==obuf+OUT_LEN)fwrite(obuf,1,OUT_LEN,stdout),ooh=obuf;
*ooh++=c;
}
template
inline void print(T x){
static int buf[30],cnt;
if(x==0)print('0');
else{
if(x<0)print('-'),x=-x;
for(cnt=0;x;x/=10)buf[++cnt]=x%10+48;
while(cnt)print((char)buf[cnt--]);
}
}
inline void flush(){fwrite(obuf,1,ooh-obuf,stdout);}
struct Poly{vector a;int mx;}f[20010];
int w[N],n,m,k,Lim,rev[N];
LL jc[N],inv[N],ans,M=1,g[N],mi[41],iv[41];
LL C(int n,int m){
if(n>=1;
}
return res;
}
void NTT(vector &a,int tp){
for(int i=0;i>1;
Poly a=work(l,mid);
Poly b=work(mid+1,r);
int sum=a.mx+b.mx; rev[0]=0;
for(Lim=1;Lim<=sum;Lim<<=1)len++;
for(int i=0;i>1]>>1)|((i&1)<<(len-1));
a.a.resize(Lim),b.a.resize(Lim);
NTT(a.a,1),NTT(b.a,1);
for(int i=0;i