【JZOJ4941】宝石魔术 题解

题目大意

       有 Q 种操作。
       1、加入一个魔力为 x 的宝石;
       2、删去一个魔力为 x 的宝石(保证操作合法);
       3、询问有多少种选取宝石的方法,使得选取的魔力和为 x;(不同下标的宝石视为不同,即两种方法不同当且仅当两种方法选取的宝石有不同)
       4、询问有多少种选取宝石的方法,使得选取的魔力和为 x。(不同魔力的宝石视为不同,即两种方法不同当且仅当某一种魔力值的宝石数量不同)

       Q, x<=10^4,时限 3s。

解法1

       这种东西肯定先想到 dp。
       设 f[i] 表示操作3条件下,魔力和为 i 的方案数;设 g[i] 表示操作4的方案数。f 是个01背包,而 g 是个多重背包。

       然后这就是个动态dp了。
       f 的维护比较简单,插入就倒着扫一遍,删除就正着扫一遍。
       g 的维护比较麻烦,主要是它的转移跟数量有关。实现程序时瞎搞一下也可以使其成为线性。
       一次dp是 O(x) 的,所以总时间是 O(Qx)。

解法2

       考虑dp的生成函数。我贴个题解。(建议把图拖到新标签页中查看,字会大一点)
       以下的 cnt[i] 表示魔力值为 i 的宝石数量,f[i][j]表示01背包的dp

这里写图片描述

代码

解法1

#include<cstdio>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long LL;

const int maxn=1e4+5;
const LL mo=1e9+7;

int n,cnt[maxn];
LL f[maxn],g[maxn];

int ReadInt()
{
    char ch=getchar();
    int data=0;
    while (ch<'0' || ch>'9') ch=getchar();
    do{
        data=data*10+ch-'0';
        ch=getchar();
    } while (ch>='0' && ch<='9');
    return data;
}
char ReadChar()
{
    char ch=getchar();
    while (ch!='i' && ch!='q' && ch!='d' && ch!='R' && ch!='T') ch=getchar();
    return ch;
}

LL gj[maxn],gp[maxn];
void NewG(int x,int z)
{
    if (cnt[x])
    {
        fo(i,0,x-1) gj[i]=g[i];
        fo(i,x,n)
        {
            g[i]=(g[i]-gj[i-x]+mo)%mo;
            gj[i]=(gj[i-x]+g[i])%mo;
            if (i-cnt[x]*x>=0) gj[i]=(gj[i]-g[i-cnt[x]*x]+mo)%mo;
        }
    }
    cnt[x]+=z;
    if (cnt[x])
    {
        memcpy(gp,g,sizeof(g));
        fo(i,0,x-1) gj[i]=gp[i];
        fo(i,x,n)
        {
            g[i]=(g[i]+gj[i-x])%mo;
            gj[i]=(gj[i-x]+gp[i])%mo;
            if (i-cnt[x]*x>=0) gj[i]=(gj[i]-gp[i-cnt[x]*x]+mo)%mo;
        }
    }
}

int Q;
int main()
{
    n=10000;
    f[0]=g[0]=1;

    scanf("%d",&Q);
    while (Q--)
    {
        char ty=ReadChar();
        if (ty=='i')
        {
            int x=ReadInt();

            fd(i,n,x) f[i]=(f[i]+f[i-x])%mo;
            NewG(x,1);
        } else if (ty=='d')
        {
            int x=ReadInt();

            fo(i,x,n) f[i]=(f[i]-f[i-x]+mo)%mo;
            NewG(x,-1);
        } else
        {
            char now=ReadChar();
            int x=ReadInt();

            printf("%lld\n",(now=='T') ?f[x] :g[x] );
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值