In mathematics you don't understand things, you just get used to them.

标签 data structures 下的文章

Description

Link.

区间查 $x$ 的倍数并除掉,区间查和。

Solution

平衡树。

首先有个基本的想法就是按 $a_{i}$ 开平衡树,即对于每个 $a_{i}$ 都开一棵平衡树,共5e5棵,第 $i$ 棵平衡树维护的值是所有 $a_{i}$ 的倍数在原数组中的下标,用处后面讲。

注意到一个小性质,一个正整数 $A$ 最多被整除 $\log_{2}A$ 次,这个很好想,每次都至少减少一半。可以当成一个 trick 记下来。

整个区间的数最多被除 $\sum_{i=1}^{n}\log_{2}a_{i}$ 次,区间和的操作可以用树状数组操作实现,则整体的操作复杂度为 $\Theta(\sum_{i=1}^{n}\log_{2}a_{i}+\log_{2}a_{i})$。

开头提到了对于每个 $a_{i}$ 我们都开一棵平衡树,作用就体现在这里。因为如果要保证正确的时间复杂度,我们需要快速的找到需要执行操作的数。

这里我采用的是 FHQ-Treap。

我们可以用两次 split 操作在 $x$ 的平衡树中提取出当前的询问区间,由于我们是以下标为平衡树维护的权值,所以我们用按值分裂即可提取出区间。

然后我们就在提取出的子树中 DFS 遍历,然后暴力操作,把操作后依然是 $x$ 的倍数的数记录下来,操作完后用这个数组再建一棵临时树然后和之前 split 出来的子树一起合并回去。

操作之前记得预处理每个数的所有约数,这个简单,直接用 vector 即可。

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <ctime>
#include <queue>

using namespace std;
typedef long long t_t;

const int Maxn = 1e5 + 5;
const int Maxa = 5e5 + 5;
int n, m, xxx, zip, tot, isa[Maxn], bin[Maxa], root[Maxa];
struct Treap
{
    int l, r, key, val;
} t[Maxa * 230];
struct Fenwick_Tree
{
    t_t bit[Maxn];
    
    void ins(int x, t_t v)
    {
        for (; x <= n; x += x & -x)     bit[x] += v;    
    }
    
    t_t sum(int x)
    {
        t_t ret = 0;
        for (; x; x -= x & -x)    ret += bit[x];
        return ret;
    }
} fwt;
vector < int > vec[Maxa];

int newnode(int val)
{
    t[++tot].val = val;
    t[tot].key = rand();
    return tot;
}

void split(int now, int val, int &x, int &y)
{
    if (now == 0)    x = y = 0;
    else
    {
        if (t[now].val <= val)
        {
            x = now;
            split(t[now].r, val, t[now].r, y);
        }
        else
        {
            y = now;
            split(t[now].l, val, x, t[now].l);
        }
    }
}

int merge(int x, int y)
{
    if (x == 0 || y == 0)    return x | y;
    if (t[x].key > t[y].key)
    {
        t[x].r = merge(t[x].r, y);
        return x;
    }
    else
    {
        t[y].l = merge(x, t[y].l);
        return y;
    }
}

int build(int l, int r)
{
    if (l > r)    return 0;
    int mid = (l + r) >> 1;
    int now = newnode(bin[mid]);
    t[now].l = build(l, mid - 1);
    t[now].r = build(mid + 1, r);
    return now;
}

void dfs(int now, int val)
{
    if (now == 0)    return ;
    dfs(t[now].l, val);
    if (isa[t[now].val] % val == 0)
    {
        fwt.ins(t[now].val, -isa[t[now].val]);
        isa[t[now].val] /= val;
        fwt.ins(t[now].val, isa[t[now].val]);
    }
    if (isa[t[now].val] % val == 0)    bin[++zip] = t[now].val;
    dfs(t[now].r, val);
}

int tx, ty, tp;
void change(int l, int r, int x)
{
    if (x == 1)     return ;
    split(root[x], r, tx, tp);
    split(tx, l - 1, tx, ty);
    zip = 0;
    dfs(ty, x);
    root[x] = merge(tx, merge(build(1, zip), tp));
}

