[BZOJ3636] 教义问答手册

有三个不是那么暴力的复杂度
O(NlogN * L^3) // 在线
O(N sqrt(NL)) // 在线
O(NlogN*L + QL + QlogN) // 离线
然而只有离线的那个是滋磁的>..<
4..4
等我细细道来

题意:有一个长度为N的序列(元素有正有负),再来一个L
Q组询问,每次问区间[l,r]中,选出若干个不相交的长度正好是L的区间,使得和最大

总共有N-L+1个区间嘛..再来一个长度N-L+1的序列B,表示原序列A中每个长度正好为L的区间的价值。说人话就是 B[i]=i+L1j=iA[i]

那题意就变成了,每次询问B中的一个区间,在这个区间中选若干个数,在满足任意两个元素的下标的差的绝对值都>=L的条件下,使得被选中的值之和最大(这转换很显然啊>..<其实不转换直接来也可以)

算法一:线段树

时间O(L^3NlogN),空间O(NL^2)

我们想要知道一个区间的答案并修改…第一眼感觉是线段树,可惜合并起来就非常困难啦…如果只知道两个区间的答案,要拼起来,显然是不可以的,因为你并不能保证左边那个区间的最右边的被选元素和右边那个区间的最左边的被选元素没有靠得太近。那么用一个矩阵f[x][y]表示一个区间的信息,表示这个区间,删去左边的x个元素和右边的y个元素后的答案,这样就能合并啦>..< f[x][y]=maxL1i=0(fleft[x][i]+fright[L1i][y]) 合并两个区间的时间是O(L^3)于是就有了O(L^3NlogN)的做法,这只能做L较小的情况

算法二:分块

时间空间都是O(Nsqrt(NL))

如果只有一个询问,显然有O(N)算法。设序列为B,f[i]为前i个数的答案,那么
f[i]=max(f[i1],f[iL]+B[i])(i>=L)
f[i]=max(f[i1],B[i])(i<L)

如果我们知道了[l, r], [l, r + 1], [l, r + 2], …, [l, r + L - 1]的答案和B[r+L],就能得到[l, r + L]的答案,那就分块吧。

把序列分成sqrt(N/L)块,每块里选择L个连续的特殊点,对于每个特殊点,预处理出所有以这个特殊点为左端点的询问的答案,预处理的复杂度是O(Nsqrt(NL))。处理一个询问[l, r]时,找出l右边离l最近的那一段L个特殊点,然后移过来(分的是sqrt(N/L)块,所以移动距离是sqrt(NL)),那么处理一个询问就是O(sqrt(NL)),总时间复杂度还是sqrt(Nsqrt(NL))

算法三:分治

时间O(LNlogN + QL + QlogN) 空间O(NL) 可AC

离线处理所有询问。
当前区间是[x,y]时,只处理经过mid = (x + y) / 2的询问,剩下的只存在于左半或右半的询问在左右两半递归下去。
那么怎么处理经过mid的询问呢?
O((y-x)L)时间内,处理出所有[i, j] (x <= i <= j && mid - L < j <= mid)的答案和[i, j] (mid < i <= mid + L && i <= j <= y)的答案
现在来看一个经过mid的询问[ql, qr],你需要将区间[ql, m]和[m+1, qr]合并起来,不过不是O(L^3)而是O(L)因为你只想知道f[0][0]的值
所以这题就AC了!>..<

#include <cstdio>
int a[100001], N, L, OUT[100001], dp[50][100001];
struct query
{
    int l, r, id;
}
q[100001], tmpq[100001];
int I()
{
    char c = getchar();
    int r = 0, f = 0;
    while ((c < 48 || c > 57) && c != '-')
        c = getchar();
    if (c == '-')
        f = 1, c = getchar();
    while (c > 47 && c < 58)
        r = (r << 3) + r + r + c - 48, c = getchar();
    return f ? -r : r;
}
void init()
{
    N = I(), L = I();
    for (int i = 1; i <= N; i++)
        a[i] = a[i - 1] + I();
    for (int i = N; i > L; i--)
        a[i] -= a[i - L];
}
inline int max(int x, int y)
{
    return x > y ? x : y;
}
void NiroBC(int al, int ar, int ql, int qr)
{
    if (ql > qr)
        return;
    int m = al + ar >> 1;
    for (int rem = 0; rem < L; rem++)
    {
        int *DP = dp[rem];
        DP[m - rem] = max(0, a[m - rem]);
        for (int i = m - rem - 1; i > m - rem - L; i--)
            DP[i] = max(DP[i + 1], a[i]);
        for (int i = m - rem - L; i >= al; i--)
            DP[i] = max(DP[i + 1], DP[i + L] + a[i]);
        DP[m + rem + 1] = max(0, a[m + rem + 1]);
        for (int i = m + rem + 2; i <= m + rem + L; i++)
            DP[i] = max(DP[i - 1], a[i]);
        for (int i = m + rem + L + 1; i <= ar; i++)
            DP[i] = max(DP[i - 1], DP[i - L] + a[i]);
    }
    int DL = ql - 1, DR = qr + 1;
    for (int i = ql; i <= qr; i++)
        if (q[i].r <= m)
            tmpq[++DL] = q[i];
        else
            if (q[i].l > m)
                tmpq[--DR] = q[i];
            else
            {
                OUT[q[i].id] = max(dp[0][q[i].l], dp[0][q[i].r]);
                for (int LC = 0; LC < L; LC++)
                    if (LC <= m - q[i].l && L - LC <= q[i].r - m)
                        OUT[q[i].id] = max(dp[LC][q[i].l] + dp[L - 1 - LC][q[i].r], OUT[q[i].id]);
            }
    for (int i = ql; i <= DL; i++)
        q[i] = tmpq[i];
    for (int i = DR; i <= qr; i++)
        q[i] = tmpq[i];
    NiroBC(al, m, ql, DL), NiroBC(m + 1, ar, DR, qr);
}
int main()
{
    init();
    int Q = I(), QQ = 0;
    for (int i = 1; i <= Q; i++)
    {
        int l = I() + L - 1, r = I();
        if (r - l + 1 >= L + L)
            q[++QQ] = (query) { l, r, i };
        else
            if (r - l + 1 <= L)
            {
                for (int j = l; j <= r; j++)
                    if (a[j] > OUT[i])
                        OUT[i] = a[j];
            }
            else
            {
                int pre = 0;
                for (int j = l; j <= r; j++)
                {
                    if (j - L >= l && a[j - L] > pre)
                        pre = a[j - L];
                    if (pre + a[j] > OUT[i])
                        OUT[i] = pre + a[j];
                }
            }
    }
    NiroBC(L, N, 1, QQ);
    for (int i = 1; i <= Q; i++)
        printf("%d\n", OUT[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值