[Ynoi2013]对数据结构的爱

( exttt {data-2020-10-24})

修正了一些语言描述上的错误,以及对于一些地方使用了更好的表述。


( exttt {data-2020-9-17})

修正了一些公式和语言描述上的错误。


对于当前要经过的一段区间 (x),若给定不同初始值的 (sum) ,其减 (p) 的次数 (k) 也不同,那么 (k)(sum) 是否满足什么关系呢?

意淫一下感觉是非递减,看能不能写清楚。

考虑增大 (sum) ,此时虽然模 (p) 的位置并不确定,但是 (k) 要么不变,要么增大,理解一下这个过程中取模位置和次数的变化:

先扫过去,对于 (iin[r_1,r_2)) 这段范围内的数,过程量 (sum_i) 都增加 (num)(增大的量),原来满足 (sum_i<p) 这个条件的有:(sum_i+num<p)

然后 (r_2) 这个位置满足:(sum_{r_2}<p)(sum_{r_2}+numge p),此时 (r_2) 这个位置取模。

这时有两种情况:

(1. num-pge 0)

对于 (iin[r_2,r_3)) 这段范围内的数,过程量 (sum_i) 都增加 (num-p),后同上。

(2. num-p<0)

实际上这个子问题就是 (num<0) 的实现。

此时后面一段的过程量减小,直到一个原本应当进行一次减 (p) 操作的位置不减了,而这个情况可以看做两个位置是否取模的真假(true or false)交换,可以保证 (k) 不变。然后后面的又是变成跟原来一样的子问题。

但也有可能后面没有这个真变假(原本取模变成不取模)的位置,也就是减 (p) 次数变为 (k+1)

综上可得:对于每个使 (k+1) (对 (k) 产生了 (1) 的贡献)的位置,最多有一个使 (k-1) 的位置与其对应,所以 (k)(sum) 非严格单调递增。

接下去就是考虑实现了:

(k)(sum) 非严格单调递增,同时有 (0le kle lens),其中 (lens) 为块长。

更严格来说,设原来的取模次数为 (sl),有:

[egin {cases} slle kle lens,sum>0\ 0le kle sl,sum<0end {cases} ]

这里 (k)(sum) 的关系十分相似于函数 (y=leftlfloor x ight floor)

那么如果二分 (sum) 的话,扫过这个块模拟,就可以得到这个 (sum) 对应的 (k)

也就是说可以预处理出对于每个 (k),最小的 (sum) 值,设这个东西为 (f(k)),特殊的,(f(0)=-infty)

那么如果前面的值处理好了传给当前区间,就可以求出对应的 (k) 然后加上这段区间的和,减去取模次数乘模数。

分块的话:

预处理复杂度:(operatorname O(log(2e9 imes lens) imes n)) 乘上一个常数。

查询的复杂度:(operatorname O(m imes frac{n}{lens} imes log lens))

最优的极限复杂度为:(1.3 imes 10^{10})

分块打了,炸了,一分都骗不到


看了题解,不是傻傻地每一块单独算,而是“合并”左右子树的信息。

处理好自己左子树和右子树的信息以后,设当前节点为 (i),左儿子为 (l),右儿子为 (r)

