有限背包计数问题

https://class.51nod.com/Html/Textbook/ChapterIndex.html#chapterId=335&textbookId=126

https://class.51nod.com/Html/Challenge/Problem.Html#problemId=1597

如有限背包计数问题

发现对于物品 \(i\) 最多填充 \(i*i\),而对于 \(i>\sqrt n\) 可以视为完全背包,所以我们分为两类。

对于小的那一类,可以用临时数组维护对应余数的和(像多重背包一样),然后超出范围的再减去。

大的那一类,考虑每次添加一个新的数或者让所有数+1(这样就能涵盖所有的情况,所有数+1相当于偏移,你想让它等于多少,就在最后一个数加入前特定时间加入即可)。

复杂度 \(O(n\sqrt n)\)

#include<iostream>
#include<cmath>
#include<cstring>
#define add(a,b) (a+=b)>=mod&&(a-=mod)
#define sub(a,b) (a-=b)<0&&(a+=mod)
using namespace std;
const int N=100010,mod=23333333,Q=320;
int n,q,tmp[Q],f[2][N],g[2][N];
int main(){
    #ifdef LOCAL
    freopen("1.txt","r",stdin);
    #endif
    #ifndef LOCAL
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    #endif
    cin>>n;
    q=sqrt(n)+1;
    f[0][0]=1;
    for(int i=1;i<q;++i){
        int I=i&1,L=!I;
        memset(tmp,0,i*4);
        int mo=-1;
        for(int j=0;j<=n;++j){
            ++mo;
            if(mo==i)mo=0;
            add(tmp[mo],f[L][j]);
            f[I][j]=tmp[mo];
            if(j>=i*i)sub(tmp[mo],f[L][j-i*i]);//can't reach
        }
    }
    int qq=(q-1)&1;
    // for(int i=1;i<q;++i,puts(""))
    //     for(int j=0;j<=n;++j)
    //         cout<<f[i][j]<<' ';
    g[0][0]=1;
    int ans=f[qq][n],nq=n/q;
    for(int i=1;i<=nq;++i){int I=i&1,L=!I;
        g[I][0]=0;
        for(int j=q;j<=n;++j){
            g[I][j]=g[L][j-q];
            add(g[I][j],g[I][j-i]);
            add(ans,1ll*g[I][j]*f[qq][n-j]%mod);
        }}
    cout<<ans;
    return 0;
}
posted @ 2024-07-21 22:04  wscqwq  阅读(5)  评论(0编辑  收藏  举报