HDU6315 Naive Operatios 离线 + 线段树计算贡献

HDU 6315 Naive Operations 离线 + 线段树

写在前面

因为今天师弟们训练打多校, 再次翻到了这道题, 想起去年多校比赛时想到了做法但是却没有敲出来, 十分遗憾, 但是赛后询问TMK他们的做法的时候, 感到十分惊讶, 因为我的做法是离线 + 两棵线段树计算贡献, 他们是维护最小值暴力更新, 惊叹他们做法的巧妙之余也对自己的算法有了怀疑, 在题解出来后更是觉得想要验证一下自己的想法, 于是在当天晚上敲了一发并AC, 证明了自己的想法是对:
AC截图
不过跑起来比较慢, 跑了将近1s, 队友在看了题解之后照着题解思路敲了一发, 时间不到900ms, 感觉我的做法确实常数比较大。
然后想起自己这个不一样的做法, 就翻出来代码, 对着师弟吹了一顿牛逼, 然后想到我这个做法虽然暴力, 但是跟题解不一样, 想着会不会比较少人想到, 就萌生出了写一篇题解的想法, 然后随便百度一下, 确实没什么人写, 不过有份题解里提到了“两棵线段树”, 感觉应该还是有人写了, 不过想了想, 为了记录此刻的心情, 还是写下来吧。
以下是题目以及题解正文:

Naive Operations

Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 502768/502768 K (Java/Others)
Problem Description

In a galaxy far, far away, there are two integer sequence a and b of length n.
b is a static permutation of 1 to n. Initially a is filled with zeroes.
There are two kind of operations:
1. add l r: add one for al,al+1…ar
2. query l r: query ∑ri=l⌊ai/bi⌋

Input

There are multiple test cases, please read till the end of input file.
For each test case, in the first line, two integers n,q, representing the length of a,b and the number of queries.
In the second line, n integers separated by spaces, representing permutation b.
In the following q lines, each line is either in the form ‘add l r’ or ‘query l r’, representing an operation.
1≤n,q≤100000, 1≤l≤r≤n, there’re no more than 5 test cases.

Output

Output the answer for each ‘query’, each one line.

Sample Input

5 12
1 5 2 4 3
add 1 4
query 1 4
add 2 5
query 2 5
add 3 5
query 1 5
add 2 4
query 1 4
add 2 5
query 2 5
add 2 2
query 1 5

Sample Output

1
1
2
4
4
6

题意

给你一个数组a, 初始全为0, 再给你一个排列b, 现在有q次操作, 操作1是给出了,r,让a[l]~a[r]+1,操作二是给出l,r询问⌊ai/bi⌋,其中i∈[l,r]。

首先题目这个东西确实不好算, 主要因为有个下取整, 不能用线段树直接维护, 此时就想到按每个位置的贡献来计算, 那么就可以想到, 位置i被加了bi次之后才会使得有询问到这个位置的答案加一。 但是此时问题依然没有解决, 因为暴力的做的复杂度难以估计, 而且在线维护贡献十分难, 于是便想到了离线 + 打标记
假设我们两个vector数组,记为TADDTQUE, 且TADD[i]表示那些影响到了位置i的操作1的时间点,例如TADD[i] = { 1, 3, 5 }; 的话就表示第1, 3, 5个操作影响到了位置 i 。TQUE也同理表示询问到了位置i的询问。
那么有了这个我们怎么处理贡献呢, 首先对于位置i,TADD[i]每有b[i] 元素就会对达到这个数量开始以后的询问产生1点贡献, 比如说TADD[i] = { 1, 3, 5, 7, 9 }; TQUE[i] = { 2, 4, 6, 8 }且b[i] = 2, 那么在TQUE[i]大于3的询问就会答案+1,然后TQUE[i] 中大于7的询问的答案再次加一,如此,我们就能算出每个询问的答案。
那么现在的问题的就变成了如何维护TADD[i] 以及TQUE[i] 以及更新答案了。
TADD[i] 的维护其实十分容易, 把询问离线之后给每个位置维护一个addLaddR的vector就知道每个位置有哪些区间的开头结尾在这个位置了, 那么计算每个位置的贡献的时候, 先把addL里的每个询问加入TADD, 处理完贡献之后把每个addR退出TADD就可以了。而这个操作可以维护一个值域线段树表示操作x是否对当前位置产生贡献来解决, 这样我们就可以用log的代价得到每个产生贡献的位置的操作时间点了。
类似的, TQUE[i] 需要用来类似区间更新的操作, 且每次更新都是1, 那么显然我们可以用线段树的区间更新(记得带lazy标记)来维护,线段树中表示的位置x就表示在时间点x的询问的答案。
维护TQUE这里有个细节需要讲解一下的就是, 每次我们更新答案的时候, 假设现在在t以后的询问答案加一, 那么我们直接更新t~q的所有询问的话, 可能会把不涉及位置i的询问也更新了, 但是事实上我们通过一个小小的讨论和操作就可以解决这个问题了。 假如我们现在更新把某个不涉及当前位置的询问x给更新了, 那么假如这个x的右端点我们已经处理过了, 那么它的答案我们已经得到过了, 我们此时给这个x增加多余的答案并不会影响最终答案, 因为最终答案我们可以保存在另外一个数组ans中, 更新后不再去动它, 自然就不会影响答案; 反过来, 如果这个x的左端点我们还没有遇到, 我们这样提前给他加答案肯定是不行的, 那么很简单, 当我们遇到了这个询问x的左端点的时候, 把那个位置清零, 也就是把之前不小心给他加上的额外的答案去掉就可以了。
至此, 离线 + 两棵线段树维护答案的思路就是这样了, 复杂度的计算也类同其他博客。 首先主要的耗费的时间就是, 我们会遍历每一个位置, 此时, 我们以b[i] 的步长遍历TADD[i],而TADD[i]的长度最坏情况为每个都是n, 那么总的复杂度就是n / b1 + n / b2 + … + n / bn = n * ( 1 + 1 / 2 + 1 / 3 + … + 1 / n ) ≈ n * logn, 同时, 以b[i] 的步长的时候进行遍历的时候我们又需要logn的时间来得到第若干个产生贡献的位置, 所以总的时间复杂度是O(n * logn * logn)

