JZOJ 5628. 【NOI2018模拟4.4】Travel

题目

有N个人出去旅行,第i个人去A国有Ai种游玩方式,去B国有Bi种游玩方式,问至少有C个人去A国的情况下,所有人的游玩方式有多少种不同的可能。
两种所有人的游玩方式不同当且仅当存在一个人选择的游玩方式不同,或选择去的国家不同。
接下来有P次修改,每次修改一个人的Ai和Bi。
总方案数要 mod 10007
这里写图片描述

解题思路

找突破口。
寻找是哪C个去A国,甚至去A国的人数要比C大,这种枚举方法显然不行。
但是,需要求一种方案,使得既知道去的人的范围,也知道有多少个人去A国。
题目要求支持单点修改。并且C很小。
所以利用线段树的每个点维护一个数组 f[] f [ ] f[i] f [ i ] 表示区间 [l,r] [ l , r ] 中去A国的人有 i i 个的答案。
通过线段树合并,则

tr[l,r].f[min(C,x+y)]+=tr[l,mid].f[x]+tr[mid+1,r].f[y]

通过 min(C,x+y) m i n ( C , x + y ) 可以表示 f[C] f [ C ] 是区间内大于等于 C C 的答案。
最后答案是tr[1,n].f[C]

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 93010
#define mo 10007
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int mat[22],sum[22];
};note tr[N*4];
int i,j,k,l,n,m,C,ans;
int a[N],b[N];
int read(){
    int fh=1,rs=0;char ch;
    while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')rs=(rs<<3)+(rs<<1)+(ch^'0'),ch=getchar();
    return fh*rs;
}
void write(int x){if(x>9)write(x/10);P(x%10+'0');}
void add(int &x,int y){x=(x+y)%mo;}
void update(int ps){
    int l=ps<<1,r=l|1,i,j,k;
    fo(i,0,C)tr[ps].mat[i]=0;
    fo(k,0,C-1)
        fo(i,0,k)
            add(tr[ps].mat[k],(tr[l].mat[i]*tr[r].mat[k-i])%mo);
    fo(i,0,C-1)if(tr[l].mat[i]){
        add(tr[ps].mat[C],(tr[l].mat[i]*tr[r].sum[C])%mo);
        add(tr[ps].mat[C],(tr[l].mat[i]*(mo-tr[r].sum[C-i-1]))%mo); 
    }
    if(tr[l].mat[C])add(tr[ps].mat[C],(tr[l].mat[i]*tr[r].sum[C])%mo);
    tr[ps].sum[0]=tr[ps].mat[0];
    fo(i,1,C)tr[ps].sum[i]=(tr[ps].sum[i-1]+tr[ps].mat[i])%mo;
}
void build(int ps,int l,int r){
    if(l==r){
        int i;
        fo(i,0,C)tr[ps].mat[i]=0;
        tr[ps].mat[1]=a[l];
        tr[ps].mat[0]=b[l];
        tr[ps].sum[0]=b[l];
        fo(i,0,C)tr[ps].sum[i]=(tr[ps].sum[i-1]+tr[ps].mat[i])%mo;
        return;
    }
    int wz=(l+r)>>1;
    build(ps<<1,l,wz);
    build((ps<<1)|1,wz+1,r);
    update(ps);
}
void change(int ps,int l,int r,int x){
    if(l==r){
        tr[ps].mat[1]=a[l];
        tr[ps].mat[0]=b[l];
        tr[ps].sum[0]=b[l];
        fo(i,0,C)tr[ps].sum[i]=(tr[ps].sum[i-1]+tr[ps].mat[i])%mo;
        return;
    }
    int wz=(l+r)>>1;
    if(x<=wz)change(ps<<1,l,wz,x);
        else change((ps<<1)|1,wz+1,r,x);
    update(ps);
}
int main(){
    freopen("travel.in","r",stdin);
    freopen("travel.out","w",stdout);
    n=read();C=read();
    fo(i,1,n)a[i]=read(),a[i]=a[i]%mo;
    fo(i,1,n)b[i]=read(),b[i]=b[i]%mo;
    build(1,1,n);
    m=read();
    while(m--){
        k=read();a[k]=read();b[k]=read();
        a[k]=a[k]%mo;b[k]=b[k]%mo;
        change(1,1,n,k);
        ans=tr[1].mat[C];
        write(ans),P('\n');
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值