signed main()
{
    srand((114514 - 1) / 3 - 4);
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &isa[i]);
        fwt.ins(i, isa[i]);
        xxx = max(xxx, isa[i]);
    }
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j * j <= isa[i]; ++j)
        {
            if (isa[i] % j == 0)
            {
                vec[j].push_back(i);
                if (j * j != isa[i])    vec[isa[i] / j].push_back(i);
            }
        }
    }
    for (int i = 1; i <= xxx; ++i)
    {
        zip = 0;
        for (unsigned j = 0; j < vec[i].size(); ++j)    bin[++zip] = vec[i][j];
        root[i] = build(1, zip);
    }
    for (int i = 0; i < m; ++i)
    {
        int t, l, r, x;
        scanf("%d %d %d", &t, &l, &r);
        if (t == 1)
        {
            scanf("%d", &x);
            change(l, r, x);
        }
        else    printf("%lld\n", fwt.sum(r) - fwt.sum(l - 1));
    }
    return 0;
}

Description

Link.

无修改区间求逆序对。

Solution

首先有一个显然的 $\Theta(N\sqrt{N}\log_{2}N)$ 做法,由于过不了所以我就不废话。

其实有了 $\Theta(N\sqrt{N}\log_{2}N)$ 的过不去做法,我们就可以根据这个思路然后预处理解决问题。

我们需要处理的信息有:

  1. 散块的逆序对数量
  2. 以块为单位的区间逆序对数量

那么我们需要处理的数组就有以下几个:

  1. previous[i] 表示 $i$ 到 该块开头的逆序对数量。
  2. suffix[i] 同理。
  3. block[i][j] 表示前 $i$ 个块中 $\leq j$ 元素个数。
  4. intervals[i][j] 表示以块为单位的区间 $[i,j]$ 中的逆序对数量。

讲讲预处理方法。

  1. previous[i]suffix[i] 的处理方法都很显然,可以一直扫着然后FWT扫就行。
  2. block[i][j] 可以递推,递推式为 block[i][j]=block[i+1][j]+block[i][j-1]-block[i+1][j-1]+cont(i,j)。其中 cont(i,j) 表示计算对应块的逆序对数。
  3. intervals[i][j] 每次循环到块的开头继承上一个块的贡献即可。

计算贡献的方法很简单,归并即可。mrsrz讲得也挺清楚的,我这里就不再赘述,主要讲讲怎么卡常。

首先我们可以把主函数里的所有循环全部展开,经过实践参数传8的时候跑得比较快。

然后八聚氧先加上,luogu O2也开着。

再其次快读fread快输fwrite,这些都是卡常的标配。

然后就把能拿出来的结构体拿出来,实在不能就不管了。

然后去STL,pair vector能去就去。

然后long long开在正确的地方,不要无脑replace。

函数inline,循环register。虽然可能作用不大但是可以先加上。

然后调块长,经过无数次实践发现取150~170较为优秀。

然后加了过后发现就算rp再好也只有60pts。

然后谷歌搜索硫酸的化学式H₂SO₄,给评测机喂硫酸(idea来自SyadouHayami)。

然后本来交了5页都过不了,这下再交两次就过了。

// 省略八聚氧
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int Maxn = 1e5 + 5;
const int Maxm = 650;
const int each = 160;
int n, m, blocks, Lp[Maxn], Rp[Maxn], isa[Maxn], head[Maxn], tail[Maxn], sorted[Maxn], belong[Maxn], previous[Maxn], suffix[Maxn], block[Maxm][Maxn];
long long intervals[Maxm][Maxn];
struct Holy_Pair
{
    int first, second;

    bool operator < (const Holy_Pair& rhs) const
    {
        return first < rhs.first;
    }
} current[Maxn];
struct Fenwick_Tree
{
    int fwt[Maxn];
    
    inline void Modify(int x, int v)
    {
        for (; x + 5 <= Maxn; x += x & -x)    fwt[x] += v;
    }

    inline int Query(int x)
    {
        int ret = 0;
        for (; x; x ^= x & -x)    ret += fwt[x];
        return ret;
    }
} FWT;

