点分治 树状数组    发布于 2020-02-03   394人围观   0条评论

题解

树上点对距离问题,非常经典的点分治问题。

点分治的基本思路:

对于当前的树(子树),求出其重心,统计过重心的合法路径数,然后以重心为分隔点,分别递归对删除该重心后的子树进行求解(继续求重心、统计,再进行递归)。

重心的概念:若一个点为树的重心,那么其作为根的时候,其子树的大小的最大值最小。

重心的性质:重心分割出的子树的大小不会超过now_n/2,可以用来证明其时间复杂度为O(logn * 统计的时间)。

此题要求距离小于等于k的路径数目,如果把每一种都算出来累加复杂度是相当高的。

可以使用树状数组,不过要注意树状数组的0号位对答案是不会造成任何影响的,所以在统计入树状数组的时候要稍微处理一下。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
struct edge{
    int to,next,val;
}e[maxn<<1];
int siz[maxn],mx[maxn],vis[maxn],dis[maxn];
int c[maxn*100],tmp[maxn];
int n,rt;
int h[maxn];
int lowbit(int x){
    return x & -x;
}
void update(int now,int d){
    while(now <= n * 100 - 2){
        c[now] += d;
        now += lowbit(now);
    }
    return;
}
int query(int now){
    int ret = 0;
    while(now > 0){
        ret += c[now];
        now -= lowbit(now);
    }
    return ret;
}
int cnt = 0,num = 0,sum = 0;
void add(int u,int v,int d){
    e[cnt].to = v;e[cnt].val = d;e[cnt].next = h[u];h[u] = cnt++;
    return;
}
void get_root(int now,int fa){
    siz[now] = 1;mx[n
查看更多
思维题 树状数组    发布于 2020-01-15   1011人围观   0条评论

题目大意

一开始有一个初始顺序的排列[1,n],一共有m次操作,每次将数ai从序列中取出,放到最开头的位置生成一个新的排列。

求问在所有过程中每一个数最小的下标位置和最大的下标位置。

题解

最小的下标位置是非常容易求得的,这很明显,因为如果从来没有被放到开头,那么答案一定是i,否则答案就是1。

需要思考的点在于求最大的下标位置。

这个数据范围不允许进行模拟,链表也不行(链表的单个操作、查询效率极差)。

先来考虑一种特殊情况1:

假设当前所有的数字都已经被放置到第一位过了,在满足这种条件后的最大的下标位置怎么求。

明显元素之间一开始的位置在哪里已经完全互不影响了。

如果一个数再被要求放到第一位,那么这个操作之前的下标位置可能就会影响答案。

而这个下标位置是什么?我们想什么样的操作会影响这个下标位置。很明显只有在上一次此数被放到第一位的时间之后,不同的数字放置到第一位才会使得这个数字的位置向后推移。也就是说,这个下标位置,就是当前未操作时刻和上一次这个数被放到第一位以后的时刻,这之间的不同数字的个数再+1,也就是区间内不同数的个数。

树状数组可以离线询问区间内不同数的个数。由于这道题的特殊性,我们无需对询问区间进行右端点优先排序,直接操作查询即可。

然后再考虑剩下的情况2:

如果一个数之前没有被放置在第一位的经历该如何计算?

这样在它被第一次放置到第一位的命令执行以前,其最大下标位置是之前的时刻被放置在第一位的大于此数的数字的个数 + 此数最初的坐标值。问题变成了维护区间内出现的不同数字的个数,需要用另外一个树状数组进行维护,只需要再每个数字第一次被放置在第一位时维护一次,查询一次即可。

在问题的最后,这样不一定已经求得最大值,因为对于每一个数字,最后可能有影响到最大值的问题但是没有查询更新。

所以在最后我们就要:

对于在操作序列中出现过的数字,我们执行情况1的查询一次。

对于未在操作序列中出现过的数字,我们执行情况2的查询一次。

代码

#include <bits/stdc++.h>
const int maxn = 3e5+5;
using namespace std;
map<int,int>mp;
int tr[maxn],a[maxn],ans[maxn],c[maxn];
int n,m;
int lowbit(int x){
	return x & (-x);
}
void add(int x,i
查看更多
树状数组 二分    发布于 2019-11-25   505人围观   0条评论

题目大意

给定一个序列,每次询问参数为pos和k,求问长度为pos的和最大的子序列中第k个元素是什么。

题解

这题其实有一个easy version,用map稍微搞一搞就能过那种

然后这题数据范围到2e5了,就不能用map乱搞了。

首先我们要想一下如何构造这个长为pos且和最大的子序列怎么构造呢?

我们只要对原序列从大到小排一次序,且保证在等大的时候原序列中靠前的元素在排序后也考前,然后我们只需要从排序后的序列中取前pos个数,就是符合要求的子序列了。

但是询问次数过多,我们就不能对于每一个request都重新求一次符合要求的子序列。

我们发现这个序列是递增的,就是如果pos[i] > pos[j],那么询问i的序列一定就是询问j的序列再接上一段。

于是我们将询问也按照pos从小到大排序。

但是我们怎么找这个子序列中第k个元素是多少呢?

我们在对原序列进行排序的时候同时记录下元素在原序列中的位置,然后在选序列的时候,可以用这样一种经典的记录方式:

将当前被选中的元素加入树状数组进行计数,即将该元素对应的原序列的位置在树状数组上的位置进行update(pos,1)

当我们询问的时候,从1——n进行二分,统计在这个位置mid之前的选中数一共有多少个。

最终二分出来的答案就代表着选中序列第k位在原数组中的位置了。直接去原数组中找就可以了。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005;
int n,m;
int a[maxn];
int c[maxn];
int lowbit(int x){
    return x & -x;
}
void update(int pos,int x){
    while(pos <= n){
        c[pos] += x;
        pos += lowbit(pos);
    }
    return;
}
int val(int pos){
    int ret = 0;
    while(pos > 0){
        ret += c[pos];
        pos -= lowbit(pos);
    }
    return ret;
}
struct num{
    int id;
    int val;
}b[max
查看更多
二分 分数规划 树状数组    发布于 2019-10-31   368人围观   0条评论

题目大意

给出一些线段,给出这些线段的起始和终止位置,并给出这些线段选取的两个代价A和B,要求选取一些线段覆盖区间[1----t],使得ΣAi与ΣBi的比值最小。

题解

很经典的01分数规划问题的形式。

当天训练的时候就想到了BUPT2019校赛的一道01分数规划问题,当时还是我们队拿的那题一血,可惜当时的队友现在因为一些原因分开了,真是令人感叹,sigh。

回归正题……

这一类最经典的01分数规划的解题基本思路是十分固定的,如下:

1.二分一个目标值mid

2.贪心地去构造检验是否有一组可行解,使得ΣAi / ΣBi <= mid;

3.如果能构造出这样一组可行解,那么将二分区间向左缩进,否则将二分区间向右缩进。

那么我们如何去构造这样一组可行解呢?

我们将不等式换一下形式,换成没有分式的不等式形式:mid * ΣBi - ΣAi >= 0;

这样我们就需要构造一组线段选取方案去满足上面的那个等式。

为了使得选取的区间完整,我们可以先将区间按照左端点为第一关键字,右端点为第二关键字从小到大进行排序,这样会保证在能构造出一组当前mid值条件下的可行解的时候我们不会漏掉中间的一部分区间。

之后我们先将所有mid * Bi - Ai >= 0的所有区间都选中,并统计加和sum,因为这些区间会使得不等式左边尽可能大。如果这时候已经能够覆盖所有需要覆盖的区间,那么就说明这样就已经能达到一组可行解了。

如果还没有覆盖所有区间,那么我们就要在剩下没有选中的线段中选取一些线段使得他们的ΣAi - mid * ΣBi尽可能小,这样不等式才能尽可能成立。

如何尽可能小?这时候我们可以使用树状数组去统计当前线段起点S前一个位置S-1到t的最小值(这里树状数组需要维护当前点到t的最小代价值),然后将选这个线段的方案统计入树状数组去求最小值。

之后我们去查询到t的最小值,判断sum是否大于等于 query(t)。如果sum >= query(t),则满足上述不等式,二分区间向左缩进。否则将二分区间向右缩进。

为了保证二分精度,我们可以限定二分次数,而非用eps判定二分区间端点l与r的关系。具体实现见代码吧。

代码

#include <bits/stdc++.h>
using namespace std;
int n,t;
int T;
const int maxn = 1e5+5;
struct interv{
    int 
查看更多
树状数组 偏序    发布于 2019-09-08   436人围观   0条评论

题目大意

给定一个1---n的排列和m次询问,每次询问区间 [l,r]之间min(pi,pj) = gcd(pi,pj)的数对的个数。

题解

一开始看到是数对问题还以为是莫队,不过话说回来莫队WA了而不是T是怎么回事……

首先我们需要注意min(pi,pj) = gcd(pi,pj)可以转化为pi和pj互为因数和倍数的关系(这样可以做一点常数上面的优化,也更好判断总体的数对的个数)。

然后我们要知道,所有的数对个数大概是O(nlogn)级别的。

于是我们可以枚举每个数的倍数预处理出所有的数对的位置对(i,j),我们尽量让i比j大,这有利于下面的思路。

然后有没有发现这个位置对很像是二维平面上的坐标啊……

欸,貌似询问也可以转化成二维坐标(r,l)啊……。

那如果我们都像上面把所有数对和询问都转化成二维坐标的话……那么就成了下面的经典二维偏序问题:

平面上有若干个点,求问横坐标小于等于r,纵坐标大于等于l的点的个数有多少。

于是我们对坐标按照横坐标排序,询问按照先r后l从小到大排序,然后横向扫一遍所有横坐标上的点,用树状数组维护当前扫过的所有点的个数。每一次询问的答案都是query(n) - query(l-1)。

非常经典的二维偏序问题,时间复杂度是O(nlogn),我还给想成了莫队,真的惭愧,果然还是练的太少……

代码

#include <bits/stdc++.h>
using namespace std;
int n,m;
const int maxn = 1e5+5;
struct quest{
    int l,r,id;
    bool operator < (const quest &b)const{
        return (this->r == b.r) ? (this->l < b.l) : (this->r < b.r);
    }
}q[maxn];
int cnt = 0;
int a[maxn];
int c[maxn];
int ans[maxn];
int lowbit(int x){
    return x & -x;
}
struct point{
    int x,y;
    bool operator < (const point &b) const{
        return (this->x == b.x) ? (this->y
查看更多
思维题 扫描线 树状数组    发布于 2019-09-02   782人围观   0条评论

题目大意

每个点的数值是通过一个从中心最大值开始的蛇形矩阵确定的。其中有m个点上的权值可用,对于q个询问,输出左下角为(x1,y1),右上角为(x2,y2)的矩阵区域内所有可用点的权值经过处理后的和。

处理的方法是,将原权值所有数位上的数相加。

题解

此题估计是模仿NOIP2014普及组T3的蛇形矩阵出的规律题目。

首先n为奇数,n*n为奇数,地图大小为 一定为奇数,所以

x = x − n/2 − 1
y = y  - n /2 - 1;
t = max(abs(x),abs(y)); //确定该点在第几圈螺旋
 if(x >= y)ans = n ∗ n −4∗ t ∗ t −2∗ t − x − y;//在向右与向上的路线上

else ans = n ∗ n −4∗ t ∗ t +2∗ t + x + y;//在向左与向下的路线上

(下面这部分代码是队友写的,我不是很清楚他的思路)

这样我们就可以O(m)的预处理所有的可用点的权值了。

对于每一次询问,都是一个矩形区域。

我们考虑二维前缀和,其答案相当于ans(x2,y2) - ans(x2,y1-1) - ans(x1-1,y2) + ans(x-1,y-1),其中ans(x,y)代表1——x,1——y矩形区域中所有值的和。

但是二维显然会爆空间,所以我们考虑如何降维。

我们可以将一个询问拆解成4个,用flag为1或-1标记此次询问的前缀和是要加还是减。

之后我们可以运用扫描线的方法,先按照x排序,然后对y进行映射,用树状数组维护映射后y上的值。用一条竖直的扫描线不断向右扫,遇到一个询问点就查询一次。

但是这样我们使用扫描线是要将所有的点当作修改操作的,而不能提前加入树状数组,否则答案会出问题。

所以我们将所有的m个点和4*q个询问加入操作合集,查询flag为-1或1,修改flag为2,按照先x、次先y、再次先修改的顺序排序,用一条平行于y轴的线从左向右扫过整个横坐标区间,用树状数组维护当前投影到y轴上的值。每当遇到修改操作,直接update树状数组中的值进行维护,遇到查询操作直接从树状数组中查询。

理论时间复杂度为O((m+4*q)log(m+4*q));

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll cal(int x, int y, int n
查看更多
树状数组    发布于 2019-05-31   337人围观   0条评论

题目大意

一场ICPC赛制比赛,有n支队伍和m次正确提交

每次正确提交包含a,b两个参数,a表示队伍编号,b表示此题的罚时。

要求对于每次事件,输出1号队伍的排名(并列的算同样的排名)

具体排名方法为:通过题目数多的队伍排名靠前,通过题目数相同的队伍总罚时低的靠前。

题解&分析

如果暴力做的话,每一修改O(1),排序查询O(nlogn),总复杂度O(mnlogn),这对于10^6的数据是不现实的。

我们要求做到O(nlogn),也就是说不能有每修改一次就排序的操作。

我们可以注意到,每一次事件发生后,只会有一个队伍的成绩发生变化,其余的成绩是不动的。

所以我们可以读入所有数据,然后计算每一次事件发生后出现的成绩二元组(通过题目数,总罚时)。这样我们可以预处理出所有可能的成绩。

之后对所有成绩排序并离散化标号,保证成绩高的离散化后标号较小。

之后我们从头开始模拟每一次事件发生。

对于每一次事件,某一个队伍成绩发生变化,从(a,b)变为(a+1,b+t),而这两个二元组都有与之对应的离散化标号。

于是我们可以考虑使用树状数组(或者线段树,不过树状数组的常数较小)对于当前所有队伍的成绩二元组进行统计。

对每一次事件,(a,b)对应的标号位置数目-1,(a+1,b+t)对应的标号位置数目+1,表示某只对于的成绩发生变化,从(a,b)变为(a+1,b+t)

每次修改完以后查询当前1号队伍的成绩二元组(nowpass,nowtime)所对应的标号之前的一共有多少支队伍,假设为k,则k+1即为当前1号队伍的排名。

 代码

#include <bits/stdc++.h>
using namespace std;
int get_num(){
    int num = 0;
    char c;
    bool flag = false;
    while((c = getchar()) == ' ' || c == '\r' || c == '\n');
    if(c == '-')
        flag = true;
    else num = c - '0';
    while(isdigit(c = getchar()))
        num = (num<<1) + (num<<3) + c - '0';
    return (flag ? -1 : 1) * num;
}
t
查看更多
莫队 树状数组    发布于 2019-05-19   460人围观   0条评论

 

题意:

给定n个数,m条询问,每个询问中包含l、r查询这个区间[l,r]中有多少个数对满足两个数的绝对值之差小于等于K

题解:

上来一看区间查询,第一反应一般都是线段树/树状数组等数据结构。

但是看一下这道题要求什么,求区间内满足条件的数对个数。

我好像没有见过能维护数对个数的树状数组或者线段树什么的。

对于数对而言,一般都应该先枚举其中一个数,剩下的才能去判断吧。

如果两个数i,j满足绝对值之差小于等于K,那么肯定能有这样一个关系式:ik<=j<=i+k

这样的话,对于某个区间内的所有数,能跟当前枚举出来的数x组成满足题意的数对的,一定在[x-k,x+k]这个区间里,我们只需要统计这个区间有多少个数就行了,这样每一侧的操作是O(Llogn)(L代表区间长度)的。

但是现在问的是对于某个给定的区间内的所有数能够组成的合法数对个数,我们就不能对区间每个数都像上面这样子暴力去统计了。那要怎么办呢?

答案就是使用莫队算法,里层查找答案时使用树状数组。

使用莫队算法能够使得我们有效利用重叠区间所统计的答案,而不必过多的修改统计树状数组来查询答案。

莫队的基本做法就是:先√n对区间分块,然后按照左端点所属块的序号为第一关键字,右端点大小为第二关键字对询问操作进行排序,然后设置l和r代表当前的位置指针, 枚举询问,不断移动l和r使之与当前的询问的l和r重合,期间添加修改信息。

对于此题,在l,r移动时,区间每加入一个数,先用树状数组统计当前区间内的满足条件的数的个数,然后将此数插入树状数组中。区间中要移除一个数的时候类似,不过两个步骤要反过来一下。读者可以自行理解一下为什么。

此题还有一个关键点是,离散化数x、x-k,x+k所组成的新数组,否则数太大树状数组会开不下的。然后离散化后的标号要O(n)用数组处理,不能使用map记录(map的查询复杂度是O(logn)的,会超时)。

总体复杂度O(n√nlogn)(实际上达不到这么大好像)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <map>
using namespace std;
const int maxn = 1e5+5;
map<int,int>u
查看更多
树状数组    发布于 2019-02-27   218人围观   0条评论

由于格式问题,这道题的题面就不往这里放了,给一个洛谷的链接吧:

[POI2005]AUT-The Bus

题解

这道题有中文题意,没有BZOJ权限号的可以去洛谷上搜到这道题。
首先会想到DP,但是一看这个数据范围也太大了,离散化后也没法做二维DP。
但是离散化也是要的,不然10的9次方的数据谁顶得住啊。
考虑这是一个二维偏序问题。一般的二维偏序都是找比当前物品的两个值都小的问题,而这道题并不是,这道题要做的是维护最大前缀和。
首先按照y排序并且离散化每个车站的y坐标,然后再按照x排序,使用树状数组维护最大前缀和即可(当然线段树应该也是可以的,只不过会比树状数组麻烦一点)。注意这里的树状数组不是维护的前缀和,而是维护前缀和的最大值。所以update操作和query操作里并不是求和,而是取最大值。


代码

#include <bits/stdc++.h>
using namespace std;
int get_num(){
    int num = 0;
    char c;
    bool flag = false;
    while((c = getchar()) == ' ' || c == '\r' || c == '\n');
    if(c == '-')
        flag = true;
    else num = c - '0';
    while(isdigit(c = getchar()))
        num = num * 10 + c - '0';
    return (flag ? -1 : 1) * num;
}
const int maxn = 1e5+5;
struct point{
    int x,y,k;
}p[maxn];
bool cmp1(point a,point b){
    return a.y < b.y;
}
bool cmp2(point a,point b){
    if(a.x == b.x)return a.y < b.y;
    return a.x < b.x;
}
int lowbit(int x){
    return x & (-x);
}
int c[maxn];
int n,m,k,cnt;
void update(int x,int d){
    while(x <= 
查看更多
CDQ分治 树状数组    发布于 2017-01-18   238人围观   0条评论

Description

你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作:
命令 参数限制 内容
1 x y A 1<=x,y<=N,A是正整数 将格子x,y里的数字加上A
2 x1 y1 x2 y2 1<=x1<= x2<=N1<=y1<= y2<=N 输出x1 y1 x2 y2这个矩形内的数字和
3 无 终止程序

Input

输入文件第一行一个正整数N。

接下来每行一个操作。

Output

对于每个2操作,输出一个对应的答案。

Sample Input

4
1 2 3 3
2 1 1 3 3
1 2 2 2
2 2 2 3 4
3

Sample Output

3
5

HINT

1<=N<=500000,操作数不超过200000个,内存限制20M。

对于100%的数据,操作1中的A不超过2000。

题解

首先对于这道题,看了范围之后,二维的数据结构是显然不能过的,于是我们可能会考虑把一维排序之后另一位上数据结构什么的,然而cdq分治却能够很好的体现它的作用。

首先,对于每一个询问求和,显然是x在它左边的并且出现时间在它之前的所有的change对他可能会有影响。

我们按照x第一关键字,y第二关键字,操作第三关键字来排序所有的询问,然后在cdq的时候,每次递归处理左半区间,按照x动态的将y这一列的值加到树状数组里,来更新右半边的所有询问,注意这里的树状数组是需要清的,也就是每次cdq都是采用不同的树状数组。

另:这题神坑,数组开小不是RE而是WA

数组范围是要开到200000*4的,因为对于每个1操作,是一个操作,而2操作根据容斥原理,是4个操作。所以复杂度大概是多少呢O(nlog2(n))cdq+树状数组。n<=800000

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int get_num(){
    int num = 0;
    char c;
    bool flag = false;
    while((c = getchar()) == ' ' || c == '\r' || c == '\n');
    if(c == '-')
      
查看更多