巧妙的 DP 题目。
设 ,并接下来都是用 。设每种材料被收集的时间为 ,相当于求 。max 显然不好直接求,考虑 min-max 容斥得到:
其中 。考虑一个暴力 DP,设状态 表示选择 种材料, 的方案数。因为需要每次枚举所有材料。这样的时间复杂度 ,不能通过。
注意到 是很小的。考虑 压进去。于是我们直接求容斥系数 。因为需要计算 的贡献, 还是需要压进状态里,于是考虑设计状态 表示考虑前 个数,,关于 的容斥系数。但是处理组合数似乎令人头疼,尝试利用 ,拆一下 的容斥系数,得到:
拆出来的两个是 和 的容斥系数!这就很棒了,因为这个递推关系与 具体值无关,我们就不用枚举每一种材料了。我们容易得到状态转移方程:
需要注意的是对边界的处理。有用的状态只有 。 作为边界,只会被贡献到 ,新选的产生的容斥系数为 ,于是边界应为 。或者换一种角度,从定义出发,,这里的组合数涉及到负数,需要用到扩展的定义,但依然满足上面的递推式。
滚动数组倒序枚举 能够压掉 这一维的空间。总的时间复杂度为 。
代码极短,并且十分好写。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include<iostream> #include<cstdio> using namespace std; const int N=1e3+10,M=1e4+10,K=15,mod=998244353; template<class T> inline void read(T &x) { x=0;bool f=0; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); if(f)x=~x+1; } template<class T,class ...T1> inline void read(T &x,T1 &...x1) { read(x),read(x1...); } int n,m,k; int p[N];
int f[M][K]; inline int adj(int x){return x>=mod?x-mod:x;} inline int qpow(int x,int k=mod-2) { int res=1; while(k) { if(k&1)res=1ll*res*x%mod; x=1ll*x*x%mod; k>>=1; } return res; } int main() { read(n,k,m); k=n-k+1; for(int i=1;i<=n;i++)read(p[i]); f[0][0]=1; for(int i=1;i<=n;i++) for(int j=m;j>=p[i];j--) for(int l=k;l;l--) f[j][l]=adj(adj(f[j][l]+f[j-p[i]][l-1])-f[j-p[i]][l]+mod); int ans=0; for(int i=1;i<=m;i++)ans=adj(ans+1ll*qpow(i)*f[i][k]%mod); printf("%lld\n",1ll*m*ans%mod); return 0; }
|
Gitalk 加载中 ...