多项式泛做2
多项式泛做2
卷积变换技巧
目标:将两个相乘的下标之和变为定值
-
若存在这样的式子
令,则,也就是把翻转
于是:,下标和为n为定值,可以利用加速卷积
-
计算,转换为多项式中,把变为,保证指数为正
-
计算这样循环的式子,
令
则指数从到的所有系数即为所有这样循环式子的结果。
-
计算阶前缀或差分
记,
前缀:
阶前缀: 阶前缀
而第项的贡献为。
后缀:
第项贡献为
AT2064 [AGC005F] Many Easy Problems(NTT)
Problem
给定一棵无根树,定义 ,对于所有大小为 的点集,求出能够包含它的最小连通块大小之和。对于 的所有 ,求出 ,答案对取模。()
Sol
考虑对于一个点,若移除这个点,就可以将原树分成若干个连通块,现在考虑点对大小为的连通块的贡献。点包含在大小为的连通块里,当且仅当其他个点存在至少个在不同的连通块中,若个点都在分出的某一个连通块中,则点肯定不会对这样的连通块有贡献。总结一下就是大小为的点集不包含当且仅当所有个点都在以为根的一个子树中。根据容斥,那么。设,令,,那么,经典的变换技巧。
//大坑,这题的模数不是998244353,是924844033,这个原根是5
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 1100000;
const int p = 924844033, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809
//924844033=>gg=5
const int M=18e5;
const int mod=924844033;
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly{
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
//freopen("gen.in","r",stdin);
Poly::init(20);
ll n;
cin>>n;
vector<ll>fac(n+1),infac(n+1);
fac[0]=1;
for(ll i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
infac[n]=qpow(fac[n],mod-2);
for(ll i=n;i>=1;i--)infac[i-1]=infac[i]*i%mod;
vector<vector<int>>E(n+1);
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
vector<ll>sz(n+1),cnt(n+1);
auto dfs=[&](auto self,int u,int fa)->void{
sz[u]=1;
for(auto v:E[u])
{
if(v==fa) continue;
self(self,v,u);
sz[u]+=sz[v];
cnt[sz[v]]++;
}
cnt[n-sz[u]]++;
};
dfs(dfs,1,0);
auto binmo=[&](int x,int y)->ll{
if(x<y) return 0;
return fac[x]*infac[y]%mod*infac[x-y]%mod;
};
vector<ll>C(n+1);
for(int i=1;i<=n;i++) C[i]=binmo(n,i)*n%mod;
Poly::poly A(n+1),B(n+1);
for(int i=1;i<=n;i++) A[i]=1LL*cnt[i]*fac[i]%mod;
for(int i=0;i<=n;i++) B[i]=infac[i];
reverse(A.begin(), A.end());
Poly::poly G=Poly::poly_mul(A,B);
G.resize(n+1);
reverse(G.begin(), G.end());
for(int i=1;i<=n;i++) G[i]=infac[i]*G[i]%mod;
for(int i=1;i<=n;i++)
cout<<(C[i]-G[i]+mod)%mod<<'\n';
return 0;
}
Loj6503-「雅礼集训 2018 Day4」Magic【分治NTT]
Problem
桌面上摆放着种魔术卡,共 张,第种魔术卡数量为,魔术卡顺次摆放,形成一个长度为的魔术序列,在魔术序列中,若两张相邻魔术卡的种类相同,则它们被称为一个魔术对。
两个魔术序列本质不同,当且仅当存在至少一个位置,使得两个魔术序列这个位置上的魔术卡的种类不同,求本质不同的恰好包含个魔术对的魔术序列的数量,答案对取模。
Sol
题目要求恰好个,套路地进行而二项式反演,令表示恰好对,表示至少对,那么,接下来考虑如何求。对于第种魔术卡,如果不把它们分开,那么组合的时候就会贡献对魔术对,现在考虑把第种魔术卡切成几段,每切一段就会少个魔术对,那么假设第种魔术卡切了刀,切完后把颜色相同的段看成一个整体,然后就有个整体,把它们全排列,因为求的是至少,所以就算有相同颜色的段排在相邻位置也没有问题,由于是无标号的可重集,注意除相应的阶乘。生成函数,则。这里统计的是减少的魔术对的个数,本来如果不切的话有个魔术对,现在切了次,就减少个魔术对。这里为什么要减去,是因为在算生成函数的时候算的是,也就是说第项是切刀,总共有种,最后的乘完的幂次其实对应的是,就减少个魔术对
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 1100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809
const int M=18e5;
const int mod=998244353;
template <typename T>void rd(T &x)
{
x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly
{
#define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
#define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了
typedef vector<ll> poly;
const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
//一般模数的原根为 2 3 5 7 10 6
const int inv_G = qpow(G, mod - 2);
int RR[N], deer[2][21][N], inv[N];
void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
for(int p = 1; p <= t; ++ p) {
int buf1 = qpow(G, (mod - 1) / (1 << p));
int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
deer[0][p][0] = deer[1][p][0] = 1;
for(int i = 1; i < (1 << p); ++ i) {
deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
}
}
inv[1] = 1;
for(int i = 2; i <= (1 << t); ++ i)
inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
}
int NTT_init(int n) {//快速数论变换预处理
int limit = 1, L = 0;
while(limit < n) limit <<= 1, L ++ ;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
return limit;
}
void NTT(poly &A, int type, int limit) {//快速数论变换
A.resize(limit);
for(int i = 0; i < limit; ++ i)
if(i < RR[i])
swap(A[i], A[RR[i]]);
for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
int len = mid >> 1;
for(int pos = 0; pos < limit; pos += mid) {
int *wn = deer[type][j];
for(int i = pos; i < pos + len; ++ i, ++ wn) {
int tmp = 1ll * (*wn) * A[i + len] % mod;
A[i + len] = ck(A[i] - tmp + mod);
A[i] = ck(A[i] + tmp);
}
}
}
if(type == 0) {
for(int i = 0; i < limit; ++ i)
A[i] = 1ll * A[i] * inv[limit] % mod;
}
}
inline poly poly_mul(poly A, poly B) {//多项式乘法
int deg = A.size() + B.size() - 1;
int limit = NTT_init(deg);
poly C(limit);
NTT(A, 1, limit);
NTT(B, 1, limit);
for(int i = 0; i < limit; ++ i)
C[i] = 1ll * A[i] * B[i] % mod;
NTT(C, 0, limit);
C.resize(deg);
return C;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
Poly::init(19);
int m,n,k;
cin>>m>>n>>k;
vector<int>a(m+1);
for(int i=1;i<=m;i++) cin>>a[i];
vector<ll>fac(n+1),infac(n+1);
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
infac[n]=qpow(fac[n],mod-2);
for(int i=n;i>=1;i--) infac[i-1]=infac[i]*i%mod;
vector<vector<ll>>g(m+1);
auto C=[&](int x,int y)->ll{
if(x<y) return 0;
return fac[x]*infac[y]%mod*infac[x-y]%mod;
};
for(int i=1;i<=m;i++)
{
g[i].push_back(0);
for(int j=1;j<=a[i];j++)
g[i].push_back(C(a[i]-1,j-1)*infac[j]%mod);
}
auto CDQ=[&](auto self,int l,int r)->Poly::poly{
if(l==r) return g[l];
int mid=l+r>>1;
Poly::poly L=self(self,l,mid);
Poly::poly R=self(self,mid+1,r);
Poly::poly Q=Poly::poly_mul(L,R);
return Q;
};
Poly::poly F=CDQ(CDQ,1,m);
Poly::poly G((int)F.size());
for(int i=0;i<=n;i++) G[i]=F[n-i]*fac[n-i]%mod;
ll ans=0;
for(int i=k;i<=n;i++)
{
if((i-k)&1) ans=(ans+mod-C(i,k)*G[i]%mod)%mod;
else ans=(ans+C(i,k)*G[i]%mod)%mod;
}
cout<<ans<<'\n';
}
P4491 [HAOI2018]染色(二项式反演+NTT)
Problem
为了报答小 的苹果, 小 打算送给热爱美术的小 一块画布, 这块画布可 以抽象为一个长度为 的序列, 每个位置都可以被染成 种颜色中的某一种.然而小C只关心序列的 个位置中出现次数恰好为 的颜色种数, 如果恰好出现了 次的颜色有 种, 则小 C 会产生 的愉悦度.小C希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 取模的结果是多少.
Sol
二项式反演,设表示恰好出现了次,表示至少出现了次,则。考虑求,由组合数学的思想算出,组合意义就是先从种颜色取种,使得每一种涂次,然后考虑选排列,从个物品种选出个,然后全排列,由于每种颜色有多个,相当于是可重复集的排列,每种颜色要除以,总共要除,然后剩下的种颜色随便涂在剩下的个物品上。注意到,令,,那么,卷积形式,这样就可加快运算,最后答案就是
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 1100000;
const int p = 1004535809, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809
const int M=18e5;
const int mod=1004535809;
template <typename T>void rd(T &x)
{
x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly{
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
Poly::init(19);
int n,m,s;
cin>>n>>m>>s;
int L=max(n,m);
vector<ll>w(m+1);
for(int i=0;i<=m;i++) cin>>w[i];
vector<ll>fac(L+1),infac(L+1);
fac[0]=1;
for(int i=1;i<=L;i++) fac[i]=fac[i-1]*i%mod;
infac[L]=qpow(fac[L],mod-2);
for(int i=L;i>=1;i--) infac[i-1]=infac[i]*i%mod;
auto C=[&](int x,int y)->ll{
if(x<y) return 0;
return fac[x]*infac[x-y]%mod*infac[y]%mod;
};
auto P=[&](int x,int y)->ll{
if(x<y) return 0;
return fac[x]*infac[x-y]%mod;
};
int L_=min(m,n/s);
Poly::poly A(L_+1),B(L_+1);
auto g=[&](int i)->ll{
return C(m,i)*P(n,s*i)%mod*qpow(infac[s],i)%mod*qpow(m-i,n-s*i)%mod;
};
for(int i=0;i<=L_;i++)
{
A[i]=fac[i]*g(i)%mod;
if(i&1) B[i]=(mod-infac[i])%mod;
else B[i]=infac[i];
}
reverse(A.begin(), A.end());
Poly::poly F=Poly::poly_mul(A,B);
F.resize(L_+1);
reverse(F.begin(), F.end());
ll ans=0;
for(int i=0;i<=L_;i++)
ans=(ans+infac[i]*F[i]%mod*w[i]%mod)%mod;
if(ans<0) ans=(ans+mod)%mod;
cout<<ans<<'\n';
return 0;
}
P5644 [PKUWC2018]猎人杀
Problem
一开始有个猎人,第i个猎人有仇恨度 ,每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡。然而向谁开枪也是有讲究的,假设当前还活着的猎人有,那么有的概率向猎人开枪。一开始第一枪由你打响,目标的选择方法和猎人一样,由于开枪导致的连锁反应,所有猎人最终都会死亡,现在 号猎人想知道它是最后一个死的的概率。答案对 取模。
Sol
首先对题意进行一波转换,设,则题意可以转为为:每个人被打中的概率为,不断开枪直至打中一个活着的人,则这个人就会死去。什么意思呢?就是说就算每轮我们允许向一个死去的人开枪,但最终第个活着的人被射中的概率是不变的,这样就可以不用处理每次射击分母都改变的情况了,证明:
假设当前还存活的猎人的仇恨度为,并且第个活着的猎人被射中的概率为,题意转换前,题意转换后。
接下来考虑容斥,枚举在号猎人之后死的猎人的集合,并且该集合猎人总的仇恨值为,全部猎人总的仇恨值为,那么。
由于,所以考虑枚举,问题就只需要在已知的情况下如何快速求出有多少个集合的仇恨值和为,考虑生成函数,的贡献就是,所以答案就是
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 5100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809
//924844033=>gg=5
const int M=18e5;
const int mod=998244353;
ll qpow(ll a, int b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly{
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
//freopen("gen.in","r",stdin);
Poly::init(20);
ll n;
cin>>n;
vector<int>w(n+1);
int S=0;
for(int i=1;i<=n;i++) cin>>w[i],S+=w[i];
auto CDQ=[&](auto self,int l,int r)->Poly::poly{
if(l==r)
{
Poly::poly F(w[l]+1);
F[0]=1,F[w[l]]=mod-1;
return F;
}
int mid=l+r>>1;
Poly::poly L=self(self,l,mid);
Poly::poly R=self(self,mid+1,r);
return Poly::poly_mul(L,R);
};
Poly::poly F=CDQ(CDQ,2,n);
ll ans=0;
for(int i=0;i<=S-w[1];i++)
if(F[i]<0) ans=(ans+mod+F[i]*w[1]%mod*qpow(i+w[1],mod-2)%mod)%mod;
else ans=(ans+F[i]*w[1]%mod*qpow(i+w[1],mod-2)%mod)%mod;
cout<<ans<<'\n';
return 0;
}
CF960G-Bandit Blues[第一类斯特林数,分治,NTT]
Problem
给你三个正整数 ,定义 为一个排列中是前缀最大值的数的个数,定义 为一个排列中是后缀最大值的数的个数,求长度为 的排列中满足 且 的排列个数。 ,答案对 取模。
Sol
考虑最大数放的位置为,这时候排列被分成部分,然后在剩下的个数选个放左边,选个放右边,方法数为。然后先考虑左边,由于最大的数一定会同时存在于前缀最大和后缀最大,所以前个数有个最大前缀,考虑一种构造方法,先把这个数分成组,然后每组把最大的数放到最左边,最后把按照每组最左边数的大小按照从小到大的顺序放置。也可以理解为就是一种圆排列,因为是循环的,所以我们每次把这个圆环从最大的数断开,方法数是。同理右边就是,最后答案就是
2021牛客暑期多校训练营9 C Cells
题意
给定n个点,分别在,现在这个点只能向右或向下移动,要求这个点分别走到且路径两两不相交的总方案数,注意可以走到中的任何一个。
Sol
首先不相交路径数我们优先考虑引理(当时不会,大雾)
根据,由于每个到的路径数分别为
也即
那么所对应的答案矩阵为
然后每一列提出个出来,再把上下约分一下
以第一行为例
以此类推:每一列都可以通过前几列华为的形式,于是
然后每行提一个公因式出来
到这里就很清楚了,后面的行列式是一个范德蒙行列式
于是用处理处和
后面差的形式如果暴力则,显然不可,考虑用卷积 ,优化到
为什么可以这样?一般的卷积是求系数相乘,怎么才能求减法呢?
由于卷积相乘幂次是相加的,于是可以把当做幂次,这样卷积完之后,遍历幂次的所有范围,如果系数不为0,说明这个幂次存在也即存在。
下面再考虑一个问题,卷积求的是任意两个的乘积,而我们要求的是有顺序的。
首先由于存在负数,于是我们把负数加上一个偏移量(的最大值记为),于是我们幂次有和
,而我们最后卷积完会得到的所有幂次范围为 ,这是注意题目有个条件,于是这样卷完等价于 于是所有符合的幂次我们就从到枚举,其对应的系数就为这个差值出现的次数,快速幂求一下就可以了。
#include <bits/stdc++.h>
#define ll long long
#define lll __int128
using namespace std;
const int P = 998244353,G=3; // P=1004535809 ord(P)=3
const int pi=acos(-1);
inline ll read()
{
ll x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int N=1e6+10,M=1e6;
ll n,m;
ll rev[1<<22],bit,tot;
ll a[1<<22],b[1<<22];
ll qmi(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%P;
x=x*x%P;
y>>=1;
}
return res;
}
ll inv(ll x)
{
return qmi(x,P-2);
}
void NTT(ll a[],int type)
{
for(int i=0;i<tot;i++)
if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int mid=1;mid<tot;mid<<=1)
{
ll wn=qmi(G,(P-1)/(mid*2));
if(type==-1) wn=qmi(wn,P-2);
for(int i=0,len=mid<<1;i<tot;i+=len)
{
ll w=1;
for(int j=0;j<mid;j++,w=w*wn%P)
{
ll x=a[i+j],y=w*a[i+j+mid]%P;
a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
}
}
}
if(type==-1)
{
ll inv_=inv(tot);
for(int i=0;i<tot;i++)
a[i]=a[i]*inv_%P;
}
}
int main()
{
n=read();
ll ans=1;
for(int i=1;i<=n;i++)
{
ll x;
x=read();
ans=ans*(x+1)%P;
a[x]=1,b[M-x]=1;
}
ll k1=1,k2=1;
for(int i=1;i<=n;i++)
{
k1=k1*i%P;
k2=k2*k1%P;
}
ans=ans*inv(k2)%P;
//cout<<ans<<endl;
tot=1<<21,bit=21;
for(int i=0;i<tot;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
NTT(a,1),NTT(b,1);
for(int i=0;i<tot;i++) a[i]=a[i]*b[i]%P;
NTT(a,-1);
for(int i=1e6+1;i<=M*2;i++)
{
if(a[i])
{
ans=ans*qmi(i-1e6,a[i])%P; //求的是 (a_j - a_i ) (0<i<j<=n&&a_i<a_j)
//所以枚举>M的数就是(a_j-a_i)
}
}
cout<<ans<<endl;
puts("");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律