#define io_e '\0'
#define io_s ' '
#define io_l '\n'
namespace Fast_IO
{
... // 省略快读
}  // namespace Fast_IO

using Fast_IO::read;
using Fast_IO::write;

inline Holy_Pair make_pair(int first, int second)
{
    Holy_Pair ret;
    ret.first = first;
    ret.second = second;
    return ret;
}

inline int Merge_Vct(int rhs[], int shr[], int szl, int szr)
{
    int itl = 1, itr = 1;
    int ret = 0, ctr = 1;
    while (itl <= szl && itr <= szr)
    {
        if (rhs[itl] < shr[itr])     ++itl, ++ctr;
        else
        {
            ret += szl - ctr + 1;
            ++itr;
        }
    }
    return ret + szr - szr;
}

inline int Merge_Idx(int st1, int st2, int sz1, int sz2)
{
    int ret = 0, id1 = st1 + 1, id2 = st2 + 1;
    sz1 += st1, sz2 += st2;
    while (id1 <= sz1 && id2 <= sz2)
    {
        if (sorted[id1] < sorted[id2])        ++id1;
        else
        {
            ret += sz1 - id1 + 1;
            ++id2;
        }
    }
    return ret;
}

inline void Behavior(int l, int r, long long &ans)
{
    int itl = 0, itr = 0;
    if (belong[l] == belong[r])
    {
        for (int i = head[belong[l]]; i <= tail[belong[r]]; ++i)
        {
            if (current[i].second >= l && current[i].second <= r)    Rp[++itr] = sorted[i];
            else if (current[i].second < l)        Lp[++itl] = sorted[i];
        }
        if (l == head[belong[l]])    ans = previous[r] - Merge_Vct(Lp, Rp, itl, itr);
        else     ans = previous[r] - previous[l - 1] - Merge_Vct(Lp, Rp, itl, itr);
    }
    else
    {
        ans = intervals[belong[l] + 1][belong[r] - 1] + previous[r] + suffix[l];
        for (int i = head[belong[l]]; i <= tail[belong[l]]; ++i)
        {
            if (current[i].second >= l)
            {
                Lp[++itl] = sorted[i];
                ans += block[belong[r] - 1][1] - block[belong[r] - 1][sorted[i]] - block[belong[l]][1] + block[belong[l]][sorted[i]];
            }
        }
        for (int i = head[belong[r]]; i <= tail[belong[r]]; ++i)
        {
            if (current[i].second <= r)
            {
                Rp[++itr] = sorted[i];
                ans += block[belong[r] - 1][sorted[i] + 1] - block[belong[l]][sorted[i] + 1];
            }
        }
        ans += Merge_Vct(Lp, Rp, itl, itr);
    }
    write(io_l, ans);
}