代码如下:
#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 5;

bool flag[maxn];
int n, q, b[maxn], ans[maxn];
vector<int> add[2][maxn], qus[2][maxn];
int ADD[maxn * 4], ANS[maxn * 4], mark[maxn * 4];

void build(int* tree, int* marks, int u, int l, int r)
{
    if(marks != NULL) marks[u] = 0;
    if(l == r) tree[u] = 0;
    else
    {
        tree[u] = 0;
        int mid = (l + r) >> 1;
        build(tree, marks, u << 1, l, mid);
        build(tree, marks, u << 1 | 1, mid + 1, r);
    }
}

//清空
void clean()
{
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 0; j < 2; ++j)
        {
            add[j][i].clear();
            qus[j][i].clear();
        }
    }
    for(int i = 1; i <= q; ++i)
    {
        ans[i] = 0;
        flag[i] = false;
    }
    build(ADD, NULL, 1, 1, q);
    build(ANS, mark, 1, 1, q);
}

//单点更新
void ADD_update(int u, int l,  int r, int x, int val)
{
    if(l == r) ADD[u] = val;
    else
    {
        int mid = (l + r) >> 1;
        if(x <= mid) ADD_update(u << 1, l, mid, x, val);
        else ADD_update(u << 1 | 1, mid + 1, r, x, val);
        ADD[u] = ADD[u << 1] + ADD[u << 1 | 1];
    }
}

//得到第k个有效的位置
int ADD_queryk(int u, int l, int r, int k)
{
    if(l == r) return l;
    else
    {
        int mid = (l + r) >> 1;
        if(k <= ADD[u << 1]) return ADD_queryk(u << 1, l, mid, k);
        else return ADD_queryk(u << 1 | 1, mid + 1, r, k - ADD[u << 1]);
    }
}

void pushdown(int u, int l, int r)
{
    if(mark[u])
    {
        int mid = (l + r) >> 1;
        mark[u << 1] += mark[u];
        mark[u << 1 | 1] += mark[u];
        ANS[u << 1] += (mid - l + 1) * mark[u];
        ANS[u << 1 | 1] += (r - mid) * mark[u];
        mark[u] = 0;
    }
}

//单点置零
void ANS_SET_update(int u, int l, int r, int x)
{
    if(l == r)
    {
        mark[u] = 0;
        ANS[u] = 0;
    }
    else
    {
        pushdown(u, l, r);
        int mid = (l + r) >> 1;
        if(x <= mid) ANS_SET_update(u << 1, l, mid, x);
        else ANS_SET_update(u << 1 | 1, mid + 1, r, x);
        ANS[u] = ANS[u << 1] + ANS[u << 1 | 1];
    }
}

//区间更新
void ANS_ADD_update(int u, int l, int r, int ul, int ur, int val)
{
    if(ul <= l && r <= ur)
    {
        ANS[u] += (r - l + 1) * val;
        mark[u] += val;
    }
    else
    {
        pushdown(u, l, r);
        int mid = (l + r) >> 1;
        if(ul <= mid) ANS_ADD_update(u << 1, l, mid, ul, ur, val);
        if(mid < ur) ANS_ADD_update(u << 1 | 1, mid + 1, r, ul, ur, val);
        ANS[u] = ANS[u << 1] + ANS[u << 1 | 1];
    }
}

//单点询问
int ANS_query(int u, int l, int r, int x)
{
    if(l == r) return ANS[u];
    else
    {
        pushdown(u, l, r);
        int mid = (l + r) >> 1, res = 0;
        if(x <= mid) res = ANS_query(u << 1, l, mid, x);
        else res = ANS_query(u << 1 | 1, mid + 1, r, x);
        return res;
    }
}