[f_{i,x+y}=min(f_{i,x+y},max(f_{l,x},f_{r,y}+p imes x-add_l) ]

其中,(add_l) 表示 (l) 这段区间的和,(x,y) 分别为左右区间取模的次数。

关于@81179332_ 提到的 (c_{x+1}-c_xge p) 的证明(在我这里写成 (f_{x+1}-f_xge p)),我的理解是:

因为 (f_x) 对应的是取模 (x)(sum) 的最小值,那么当 (sum=f_x-1),有一个本来要取模的位置不取模了,所以只可能是某个过程量在 (sum=f_x) 的时候刚好等于 (0)(即这步操作减 (p) 以后等于 (0))。

(sum=f_x+p-1) 时,可以用前面 (operatorname A) 的处理过程结合上一段来理解,(k) 一定等于 (x),所以相邻两个值的差至少为 (p)


那么对于上面的式子来说,有:

[egin {cases}f_{r,y}+p imes x-add_lge f_{r,y-1}+ p imes (x+1)-add_l \ f_{l,x}+ple f_{l,x+1}end {cases} ]

并且一个合法的二元组 ((x,y)) 需满足:(f_{l,x+1}-1+add_l-p imes xge f_{r,y})

对于一个常数 (k=x+y),若 (k) 已经被 ((x,y)) 更新过了,那么 ((x+1,y-1)) 还有存在的必要吗?

由前面可以得到:

[f_{r,y-1}+p imes x-add_lle f_{r,y}+p imes x-add_lle f_{l,x+1}-1 ]

那么此时对于二元组 ((x+1,y-1))(max(f_{l,x+1},f_{r,y-1}+p imes x-add_l)) 取得一定是前面的那项。

前面那项和 (y) 又没有任何关系,所以有:

如果存在合法二元组 ((x,y)),则 ((x+1,y-1)) 对答案无贡献。(当然,要考虑 (x) 的贡献。即如果 ((x,|r|)) 符合二元组,为了让 (x+1) 的贡献能够更新 (f_{i,x+|r|+1}),之后 ((x+1,|r|)) 也得考虑)

注:这里的 (|r|) 指区间 (r) 的长度,即 (y) 的最大取值。

那么就可以双指针使得复杂度正确了。

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MAN=1e6+3;
const LL MAX=1e18;
struct milk
{
    int l,r;
    int ls,rs;
    int lens;
    LL sum;
    vector <long long> q;
}t[MAN<<1];
int nam;
int n,m;
LL ans=0;
LL p;
int rin()
{
    int s=0;
    char c=getchar();
    bool bj=0;
    for(;(c>'9'||c<'0')&&c!='-';c=getchar());
    if(c=='-')c=getchar(),bj=true;
    for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
    if(bj)return -s;
    return s;
}
inline void make_tree(int l,int r,int i)
{
    t[i].l=l;t[i].r=r;
    t[i].lens=t[i].r-t[i].l+1;
    t[i].ls=t[i].rs=0;
    t[i].q.push_back(-MAX);//f[0]为负无穷
    if(l==r)
    {
        t[i].sum=rin();
        t[i].q.push_back(p-t[i].sum);
        t[i].q.push_back(MAX);
        return;
    }
    int mid=(l+r)>>1;
    t[i].ls=++nam;make_tree(l,mid,nam);
    t[i].rs=++nam;make_tree(mid+1,r,nam);
    t[i].sum=t[t[i].ls].sum+t[t[i].rs].sum;
    for(int _=l;_<=r+3;_++)t[i].q.push_back(MAX);
    l=t[i].ls;r=t[i].rs;
    int x,y;
    x=y=0;
    for(;x<=t[l].lens;x++)
    {
        if(y>t[r].lens)y--;
        for(;y<=t[r].lens;y++)
        {
            if(t[l].q[x+1]-1-p*x+t[l].sum<t[r].q[y]){if(y!=0)y--;break;}//当前的 x 无法满足后面减 y 次的需求
            t[i].q[x+y]=min(t[i].q[x+y],max(t[l].q[x],t[r].q[y]+p*x-t[l].sum));
        }
    }
    return;
}
inline void cheak(int l,int r,int i)
{
    if(l<=t[i].l&&t[i].r<=r)
    {
        int left=1,right=t[i].r-t[i].l+1;
        int last=0;
        for(;left<=right;)
        {
            int mid=(left+right)>>1;
            if(ans>=t[i].q[mid])last=mid,left=mid+1;
            else right=mid-1;
        }
        ans+=t[i].sum;
        ans-=p*last;
        return;
    }
    if(t[i].ls==0)return;
    if(t[t[i].ls].r>=l)cheak(l,r,t[i].ls);
    if(t[t[i].rs].l<=r)cheak(l,r,t[i].rs);
    return;
}
int main()
{
    int i,j;
    n=rin();m=rin();p=rin();
    nam=1;
    make_tree(1,n,1);
    for(i=1;i<=m;i++)
    {
        int l,r;
        l=rin();r=rin();
        ans=0;
        cheak(l,r,1);
        printf("%lld
",ans);
    }
    return 0;
}
$$ exttt{Dirty Deeds Done Dirt Cheap}$$
原文地址:https://www.cnblogs.com/zjjws/p/13736503.html