signed main()
{
    read(n, m), blocks = (n - 1) / each + 1;
    if (n <= 8)
    {
        for (int i = 1; i <= n; ++i)
        {
            read(isa[i]);
            current[i] = make_pair(isa[i], i);
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= n; ++i)
        {
            read(isa[i]);
            current[i] = make_pair(isa[i], i);
        }
    }
    if (blocks <= 8)
    {
        for (int i = 1; i <= blocks; ++i)
        {
            head[i] = tail[i - 1] + 1;
            tail[i] = tail[i - 1] + each;
            if (i == blocks)     tail[i] = n;
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= blocks; ++i)
        {
            head[i] = tail[i - 1] + 1;
            tail[i] = tail[i - 1] + each;
            if (i == blocks)     tail[i] = n;
        }
    }
    if (blocks <= 8)
    {
        for (int i = 1; i <= blocks; ++i)
        {
            memcpy(block[i], block[i - 1], sizeof(block[0]));
            sort(current + head[i], current + 1 + tail[i]);
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                ++block[i][isa[j]];
                belong[j] = i;
                sorted[j] = current[j].first;
            }
            int satisfy = 0;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                FWT.Modify(isa[j], 1);
                satisfy += FWT.Query(n) - FWT.Query(isa[j]);
                previous[j] = satisfy;
            }
            intervals[i][i] = satisfy;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                suffix[j] = satisfy;
                FWT.Modify(isa[j], -1);
                satisfy -= FWT.Query(isa[j] - 1);
            }
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= blocks; ++i)
        {
            memcpy(block[i], block[i - 1], sizeof(block[0]));
            sort(current + head[i], current + 1 + tail[i]);
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                ++block[i][isa[j]];
                belong[j] = i;
                sorted[j] = current[j].first;
            }
            int satisfy = 0;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                FWT.Modify(isa[j], 1);
                satisfy += FWT.Query(n) - FWT.Query(isa[j]);
                previous[j] = satisfy;
            }
            intervals[i][i] = satisfy;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                suffix[j] = satisfy;
                FWT.Modify(isa[j], -1);
                satisfy -= FWT.Query(isa[j] - 1);
            }
        }
    }
    if (blocks <= 8)
    {
        for (int dis = 1; dis <= blocks; ++dis)
        {
            for (int i = n - 1; i; --i)        block[dis][i] += block[dis][i + 1];
            for (int l = 1, r = dis + 1; r <= blocks + 1; ++l, ++r)
                intervals[l][r] = intervals[l + 1][r] + intervals[l][r - 1] - intervals[l + 1][r - 1] +
                                    Merge_Idx(head[l] - 1, head[r] - 1, tail[l] - head[l] + 1, tail[r] - head[r] + 1);
        }
    }
    else
    {
        #pragma unroll 8
        for (int dis = 1; dis <= blocks; ++dis)
        {
            for (int i = n - 1; i; --i)        block[dis][i] += block[dis][i + 1];
            for (int l = 1, r = dis + 1; r <= blocks + 1; ++l, ++r)
                intervals[l][r] = intervals[l + 1][r] + intervals[l][r - 1] - intervals[l + 1][r - 1] +
                                    Merge_Idx(head[l] - 1, head[r] - 1, tail[l] - head[l] + 1, tail[r] - head[r] + 1);
        }
    }

    if (m <= 8)
    {
        long long lastans = 0;
        for (int i = 0; i < m; ++i)
        {
            long long l, r;
            read(l, r);
            l ^= lastans;
            r ^= lastans;
            Behavior(l, r, lastans);
        }
    }
    else
    {
        long long lastans = 0;
        #pragma unroll 8
        for (int i = 0; i < m; ++i)
        {
            long long l, r;
            read(l, r);
            l ^= lastans;
            r ^= lastans;
            Behavior(l, r, lastans);
        }
    }
    return 0;
}

Description

Link.

无修支持查询:查询一个区间 $[l,r]$ 中所有子序列分别去重后的和 $\bmod\ p$

Solution

这是数据结构一百题的第50题(一半了哦)的纪念题解。

无修改操作,基本确定大方向莫队。

考虑查询的问题,我们可以转化一下。即求区间内每个数出现的次数。

一个区间 $[l,r]$ 的子序列数量为:

$$\sum_{i=0}^{r-l+1}C^{i}_{r-l+1}=2^{r-l+1}$$

比如一个数在区间 $[l,r]$ 出现了 $k$ 次,那么一共有 $2^{r-l+1-k}$ 个子序列不包含这个数。这个很好理解,从组合数的意义可知。那么就有 $2^{r-l+1}-2^{r-l+1-k}$ 个子序列包含了这个数。

那么我们就可以用莫队维护区间中出现了 $k$ 次的所有数的和,然后乘上一个 $2^{r-l+1}-2^{r-l+1-k}$ 就是它的贡献了。

问题又来了:每次询问的模数是不确定的,我们需要每次都需要 $\Theta(n)$ 处理一遍2的幂。

有没有什么方法能把处理这个东西的复杂度降到 $\Theta(\sqrt{n})$ 或以下呢?

对此SyadouHayami表示我们可以打个高精。

方法是有的。

我们可以每次询问都处理出 $2^{1},2^{2},\cdots,2^{\sqrt{n}}$ ,以及 $2^{2\sqrt{n}},2^{3\sqrt{n}},\cdots,2^{n}$,都只需要 $\Theta(\sqrt{n})$。当然,都是在模 $p$ 的意义下的。我们分别记为pow1pow2

