工资管理 树状数组+离散化

本文解析了NKOJ3776工资管理问题,介绍了一种有效的算法来处理员工工资更新及减薪操作可行性判断。通过离散化和树状数组维护工资分布状态,实现高效计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NKOJ 3776 工资管理

问题描述

何老板的公司有n名员工,编号1到n。一开始所有员工的工资都是0。根据何老板的心情好坏,可能出现下列两种针对员工工资的操作:
1.U x y 改工资操作:何老板将第x号员工的工资改成了y;
2.Z x y 减工资操作:何老板生气了,他想选出x个员工,并将他们的工资全都减去1。何老板想知道,他能否一口气进行y次这样的减工资操作。能输出TAK,否则输出NIE。注意,员工的工资不能为
负。

对于每个减工资的操作,何老板只是在心里想想,口头上说说,吓唬吓唬大家,解解闷气,他并不会真正执行。即不会对任何人的工资进行修改。

输入格式

第一行包含两个正整数n,m,分别表示员工的人数和操作次数。 接下来m行,每行一个操作,形式如题面所述。

输出格式

包含若干行,对于每个减工资操作,若可行,输出TAK,否则输出NIE。

样例输入 1

3 8
U 1 5
U 2 7
Z 2 6
U 3 1
Z 2 6
U 2 2
Z 2 6
Z 2 1

样例输出 1

NIE
TAK
NIE
TAK

样例输入 2

13 17
U 1 12
Z 1 9
Z 1 5
Z 4 7
U 7 18
Z 1 1
Z 1 8
U 6 4
U 1 9
U 3 13
Z 5 2
U 7 8
U 4 20
U 7 14
Z 6 1
Z 3 2
Z 8 7

样例输出 2

TAK
TAK
NIE
TAK
TAK
NIE
NIE
TAK
NIE

提示

对于30%的数据:1<=n,m<=1000 对于100%的数据:1<=n,m<=200000
1<=x<=n,0<=y<=10^9,1<=y<=10^9。

来源

改编自POI2015 Logistyka


显然,思维量主要在Z操作上。对于Z操作,首先容易想到,如果有工资大于等于y的人,那么他们在这y次操作中都应该被选到,因为选肯定比不选更优。假设我们找到了工资大于等于y的人数,接下来只需要考虑x个位置中剩下的部分怎么选。

不妨设每次剩下需要选的人数为z。我们首先会想到:如果工资低于y的人的工资总数都比z*y要小,那么这个操作显然不能完成。这是一个必要条件,然而这也同样是一个充分条件!

下面略微说明一下为什么这是一个充分条件:

在剩下的人的工资总数不低于z*y的条件下,假设存在这样一种情况,在完成第y次操作前的某一次选不出z个满足条件的人(即,原工资小于y,操作若干次后工资大于0)。由于现在存在的满足条件的人数小于z,且剩下的人的工资总数不低于z*y,分析得一定存在某人的原工资不低于y,与条件矛盾。

所以我们现在只需要知道下列数据:
1.工资不低于某个值的人数总和;
2.工资小于某个值的人的工资总和。

离散化之后用树状数组维护即可。

#include<stdio.h>
#include<algorithm>
#define ll long long
#define MAXN 200005
using namespace std;
struct node{int k,x,y;}data[MAXN];
int N,M,w[MAXN],e[MAXN];int C[MAXN];ll D[MAXN];

void Modify(int x,int k){for(int i=x;i<=M;i+=(i&-i))C[i]+=k,D[i]+=1ll*k*w[x];}
ll Tot,Cnt;
void GetSum(int x)
{
    int i;Cnt=Tot=0;
    for(i=x;i;i-=(i&-i))Cnt+=C[i],Tot+=D[i];
}

