题解:P17224 [Math×Girl²] 搬家
ACcepted917 · · 题解
题解:P17224 [Math×Girl²] 搬家
关键性质
由于
所以任意一个编号小的物品,其价值大于后面所有物品价值之和。
因此最优解等价于贪心:
按编号从小到大扫描每个物品,如果当前物品能放入剩余容量,就一定要选它;否则跳过。
这就是“能放就放”的贪心。
枚举大小为 1 的物品数量
设共有
总大小为
如果
即
那么所有物品都能被装入箱子,打包机结果和最优解都是全集,方案数为
下面只考虑总大小大于
情况一:c\ge M
此时打包机先装大小为
因为大小为
要让贪心最优解也得到同样的结果,必须保证:
前
M-1 个物品全都是大小为1 。
否则,如果前面出现了一个大小为
因此方案数为:前
情况二:c<M
此时打包机先装完所有大小为
剩余容量为
设
其中
由于总大小大于
也就是说,打包机选出的集合是:
- 所有大小为
1 的物品; - 编号最小的
k 个大小为2 的物品。
现在考虑第
设它的位置为
因为它是第
即
在扫描到位置
为了让贪心也不选这个物品,必须有
代入
又因为
- 若
r=0 ,则A=c-1 或A=c ; - 若
r=1 ,则A=c 。
对于每个合法的
- 前
x-1 个位置中有A 个1 和k 个2 ,方案数为
- 第
x 个位置固定为大小为2 ; - 后面剩余
N-A-k-1 个位置中,还需要放c-A 个大小为1 ,方案数为
所以该部分贡献为
时间复杂度
预处理阶乘和逆元,
枚举
对于
#include<bits/stdc++.h>
using namespace std;
using int64=long long;
const int MOD=998244353;
int mod_pow(int64 a,int64 e){
int64 r=1;
while(e){
if(e&1)r=r*a%MOD;
a=a*a%MOD;
e>>=1;
}
return(int)r;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N,M;
cin>>N>>M;
vector<int>fact(N+1),inv_fact(N+1);
fact[0]=1;
for(int i=1;i<=N;i++)fact[i]=(int64)fact[i-1]*i%MOD;
inv_fact[N]=mod_pow(fact[N],MOD-2);
for(int i=N;i>=1;i--)inv_fact[i-1]=(int64)inv_fact[i]*i%MOD;
auto C=[&](int n,int k)->int{
if(k<0||k>n||n<0)return 0;
return (int64)fact[n]*inv_fact[k]%MOD*inv_fact[n-k]%MOD;
};
int ans=0;
int limit_total=2*N-M;
for(int c=0;c<=N;c++){
if(c>=limit_total){
ans=(ans+C(N,c))%MOD;
}else{
if(c>=M){
if(M<=N){
ans=(ans+C(N-M+1,c-M+1))%MOD;
}
}else{
int rem=M-c;
int r=rem&1;
int k=(rem-r)/2;
for(int A=c+r-1;A<=c;A++){
if(A<0)continue;
int term=(int64)C(A+k,A)*C(N-A-k-1,c-A)%MOD;
ans=(ans+term)%MOD;
}
}
}
}
cout<<ans<<'\n';
return 0;
}