那么 $2^{x}\operatorname{mod}p=(pow1_{x\operatorname{mod}\sqrt{n}}\times pow2_{\lfloor x\div\sqrt{n}\rfloor})\operatorname{mod}p$。

于是就解决问题了。

我的代码遇到了一下两个玄学问题,贴出来给同样情况的人看看:

  1. 链表部分的prevnext如果放在结构体里会T。
  2. pow1,pow2,sum,cnt几个数组的定义如果放在最开头和isa以及ans两个数组一起会RE。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int Maxn = 1e5 + 10;
const int Size = 320;
int n, m, isa[ Maxn ], ans[ Maxn ];
struct Query_Node
{
    int l, r, p, id, pos;
} Q[ Maxn ];
struct Linked_List
{
    int tot, prev[ Maxn ], next[ Maxn ];
    Linked_List( ) { tot = 0; }

    void insert( int x )
    {
        next[ tot ] = x;
        prev[ x ] = tot;
        tot = x;
    }

    void erase( int x )
    {
        if( tot == x )    tot = prev[ x ];
        else
        {
            next[ prev[ x ] ] = next[ x ];
            prev[ next[ x ] ] = prev[ x ];
        }
        prev[ x ] = next[ x ] = 0;
    }
} llt;

bool cmp( Query_Node rhs, Query_Node shr )
{
    if( rhs.pos != shr.pos )    return rhs.l < shr.l;
    else    return rhs.r < shr.r;
}

int pow1[ Maxn ], pow2[ Maxn ];
void Pare_Two( int p )
{
    pow1[ 0 ] = pow2[ 0 ] = 1;
    for( int i = 1; i <= Size; ++ i )     pow1[ i ] = 1ll * 2 * pow1[ i - 1 ] % p;
    for( int i = 1; i <= Size; ++ i )    pow2[ i ] = 1ll * pow1[ Size ] * pow2[ i - 1 ] % p;
}

int Get_Two( int x, int p )
{
    return 1ll * pow1[ x % Size ] * pow2[ x / Size ] % p;
}

int sum[ Maxn ], cnt[ Maxn ];
void Make_Cont( int x, int f )
{
    int pos = isa[ x ];
    sum[ cnt[ pos ] ] -= pos;
    if ( ! sum[ cnt[ pos ] ] ) llt.erase( cnt[ pos ] );
    if( f == 1 ) ++cnt[ pos ];
    else --cnt[ pos ];
    if ( ! sum[ cnt[ pos ] ] ) llt.insert( cnt[ pos ] );
    sum[ cnt[ pos ] ] += pos;
}

void Contribute( )
{
    int l = 1, r = 0;
    for( int i = 1; i <= m; ++ i )
    {
        Pare_Two( Q[ i ].p );
        while( l > Q[ i ].l ) Make_Cont( --l, 1 );
        while( l < Q[ i ].l ) Make_Cont( l++, 0 );
        while( r > Q[ i ].r ) Make_Cont( r--, 0 );
        while( r < Q[ i ].r ) Make_Cont( ++r, 1 );
        for( int s = llt.next[ 0 ]; s; s = llt.next[ s ] )
        {
            int val = 1ll * sum[ s ] * ( Get_Two( r - l + 1, Q[ i ].p ) - Get_Two( r - l + 1 - s, Q[ i ].p ) + Q[ i ].p ) % Q[ i ].p;
            ans[ Q[ i ].id ] += val;
            ans[ Q[ i ].id ] %= Q[ i ].p;
        }
    }
}

signed main( )
{
    scanf( "%d %d", &n, &m );
    for( int i = 1; i <= n; ++ i )    scanf( "%d", &isa[ i ] );
    for( int i = 1; i <= m; ++ i )
    {
        int l, r, p;
        scanf( "%d %d %d", &l, &r, &p );
        Q[ i ].l = l, Q[ i ].r = r;
        Q[ i ].p = p, Q[ i ].id = i;
        Q[ i ].pos = l / Size;
    }
    sort( Q + 1, Q + 1 + m, cmp );
    Contribute( );
    for( int i = 1; i <= m; ++ i )
        printf( "%d\n", ans[ i ] );
    return 0;
}

