RELATIVNOST (线段树优化DP)
题面
现在有n个人要买你的画,第i个人最多买ai个彩色的画,bi个黑白的画,你现在想要至少有c个人买了彩色的画,接下来有q个修改,每次修改某个人的ai和bi。问你每次修改之后有多少种情况可以满足你的要求。
分析
由于(c)很小,不妨补集转化,设(f_{i,j})表示前(i)个人买了(j)张彩色的画的方案数。那么有(f_{i,j}=b_if_{i-1,j} +a_if_{i-1,j-1}).最终答案为(prod (a_i+b_i)-sum_{j=0}^{c-1}f_{i-1,j})
容易发现转移和(a_i,b_i)的顺序无关,可以用线段树优化转移.我们重新定义子状态(f_{x,j})表示子树内有(j)张彩色的画的方案数,那么转移就类似卷积的形式。
[f_{x,i+j}=sum f_{lson(x),i}cdot f_{rson(x),j}(i+j leq c)
]
边界条件(f_{x,0}=b_l,f_{x,1}=a_l),其中(x)是一个叶子节点([l,l])
还有一个细节,我们要动态维护((a_i+b_i))的乘积,那么修改时要乘上原来的((a_i+b_i))逆元。但如果((a_i+b_i) mod 10^4+7=0)时不存在逆元,就无法维护乘积。因此还要额外记录一个这样的数的个数,然后记录其他数的乘积即可。另外,要避开这个分类讨论也可以直接把超过(c)的累加到(f_{i,c})上,这样转移就变成了$$f_{x,min(i+j,c)}=sum f_{lson(x),i}cdot f_{rson(x),j}$$,最后直接输出(f_{1,c})即可,见第二份代码.
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 10007
#define maxn 100000
using namespace std;
int n,c,q;
int a[maxn+5],b[maxn+5];
inline int fast_pow(int x,int k){
int ans=1;
while(k){
if(k&1) ans=ans*x%mod;
x=x*x%mod;
k>>=1;
}
return ans;
}
inline int inv(int x){
return fast_pow(x,mod-2);
}
struct segment_tree{
struct node{
int l;
int r;
int dp[22];//dp[i][j]为彩色人数=j的方案数,再用总的减去
}tree[maxn*4+5];
void push_up(int x){//dp与顺序无关,所以可以用线段树转移
for(int i=0;i<=c;i++) tree[x].dp[i]=0;
for(int i=0;i<=c;i++){
for(int j=0;j<=c;j++){
if(i+j<=c) tree[x].dp[i+j]=(tree[x].dp[i+j]+tree[x<<1].dp[i]*tree[x<<1|1].dp[j]%mod)%mod;
}
}
}
void build(int l,int r,int x){
tree[x].l=l;
tree[x].r=r;
if(l==r){
tree[x].dp[0]=b[l]%mod;
tree[x].dp[1]=a[l]%mod;
return;
}
int mid=(l+r)>>1;
build(l,mid,x<<1);
build(mid+1,r,x<<1|1);
push_up(x);
}
void update(int upos,int x){
if(tree[x].l==tree[x].r){
tree[x].dp[0]=b[tree[x].l]%mod;
tree[x].dp[1]=a[tree[x].l]%mod;
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if(upos<=mid) update(upos,x<<1);
else update(upos,x<<1|1);
push_up(x);
}
}T;
int main(){
#ifndef LOCAL
freopen("relati.in","r",stdin);
freopen("relati.out","w",stdout);
#endif
int p,x,y;
scanf("%d %d",&n,&c);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
int sum=1,cnt=0;
for(int i=1;i<=n;i++){
if((a[i]+b[i])%mod==0) cnt++;//统计0的数量,因为不存在逆元
else sum=sum*(a[i]+b[i])%mod;
}
T.build(1,n,1);
scanf("%d",&q);
while(q--){
scanf("%d %d %d",&p,&x,&y);
if((a[p]+b[p])%mod==0) cnt--;
else sum=sum*inv(a[p]+b[p])%mod;
if((x+y)%mod==0) cnt++;
else sum=sum*(x+y)%mod;
a[p]=x;b[p]=y;
T.update(p,1);
int ans=0;
for(int i=0;i<c;i++) ans=(ans+T.tree[1].dp[i])%mod;
if(cnt>0) sum=0;
printf("%d
",(sum-ans+mod)%mod);
}
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 10007
#define maxn 100000
using namespace std;
int n,c,q;
int a[maxn+5],b[maxn+5];
inline int fast_pow(int x,int k){
int ans=1;
while(k){
if(k&1) ans=ans*x%mod;
x=x*x%mod;
k>>=1;
}
return ans;
}
inline int inv(int x){
return fast_pow(x,mod-2);
}
struct segment_tree{
struct node{
int l;
int r;
int dp[22];//dp[i][j]为彩色人数=j的方案数,再用总的减去
}tree[maxn*4+5];
void push_up(int x){//dp与顺序无关,所以可以用线段树转移
for(int i=0;i<=c;i++) tree[x].dp[i]=0;
for(int i=0;i<=c;i++){
for(int j=0;j<=c;j++){
tree[x].dp[min(i+j,c)]=(tree[x].dp[min(i+j,c)]+tree[x<<1].dp[i]*tree[x<<1|1].dp[j]%mod)%mod;
}
}
}
void build(int l,int r,int x){
tree[x].l=l;
tree[x].r=r;
if(l==r){
tree[x].dp[0]=b[l]%mod;
tree[x].dp[1]=a[l]%mod;
return;
}
int mid=(l+r)>>1;
build(l,mid,x<<1);
build(mid+1,r,x<<1|1);
push_up(x);
}
void update(int upos,int x){
if(tree[x].l==tree[x].r){
tree[x].dp[0]=b[tree[x].l]%mod;
tree[x].dp[1]=a[tree[x].l]%mod;
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if(upos<=mid) update(upos,x<<1);
else update(upos,x<<1|1);
push_up(x);
}
}T;
int main(){
#ifndef LOCAL
freopen("relati.in","r",stdin);
freopen("relati.out","w",stdout);
#endif
int p,x,y;
scanf("%d %d",&n,&c);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
T.build(1,n,1);
scanf("%d",&q);
while(q--){
scanf("%d %d %d",&p,&x,&y);
a[p]=x;b[p]=y;
T.update(p,1);
printf("%d
",T.tree[1].dp[c]);
}
}