int main()
{
    while(~scanf("%d%d", &n, &q))
    {
        for(int i = 1; i <= n; ++i)
        {
            scanf("%d", b + i);
        }
        for(int i = 1; i <= q; ++i)
        {
            int l, r;
            char operation[15];
            scanf("%s%d%d", operation, &l, &r);
            if(operation[0] == 'a')
            {
            	// 对应addL和addR
                add[0][l].push_back(i);
                add[1][r].push_back(i);
            }
            else
            {
                flag[i] = true;
                //询问也要有所标记
                qus[0][l].push_back(i);
                qus[1][r].push_back(i);
            }
        }
        for(int i = 1; i <= n; ++i)
        {
            for(int j = 0; j < add[0][i].size(); ++j)
            {
            	//ADD树就是TADD,这里是将addL[j]加入到TADD[i]中
                ADD_update(1, 1, q, add[0][i][j], 1);
            }
            for(int j = 0; j < qus[0][i].size(); ++j)
            {
            	//将TQUE[i]中现在遇到左端点的对应询问清零
                ANS_SET_update(1, 1, q, qus[0][i][j]);
            }
            for(int j = b[i]; ADD[1] >= j; j += b[i])
            {
           		//以b[i]步长遍历TADD[i]
                int tid = ADD_queryk(1, 1, q, j);
                ANS_ADD_update(1, 1, q, tid, q, 1);
            }
            for(int j = 0; j < add[1][i].size(); ++j)
            {
            	//遇到add的右端点,将其弹出TADD[i]
                ADD_update(1, 1, q, add[1][i][j], 0);
            }
            for(int j = 0; j < qus[1][i].size(); ++j)
            {
            	//遇到询问的右端点, 表示该询问已处理完毕, 可以得到答案
                int p = qus[1][i][j], temp = ANS_query(1, 1, q, p);
                ans[p] = temp;
            }
        }
        for(int i = 1; i <= q; ++i)
        {
            if(flag[i]) printf("%d\n", ans[i]);
        }
        clean();
    }
    return 0;
}
写在最后

其实弄完之后觉得, 我这个思路其实不怎么巧妙, 但是胜在可以一步一步根据题目给出的信息推出做法和完善细节, 思维性比较差, 感觉比较适合我们这种脑子不太灵光的选手按部就班的进行推理解题。 还有就是, 离线计算贡献这种操作有时候又确实有奇效。

使用优化算法,以优化VMD算法的惩罚因子惩罚因子 (α) 和分解层数 (K)。 1、将量子粒子群优化(QPSO)算法与变分模态分解(VMD)算法结合 VMD算法背景: VMD算法是一种自适应信号分解算法,主要用于分解信号为不同频率带宽的模态。 VMD的关键参数包括: 惩罚因子 α:控制带宽的限制。 分解层数 K:决定分解出的模态数。 QPSO算法背景: 量子粒子群优化(QPSO)是一种基于粒子群优化(PSO)的一种改进算法,通过量子行为模型增强全局搜索能力。 QPSO通过粒子的量子行为使其在搜索空间中不受位置限制,从而提高算法的收敛速度与全局优化能力。 任务: 使用QPSO优化VMD中的惩罚因子 α 和分解层数 K,以获得信号分解的最佳效果。 计划: 定义适应度函数:适应度函数根据VMD分解的效果来定义,通常使用重构信号的误差(例如均方误差、交叉熵等)来衡量分解的质量。 初始化QPSO粒子:定义粒子的位置和速度,表示 α 和 K 两个参数。初始化时需要在一个合理的范围内为每个粒子分配初始位置。 执行VMD分解:对每一组 α 和 K 参数,运行VMD算法分解信号。 更新QPSO粒子:使用QPSO算法更新粒子的状态,根据适应度函数调整粒子的搜索方向和位置。 迭代求解:重复QPSO的粒子更新步骤,直到满足终止条件(如适应度函数达到设定阈值,或最大迭代次数)。 输出优化结果:最终,QPSO算法会返回一个优化的 α 和 K,从而使VMD分解效果最佳。 2、将极光粒子(PLO)算法与变分模态分解(VMD)算法结合 PLO的优点与适用性 强大的全局搜索能力:PLO通过模拟极光粒子的运动,能够更高效地探索复杂的多峰优化问题,避免陷入局部最优。 鲁棒性强:PLO在面对高维、多模态问题时有较好的适应性,因此适合海上风电时间序列这种非线性、多噪声的数据。 应用场景:PLO适合用于优化VMD参数(α 和 K),并将其用于风电时间序列的预测任务。 进一步优化的建议 a. 实现更细致的PLO更新策略,优化极光粒子的运动模型。 b. 将PLO优化后的VMD应用于真实的海上风电数据,结合LSTM或XGBoost等模型进行风电功率预测。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值