Description

Link.

给出 $N$ 个单词,每个单词有个非负权值 $C_{i}$,现要将它们分成连续的若干段,每段的代价为此段单词的权值和,还要加一个常数 $M$,即 $(\sum C_{i})^{2}+M$。现在想求出一种最优方案,使得总费用之和最小

Solution

设 $f_{i}$ 表示 $n=i$ 时的答案,转移方程为:

$$ f_{i}=\min\{f_{j}+S_{i..j}^{2}+M\} $$

显然过不了,推成可以斜率优化的形式。

我们假设决策 $j$ 优于决策 $k$ 且 $k<j<i$。

(一种错误的思路)

$$ f_{j}+S_{i..j}^{2}+M<f_{k}+S_{i..k}^{2}+M $$

$$ f_{j}+(S_{i}-S_{j})^{2}<f_{k}+(S_{i}-S_{k})^2 $$

$$ f_{j}+S_{i}^2-2S_{i}S_{j}+S_{j}^{2}<f_{k}+S_{i}^2-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+S_{j}^{2}<f_{k}-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+2S_{i}S_{k}+S_{j}^{2}-S_{k}^{2}-f_{k}<0 $$

$$ f_{j}-2S_{i}(S_{j}+ S_{k})+(S_{j}-S_{k})(S_{j}+S_{k})-f_{k}<0 $$

$$ f_{j}-(S_{j}+ S_{k})(2S_{i}+S_{j}-S_{k})-f_{k}<0 $$

$$ f_{j}-\frac{S_{j}+ S_{k}}{2S_{i}+S_{j}-S_{k}}-f_{k}<0 $$

最后发现做 $\text{TM}$ 不出来。

于是重新推:

(另一种错误思路)(我简直自闭)

$$ f_{j}+S_{i..j}^{2}+M<f_{k}+S_{i..k}^{2}+M $$

$$ f_{j}+(S_{i}-S_{j})^{2}<f_{k}+(S_{i}-S_{k})^2 $$

$$ f_{j}+S_{i}^2-2S_{i}S_{j}+S_{j}^{2}<f_{k}+S_{i}^2-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+S_{j}^{2}<f_{k}-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}S_{j}-2S_{i}S_{k} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}(S_{j}-S_{k}) $$

$$ f_{j}-f_{k}+(S_{j}-S_{k})(S_{j}+S_{k})<2S_{i}(S_{j}-S_{k}) $$

$$ \frac{f_{j}-f_{k}}{2(S_{j}-S_{k})}+\frac{S_{j}+S_{k}}{2}<S_{i} $$

嗯,还是 $\text{TM}$ 做不来。

好了,推倒重来。

(这次是对的)

$$ f_{i}=\min\{f_{j}+S_{i..j}^{2}+M\} $$

$$ f_{j}+S_{i..j}^{2}+M<f_{k}+S_{i..k}^{2}+M $$

$$ f_{j}+(S_{i}-S_{j})^{2}<f_{k}+(S_{i}-S_{k})^2 $$

$$ f_{j}+S_{i}^2-2S_{i}S_{j}+S_{j}^{2}<f_{k}+S_{i}^2-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-2S_{i}S_{j}+S_{j}^{2}<f_{k}-2S_{i}S_{k}+S_{k}^{2} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}S_{j}-2S_{i}S_{k} $$

$$ f_{j}-f_{k}+S_{j}^{2}-S_{k}^{2}<2S_{i}(S_{j}-S_{k}) $$

$$ \frac{(f_{j}+S_{j}^{2})-(f_{k}+S_{k}^{2})}{2(S_{j}-S_{k})}<S_{i} $$

我们令 $Y_{i}=f_{i}+S_{i}^{2}$,$X_{i}=2S_{i}$,则我们有:

$$ \frac{Y_{j}-Y_{k}}{X_{j}-X_{k}}<S_{i} $$

挺好的,正好就是斜率优化的形式!

斜率优化!

牛逼啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎哎啊啊哎。

那么接下来我们设 $F(j,k)=\frac{Y_{j}-Y_{k}}{X_{j}-X_{k}}$。

