神在夏至祭降下了神谕 树状数组和动态规划的结合

夏至祭是一场迎接祖灵于夏季归来同时祈求丰收的庆典。村里的男人会在广场上演出冬之军跟夏之军的战争,夏之军会打倒冬之军的大将冬男,再放火将他连山车一起烧掉。
谢尔吉斯村长已经选好了N个人参加演出,其中一些人负责演夏之军,另一些人负责演冬之军。由于人数众多,谢尔吉斯想把这N个人分成若干个连续的段。为了保证演出的顺利进行,每段的夏之君人数与冬之军人数之差的绝对值不能超过K。
谢尔吉斯想知道符合条件的划分方案有多少种。由于符合条件的方案有很多,你只要输出方案数除以1e9+7的余数。
Input
第一行两个整数N,K,意义如题目描述所示。
接下来一行N个整数,第i个整数为0表示第i个人是夏之军,1表示第i个人是冬之军。
Output
一行一个整数,表示符合条件的方案数除以1e9+7的余数。
Sample Input
4 1
0 0 1 1
Sample Output
5
Data Constraint
20%的数据保证,N≤20.
50%的数据保证,N≤8000.
另有15%的数据保证,所有的人都是夏之军。
另有15%的数据保证,K=0.
100%的数据保证,1≤N≤10^5,0≤K≤N。
Hint
样例说明
合法的5种方案分别为:
0 0 1 1
0 0 1|1
0|0 1 1
0|0 1|1
0|0|1|1
而0 0|1|1不是合法方案,
因为第一段”0 0”中夏之军人数为2,冬之军人数为0,人数之差的绝对值超过了K。

观察题目,若是我们假设了f(i),表示前i个人的划分方案,并且我们发现后面j个人(编号是 i+1~i+j)中,夏之军与冬之军的人数差低于K,则可以得到f(i+j)+=f(i),在这个转移的方程中,实际上是统计了在前(i+j)个人中,将第(i+1~i+j)个人划分为一组,而前i个人划分为一个块,这一块中可以分成很多组,可以划分的情况有f(i),因此,此时对于固定的(i+1~i+j)划分成一组的情况,根据组合数学的方法,可能性有 f(i+j)=f(i)×1,那么,我们将不同的i,j(i+j为定值)所产生的f(i+j)的和统计出来,就是我们需要的f(i+j)了,这个f(i+j)表示的就是前(i+j)个点划分为一块,这一块中的情况。因为我们明白,每次统计f(i+j)时,后面被划分为一组的时(i+1~i+j),随着i的不同,一组的情况也不一样。因此,我们可以知道这种统计方法统计得到的f(i+j)是没有重复情况出现的。
当然,由于读入的时候,0是夏之军,1是冬之军,我们很难统计出两军人数差,我们可以重新规划一下读入,当读入0时,我们把它赋值为-1,这样,对于第i个数a[i],我们可以使用正负的方法判别它们是夏之军还是冬之军。
而这种方法的好处,是我们需要统计前i个数中夏之军与冬之军的差时,我们可以利用这个方法,例如,前i个人sum[i]=Σa[j] (1≤j≤i) 那么,我们可以就得到了前i个数中,夏之军与冬之军的差为 abs(sum[i])。并且,当我们需要求取(left~right)个人中,夏之军与冬之军的人数差时,我们也可以用类似的方法,因为人数差为 Σa[i] (left≤i≤right) ,所以,它可以变式成 Σa[i] (1≤i≤right) - Σa[i] (1≤i≤left-1) 因此,第(left~right)个人中,夏之军与冬之军的人数差为 abs(sum[right]-sum[left-1])
这样,我们继续观察题目,可以发现,只有(left~right)的人数差满足 abs(sum[right]-sum[left-1])≤K,left~right才能够划分成一段,又因为划分成一段的限制只有人数差,并不限制其余的东西,那么,对于当前第i个点,我们可以枚举暴力得到答案 f(i)=Σf(j) (1≤j≤i-1,abs(sum[i]-sum[j])≤K)
当然,这并不是一个很好的办法,我们可以继续观察,发现在这个状态转移方程中,对于j的限制仅仅只有它在i前面。那么,我们就可以采取一种数据结构,每次算出 f(j) 后将它加入到这个数据结构中,那么就可以始终保证接下来要计算的 f(i) 将会在比它要早的 f(j) 中求取答案。
而另一个限制条件,则是(j+1~i)段的夏之军与冬之军的差不超过K,我们可以拆开它的绝对值,得到不等式 -K≤sum[i]-sum[j]≤K ,并且,移项使得sum[j]脱离出来,这样可以使得数据结构中我们只研究sum[j]的值,得到不等式 sum[i]-K≤sum[j]≤sum[i]+K。
所以,看到题目所给的数据范围,我们可以运用树状数组来求取满足(sum[i]-K≤sum[j]≤sum[i]+K)的所有的j的和 f(j) 。
使用树状数组,我们在加入数据的时候,首先要注意由于会出现负数,所以0的值要平移到数组的中间位置。并且,将f(j)的值加入到sum(j)的位置,然后查询统计我们可以采取做差的方法。当然,别忘了 f(0)=1 .
代码如下:

#include 

#define LL long long 

using namespace std;

const LL mod=1e9+7;
const int maxn=2e5+10;
int n,k,sum[maxn];
LL f[maxn]={1};

struct node{
    LL summation[maxn<<1];
    void add(int i,int size)
    {
        for (i+=maxn;i<=(maxn<<1);i+=(i&-i))
            summation[i]+=size;return ;
    }
    LL add_up_to(int i,LL SUM=0)
    {
        for (i+=maxn;i;i^=(i&-i))
            (SUM+=summation[i])%=mod;return SUM;
    }
    LL query(int left,int right)
    {
        return (add_up_to(right)-add_up_to(left-1)+mod)%mod;
    }
}q;

inline int read()
{
    int x=0;
    char c=getchar();
    while ((c<'0')||(c>'9'))
        c=getchar();
    while ((c>='0')&&(c<='9'))
        x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return x;
}

int main()
{
    n=read();k=read();
    q.add(0,1);
    for (int i=1;i<=n;i++)
        sum[i]=sum[i-1]+(read()?1:-1);
    for (int i=1;i<=n;i++)
        f[i]=q.query(sum[i]-k,sum[i]+k),q.add(sum[i],f[i]);
    printf("%lld",f[n]);
    return 0;
}

你可能感兴趣的:(神在夏至祭降下了神谕 树状数组和动态规划的结合)