浅谈一类组合问题的差分解法

先来一道题目:2020ICPC模拟赛A题

题意很简单,给定 a , b , c , d a,b,c,d a,b,c,d,求整数 x , y , z , k x,y,z,k x,y,z,k满足 0 ≤ x ≤ a , 0 ≤ y ≤ b , 0 ≤ z ≤ c , 0 ≤ k ≤ d 0\le x\le a,0\le y\le b,0\le z\le c,0\le k\le d 0xa,0yb,0zc,0kd x + y + z = k x+y+z=k x+y+z=k的解的数量。

首先可以考虑更简单的题意,即求 x + y = z x+y=z x+y=z的解的数量,

这个也不会,怎么办啊

那就考虑最简单的问题:已知整数 0 ≤ x ≤ a , 0 ≤ y ≤ b 0\le x\le a,0\le y\le b 0xa,0yb,如何求 x = y x=y x=y的方案数,

这个就是有手就行了,很显然,答案是 x , y x,y x,y取值范围的交,

更具体地说,设 w [ i ] w[i] w[i] x x x i i i处的取值方案数,则总的方案数就是 ∑ i = 0 b w [ i ] \sum_{i=0}^{b}w[i] i=0bw[i]

由于 x x x [ 0 , a ] [0,a] [0,a]内的每个整数点都有一种取法,即 w [ 0 ] = w [ 1 ] = . . . = w [ a ] = 1 w[0]=w[1]=...=w[a]=1 w[0]=w[1]=...=w[a]=1,我们就可以直接用一次差分更新 w [ 0 : a ] w[0:a] w[0:a]了,从而两次前缀和就求出了答案。

进一步考虑 x + y = z x+y=z x+y=z的情况(其中 0 ≤ x ≤ a , 0 ≤ y ≤ b , 0 ≤ z ≤ c 0\le x\le a,0\le y\le b,0\le z\le c 0xa,0yb,0zc),

w [ i ] w[i] w[i] x + y = i x+y=i x+y=i的方案数,则总的方案数就是 ∑ i = 0 c w [ i ] \sum_{i=0}^{c}w[i] i=0cw[i]

和上一个问题不同,对于取值范围内不同的 i i i w [ i ] w[i] w[i]的取值可能不相同,也就是说一次差分是届不到的,

因此我们考虑枚举 x x x,对于每个不同的 x x x求出此时所有可能的 y y y w [ x + y ] w[x+y] w[x+y]的贡献,

v [ i ] v[i] v[i] y y y i i i处取值的方案数,显然 v [ 0 ] = v [ 1 ] = . . . = v [ b ] = 1 v[0]=v[1]=...=v[b]=1 v[0]=v[1]=...=v[b]=1

对于一个 x x x y y y的所有取值分别会给 w [ x ] , w [ x + 1 ] , . . . , w [ x + b ] w[x],w[x+1],...,w[x+b] w[x],w[x+1],...,w[x+b]增加 v [ 0 ] , v [ 1 ] , . . . , v [ b ] v[0],v[1],...,v[b] v[0],v[1],...,v[b]的贡献,

又因 v [ 0 ] = v [ 1 ] = . . . = v [ b ] = 1 v[0]=v[1]=...=v[b]=1 v[0]=v[1]=...=v[b]=1,我们又可以用差分进行区间加操作了,

每次枚举一个 x x x,差分更新 w [ x ] w[x] w[x] w [ x + b ] w[x+b] w[x+b]的值,枚举完所有的 x x x后就通过两次前缀和求出了答案。

接下来回到 x + y + z = k x+y+z=k x+y+z=k的情况,已经可以通过这种方法解决了,

考虑先枚举 x x x,再枚举 x + y x+y x+y x + y + z x+y+z x+y+z,每次枚举结束后通过前缀和计算贡献和答案即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3000010;
ll a,b,c,d,ans;
ll v[N],w[N];//v[i]为x+y=i的方案数,w[i]为x+y+z=i的方案数
int main(){
    ios::sync_with_stdio(false);
    cin>>a>>b>>c>>d;
    for(int i=0;i<=a;i++){
        v[i]++;//对每个x更新[x,x+b]区间
        v[i+b+1]--;
    }
    for(int i=1;i<=a+b;i++){
        v[i]+=v[i-1];//前缀和求v[i]
    }
    for(int i=0;i<=a+b;i++){
        w[i]+=v[i];//对每个x+y更新[x+y,x+y+c]区间
        w[i+c+1]-=v[i];
    }
    for(int i=1;i<=a+b+c;i++){
        w[i]+=w[i-1];//前缀和求w[i]
    }
    for(int i=0;i<=d;i++){
        ans+=w[i];//对应k=i的情况加到答案上
    }
    cout<<ans;
}

杂谈:

其实这个东西是Awei几个月前做某道CF题在别人代码里看到的方法,当时没怎么看懂,昨晚CF赛后,他又跟我说起了这个(E题能用到),睡前终于想明白了,于是就写了篇博客(

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值