其中如果满足 $F(j,k)<S_{i}$,则我们称决策 $j$ 优于决策 $k$。

现在我们继续设 $k<j<i$,那么如果满足 $F(i,j)<F(j,k)$,决策 $j$ 就永远不可能成为最优解。

证明不会,$\text{PPT}$ 没看懂。

最后放个 $\text{PPT}$ 里写的 $\text{Summary}$:

1、用一个单调队列来维护解集。

2、假设队列中从头到尾已经有元素 $a$,$b$,$c$。那么当 $d$ 要入队的时候,维护队列的上凸性质,即如果 $F(d,c)<F(c,b)$,那么删除点 $c$。直到找到 $F(d,x)\ge F(x,y)$ 为止,并将 $d$ 点加入在该位置中。

3、求解的时候,从对头开始,如果已有元素 $a$,$b$,$c$,当 $i$ 点要求解时,如果 $F(b,a)<S_{i}$,说明 $b$ 点比 $a$ 点更优,$a$ 点直接排除,于是 $a$ 出队,最后 $f_{i} = \mathrm{getDp}(Q[\mathrm{head}])$。

#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long LL;

namespace MySpace
{
    template<class _T>
    void read(_T &x)
    {
        x = 0;
        char c = getchar();
        _T f = 1;
        while( c < '0' || c > '9' )
        {
            if( c == '-' )    f = -1;
            if( c == -1 )    exit( 0 );
            c = getchar();
        }
        while( c >= '0' && c <= '9' )    x = x * 10 + c - '0', c = getchar();
        x *= f;
    }
    
    template<class _T>
    void write(_T x)
    {
        if( x < 0 )    putchar( '-' ), x = -x;
        if( x > 9 )    write( x / 10 );
        putchar( x % 10 + '0' );
    }
    
    const LL MAXN = 5e5 + 5;
    LL N, M, Dp[MAXN], Q[MAXN], S[MAXN], A[MAXN];
    
    LL Square( LL x ) { return x * x; }
    LL Up( LL i ) { return Dp[i] + Square( S[i] ); }
    LL Down( LL i ) { return S[i] << 1; }
    LL FracUp( LL j, LL k ) { return Up( j ) - Up( k ); }
    LL FracDown( LL j, LL k ) { return Down( j ) - Down( k ); }
}

int main()
{
    using namespace MySpace;
    while( ~ scanf( "%lld%lld", &N, &M ) )
    {
        for( LL i = 1; i <= N; ++ i )    scanf( "%lld", &A[i] ), S[i] = S[i - 1] + A[i];
        LL l = 1, r = 1;
        Q[r] = 0;
        for( LL i = 1; i <= N; ++ i )
        {
            while( l < r && FracUp( Q[l + 1], Q[l] ) < S[i] * FracDown( Q[l + 1], Q[l] ) )    l ++;
            Dp[i] = Dp[Q[l]] + Square( S[i] - S[Q[l]] ) + M;
            while( l < r && FracUp( i, Q[r] ) * FracDown( Q[r], Q[r - 1] ) <= FracUp( Q[r], Q[r - 1] ) * FracDown( i, Q[r] ))    r --;
            Q[++ r] = i;
        }
        printf( "%lld\n", Dp[N] );
    }
//    while( read( N ), read( M ), 1 )
//    {
//        for( LL i = 1; i <= N; ++ i )    read( A[i] ), S[i] = S[i - 1] + A[i];
//        LL l = 1, r = 0;
//        Q[l] = 0;
//        for( LL i = 1; i <= N; ++ i )
//        {
//            while( l <= r && FracUp( Q[l + 1], Q[l] ) < S[i] * FracDown( Q[l + 1], Q[l] ) )    l ++;
//            Dp[i] = Dp[Q[l]] + Square( S[i] - S[Q[l]] ) + M;
//            while( l <= r && FracUp( i, Q[r - 1] ) * FracDown( Q[r - 1], Q[r - 2] ) < FracUp( Q[r - 1], Q[r - 2] ) * FracDown( i, Q[r - 1] ))    r ++;
//            Q[++ r] = i;
//        }
//        write( Dp[N] ), putchar( '\n' );
//    }
    return 0;
}

