题解 H. Fly "蔚来杯"2022牛客暑期多校训练营1
觉得题解写有点模糊,自己写一份
【大意】
给定 个正整数 ,和 个限制 。
求有多少个不同的有序数组 使得其满足以下两个限制:
【分析】
显然, 表示 的第 位为 ,这也启示我们可以从二进制位的角度考虑做法
而 对求和式的贡献,可以独立地拆解为 分别考虑
由于 且 ,故 不可能有超过 位为 的
因此问题等价于从 共 个物品中,扣除所有 的 个物品后,查询体积不超过 的背包方案数
注意到每个 前面乘的系数都是 的整幂次,若我们对进行背包的物品,按系数的 的幂次进行分类,从小到大进行背包;则在 为系数的物品进行背包后,后续物品再次进行背包,是不可能影响低于 位的二进制数码的
为了避免状态过多,我们试图采用数位 dp 的方式,一位一位考虑对答案的贡献
注意到若进行到第 位后,若体积 的二进制低 位大于 ,即 ,则当且仅当 的第 位为 且 的第 位为 会使得 ;若体积 的二进制低 位小于等于 ,即 ,则当且仅当 的第 位为 且 的第 位为 会使得
因此,我们考虑记 表示进行完 为系数的物品的背包后,体积 满足 ,且 是否严格大于 ,的方案数
故答案是 ,即是 的方案数。由于 ,故 ,因此答案为
这种情况下,
考虑该 的转移,首先,我们需要知道系数为 的物品的背包结果,它可以用生成函数 ,我们记为 ,即
我们可以先用多项式线段树分治或多项式启发式合并,先在 的时间内求出 。后续求解 时,只需要反向跑背包进行删除,复杂度为 ;由于限制数量为 个,故复杂度 是可接受的
现在考虑已知 、已知 ,如何合并得出 的结果
由于 表示进行完 为系数的物品后,体积 满足 ,且 的方案数; 表示系数为 的物品,拼出体积为 的方案数
因此将两者合并后,新体积 ,大小关系
于是可以写出一部分的转移式: ,显然是加法卷积的形式,可以在 的时间内求解
同理,由于 表示进行完 为系数的物品后,体积 满足 ,且 的方案数
则两者合并后,新体积 ,大小关系
于是另一部分转移式为: ,同样是加法卷积,可以在 的时间内求解
初始时,是 表示进行完 为系数的物品后,体积 且 是否严格大于 ;也是直接由 贡献到
综上,先对 进行 的多项式启发式合并或多项式线段树分治;接着对数位进行 DP,先求出 ,再分别利用 与 卷积递推 的贡献
求 的总复杂度为 ,卷积递推的复杂度为
故总复杂度为
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define sz(a) (int)a.size()
#define de(a) cerr << #a << " = " << a << endl
#define dd(a) cerr << #a << " = " << a << " "
#define all(a) a.begin(), a.end()
#define pw(x) (1ll<<(x))
#define lc(x) ((x)<<1)
#define rc(x) ((x)<<1|1)
#define rsz(a, x) (a.resize(x))
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
typedef double db;
const int P=998244353;
inline int kpow(int a, int x, int p=P) { int ans=1; for(;x;x>>=1, a=(ll)a*a%p) if(x&1) ans=(ll)ans*a%p; return ans; }
inline int exgcd(int a, int b, int &x, int &y) {
static int g;
return b?(exgcd(b, a%b, y, x), y-=a/b*x, g):(x=1, y=0, g=a);
}
inline int inv(int a, int p=P) {
static int x, y;
return (exgcd(a, p, x, y)==1)?((x<0)?x+p:x):(-1);
}
const int LimBit=16;
const int M=1<<LimBit<<1;
namespace Poly{
const int G=3;
struct vir {
int v;
vir(int v_=0):v(v_>=P?v_-P:v_) {}
inline vir operator + (const vir &x) const { return vir(v+x.v); }
inline vir operator - (const vir &x) const { return vir(v+P-x.v); }
inline vir operator * (const vir &x) const { return vir((ll)v*x.v%P); }
inline vir operator - () const { return vir(P-v); }
inline vir operator ! () const { return vir(inv(v)); }
inline operator int() const { return v; }
};
struct poly : public vector<vir> {
inline friend ostream& operator << (ostream& out, const poly &p) {
if(!p.empty()) out<<(int)p[0];
for(int i=1; i<sz(p); ++i) out<<" "<<(int)p[i];
return out;
}
};
int N, N_, Stk[M], curStk, rev[M];
vir invN, Inv[M], w[2][M];
inline void init() {
N_=-1;
curStk=0;
Inv[1]=1;
for(int i=2; i<M; ++i)
Inv[i]=-vir(P/i)*Inv[P%i];
}
void work(){
if(N_==N) return ;
N_=N;
int d = __builtin_ctz(N);
vir x(kpow(G, (P-1)/N)), y=!x;
w[0][0] = w[1][0] = 1;
for (int i = 1; i < N; ++i) {
rev[i] = (rev[i>>1] >> 1) | ((i&1) << (d-1));
w[0][i]=x*w[0][i-1], w[1][i]=y*w[1][i-1];
}
invN=!vir(N);
}
inline void FFT(vir a[M],int f){
static auto make = [=](vir w, vir &a, vir &b) { w=w*a; a=b-w; b=b+w; };
for(int i=0;i<N;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<N;i<<=1)
for(int j=0,t=N/(i<<1);j<N;j+=i<<1)
for(int k=0,l=0;k<i;k++,l+=t)
make(w[f][l], a[j+k+i], a[j+k]);
if(f) for(int i=0;i<N;++i) a[i]=a[i]*invN;
}
vir p1[M], p0[M];
inline void get_mul(poly &a, poly &b, int na, int nb) {//3*FFT
for(N=1;N<na+nb-1;N<<=1);
for(int i=0; i<na; ++i) p1[i]=(int)a[i]; for(int i=na;i<N;++i) p1[i]=0;
for(int i=0; i<nb; ++i) p0[i]=(int)b[i]; for(int i=nb;i<N;++i) p0[i]=0;
work(); FFT(p1,0); FFT(p0,0);
for(int i=0;i<N;++i) p1[i]=p1[i]*p0[i];
FFT(p1,1);
rsz(a, na+nb-1); for(int i=0; i<sz(a); ++i) a[i]=p1[i];
}
poly T[M];
priority_queue<pii> H;
inline poly &get_merge(int cnt) {
while(H.size()) H.pop();
for(int i=1; i<=cnt; ++i) H.emplace( mp(-sz(T[i]), i) );
for(int t=H.size(); t>=2; --t) {
int x=H.top().se; H.pop();
int y=H.top().se; H.pop();
get_mul(T[x], T[y], sz(T[x]), sz(T[y]));
H.emplace( mp(-sz(T[x]), x) );
}
return T[H.top().se];
}
}
using Poly::vir;
using Poly::poly;
const int Lim=8e4, MAXN=Lim+10;
int n, k, a[MAXN];
ll m, mask[MAXN];
poly f, g, h;
inline void divit(poly &f, int a) {
for(int i=sz(f)-1, j=i-a; j>=0; --i, --j)
f[j]=f[j]-f[i];
for(int i=0, j=a, J=sz(f); j<J; ++i, ++j)
f[i]=f[j];
rsz(f, sz(f)-a);
}
vir dp[2][MAXN][2];
inline void work() {
g=f;
for(int i=1; i<=n; ++i) if(mask[i]&1) divit(g, a[i]);
for(int i=sz(g)-1; ~i; --i) dp[0][i>>1][(i&1)>(m&1)]=dp[0][i>>1][(i&1)>(m&1)]+g[i];
for(int t=1; t<=59; ++t) {
auto lst=dp[t+1&1], now=dp[t&1];
int mt=(m>>t)&1;
for(int i=0, I=Lim; i<=I; ++i) now[i][0]=now[i][1]=0;
g=f;
for(int i=1; i<=n; ++i) if((mask[i]>>t)&1) divit(g, a[i]);
rsz(h, Lim+1);
for(int i=0; i<=Lim; ++i) h[i]=lst[i][0];
Poly::get_mul(h, g, sz(h), sz(g));
for(int i=sz(h)-1; ~i; --i)
now[i>>1][(i&1)>mt]=now[i>>1][(i&1)>mt]+h[i];
rsz(h, Lim+1);
for(int i=0; i<=Lim; ++i) h[i]=lst[i][1];
Poly::get_mul(h, g, sz(h), sz(g));
for(int i=sz(h)-1; ~i; --i)
now[i>>1][(i&1)>=mt]=now[i>>1][(i&1)>=mt]+h[i];
}
cout<<(int)dp[1][0][0];
}
inline void init() {
Poly::init();
cin>>n>>m>>k;
for(int i=1; i<=n; ++i) {
cin>>a[i];
poly &p=Poly::T[i];
rsz(p, a[i]+1);
p[0]=p[a[i]]=1;
}
f=Poly::get_merge(n);
for(int i=1, x, y; i<=k; ++i)
cin>>x>>y, mask[x]|=pw(y);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init();
work();
cout.flush();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现