int main()
{
    int i,a,b,R;
    char c[3];

    scanf("%d%d",&N,&M);
    w[1]=0;
    for(i=1;i<=M;i++)
    {
        scanf("%s%d%d",&c,&a,&b);
        if(c[0]=='U')data[i].k=0;
        else data[i].k=1;
        w[i+1]=b;data[i].x=a;data[i].y=b;
    }

    Modify(1,N);
    M++;
    sort(w+1,w+M+1);
    R=unique(w+1,w+M+1)-w;
    M--;

    for(i=1;i<=M;i++)
    {
        if(data[i].k==0)
        {
            a=lower_bound(w+1,w+R,e[data[i].x])-w;
            Modify(a,-1);
            a=lower_bound(w+1,w+R,data[i].y)-w;
            Modify(a,1);
            e[data[i].x]=data[i].y;
        }
        else
        {
            a=lower_bound(w+1,w+R,data[i].y)-w;
            GetSum(a-1);
            if(Tot>=1ll*(data[i].x-N+Cnt)*data[i].y)puts("TAK");
            else puts("NIE");
        }
    }
}
<think>嗯,用户的问是关于如何优化树状数组和莫队算法,避免超时问。首先,我需要回忆一下这两个数据结构的基本原理和常见的优化法。 树状数组的优化,我记得有几个向。首先,维度优化。比如二维树状数组,如果每个节点也是一个树状数组,这样的结构在更新和查询时的时间复杂度是O(log²n),当数据量大时可能会比较慢。或许可以尝试降维,比如将二维问转换为一维,或者使用哈希表来压缩空间,减少不必要的节点。另外,离散化坐标也是一个常见的法,特别是当坐标范围很大但实际使用到的点稀疏时,这样可以减少内存占用和访问时间。 然后是操作优化。比如批量处理多个查询或更新操作,利用缓存机制,减少重复计算。还有位运算优化,比如用lowbit的位运算代替乘法或除法,虽然这可能编译器已经优化了,但手动确保代码中使用位运算还是有帮助的。 接下来是莫队算法的优化。莫队本身的时间复杂度是O(n√n),但实际应用中常数优化也很重要。分块大小的调整,通常取√n,但有时候取n/√q(q是查询次数)会更优,这需要根据具体情况调整。另外,排序查询的顺序,使用奇偶块排序,可以减少指针移动的总距离,从而减少常数时间。 离线处理面,莫队需要所有查询离线处理,所以预处理和合理组织查询顺序是关键。还有常数优化,比如用更快的输入输出法,减少函数调用,使用数组代替STL容器等。 另外,用户提到了二维树状数组和树套树的情况,可能涉及到高维问。例如引用[1]中的二维树状数组,每个节点也是一个树状数组,这种情况下优化可能需要结合分块或者离散化。而莫队算法在处理区间查询时,分块策略和排序式都会影响效率,需要根据数据特点调整。 用户可能的应用场景比如在竞赛编程中处理大数据量的查询,或者在文件管理中需要高效的数据结构。例如引用[3]中的均分数据问,可能涉及到多次查询和更新,这时候优化树状数组和莫队算法就显得尤为重要。 需要注意的是,用户的问可能还涉及到具体实现的细节,比如代码中的循环结构、内存访问模式等。例如在树状数组中,循环展开或者使用更紧凑的数据结构来减少缓存未命中。在莫队算法中,避免频繁的内存分配和释放,使用预分配的数组。 最后,生成相关问时要覆盖这两个数据结构的优化法、应用场景以及与其他算法的结合使用。比如询问如何选择分块大小,或者如何结合离散化技术优化树状数组,或者莫队算法在动态问中的变种。</think>### 树状数组与莫队算法的优化法 #### 一、树状数组优化 1. **维度压缩** 对高维树状数组进行降维处理。例如,二维问离散化坐标后通过哈希映射到一维,减少空间和时间复杂度[^1]。若问允许,使用前缀和差分代替树状数组。 2. **离散化坐标** 当坐标范围过大但实际稀疏时,对坐标离散化以减少节点数量。例如,将坐标映射为$1,2,\dots,k$,树状数组大小从$10^9$压缩到$10^5$。 3. **批量操作优化** 合并多次更新或查询操作,减少重复计算。例如,离线处理操作序列,利用缓存机制降低时间复杂度。 4. **位运算加速** 确保`lowbit`计算使用位运算: $$ \text{lowbit}(x) = x \& (-x) $$ 同时避免循环中的冗余判断,例如预计算索引偏移量。 #### 二、莫队算法优化 1. **分块大小调整** 分块大小通常取$\sqrt{n}$,但若查询次数$q$较大,可调整为: $$ \text{块大小} = \frac{n}{\sqrt{q}} $$ 实验调整以平衡移动次数和块内排序代价。 2. **奇偶块排序** 对查询按左端点所在块排序,若块为奇数则按右端点升序,偶数块按右端点降序。减少指针跳跃次数,优化常数因子[^2]。 3. **离线处理与预处理** 预处理所有查询,按优化后的顺序处理,避免重复计算。结合前缀和或预计算区间特征(如频次分布)加速统计。 4. **常数优化** - 使用数组代替`unordered_map`统计频次。 - 使用快速读入/输出函数(如`fread`)。 - 减少函数调用,内联关键代码。 #### 三、综合优化示例 **场景**:二维区间求和(如引用[1]的二维树状数组) - **步骤1**:离散化$x,y$坐标,压缩空间至实际范围。 - **步骤2**:若操作可离线,转化为一维问(如扫描线+莫队)。 - **步骤3**:莫队处理时,调整块大小并使用奇偶排序,减少指针移动。 ```cpp // 莫队奇偶排序示例 bool cmp(const Query& a, const Query& b) { if (a.block != b.block) return a.block < b.block; return (a.block & 1) ? (a.r < b.r) : (a.r > b.r); } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值