Description

Link.

给一个数列和 $m$,在数列任选若干个数,使得他们的和对 $m$ 取模后最大。

Solution

记录一下犯下的一个 nt 错误。

首先我们有一个显然的 DFS 暴力,每次两种决策,选或不选,所以时间复杂度为 $\Theta(2^{n})$。

$n$ 的范围是 35,是过不了的,我们可以考虑折半搜索。

关于折半搜索可以看看 我的折半搜索小计

暴力搜出 $[1,\lfloor\frac{n}{2}\rfloor],[\lfloor\frac{n}{2}\rfloor+1,n]$ 的所有答案,记录到两个 vector 里面。

这一部分的时间复杂度是 $\Theta(2^{\lfloor\frac{n}{2}\rfloor})$。

考虑合并贡献。

先考虑一个暴力合并贡献的方法。

我们记第一次搜索搜出来的答案序列为 $A_{1}$,同理有 $A_{2}$。

这里的两个答案序列都是在模 $m$ 意义下的。

那么对于每一个 $A_{1,i}$,我们都可以暴力在 $A_{2}$ 中寻找两者相加模 $m$ 的最大值。

那么我们可以分类讨论了,因为序列在模 $m$ 意义下,所以我们对于每一个 $A_{1,i}$ 找到的 $A_{2,j}$ 使得 $(A_{1,i}+A_{2,j})\bmod m$ 最大,都只有两种情况。

一种是 $A_{2,j}$ 在 $A_{2}$ 中值域范围在 $[0,m-A_{1,i}-1]$ 的所有值中最大,一种是在 $A_{2}$ 中值域范围在 $[0,m\times2-A_{1,i}-1]$ 的所有值中最大。

所以我们在这两种情况中取最大即可。

由于我不理解二分做法,所以我用的是动态开点值域线段树。

(flag:动态开点不加引用就【】)

#include<bits/stdc++.h>
using namespace std;
const int N=35+5;
const int H=99999999;
int n,m,tot=1,root=1,ans,a[N];
struct Tree
{
    int ls,rs,val;
} nodes[(1<<(N>>1))<<3];
vector<int> vec[2];

void dfs(int x,int cur,int lim)
{
    if(x>lim)
    {
        if(lim==(n>>1))     vec[0].push_back(cur);
        else    vec[1].push_back(cur);
        return ;
    }
    dfs(x+1,(cur+a[x])%m,lim);
    dfs(x+1,cur,lim);
}

void ins(int &p,int l,int r,int x)
{
    if(p==0)    p=++tot;
    if(l==r)
    {
        nodes[p].val=l;
        return ;
    }
    int mid=(l+r)>>1;
    if(mid>=x)    ins(nodes[p].ls,l,mid,x);
    else    ins(nodes[p].rs,mid+1,r,x);
    nodes[p].val=max(nodes[nodes[p].ls].val,nodes[nodes[p].rs].val);
}

int find(int p,int l,int r,int x,int y)
{
    if(l>y||r<x)    return 0;
    if(l>=x&&r<=y)    return nodes[p].val;
    int mid=(l+r)>>1,ret=0;
    if(mid>=x)    ret=max(ret,find(nodes[p].ls,l,mid,x,y));
    if(mid<y)    ret=max(ret,find(nodes[p].rs,mid+1,r,x,y));
    return ret;
}

void output(int p)
{
    if(nodes[p].ls==0&&nodes[p].rs==0)
    {
        printf("%d ",nodes[p].val);
        return ;
    }
    output(nodes[p].ls);
    output(nodes[p].rs);
}

signed main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
    dfs(1,0,n>>1);
    dfs((n>>1)+1,0,n);
    sort(vec[0].begin(),vec[0].end());
    sort(vec[1].begin(),vec[1].end());
    for(auto x:vec[1])    ins(root,0,m-1,x);
    for(auto x:vec[0])    ans=max(ans,max(x+find(1,0,m-1,0,m-x-1),(x+find(1,0,m-1,0,m*2-x-1))%m));
    printf("%d\n",ans);
    return 0;
}