又扯回这道经典的可修改区间第K小值 Dynamic Ranking了:
http://www.lydsy.com:808/JudgeOnline/problem.php?id=1901
一、树套树
之前写过,挺慢的,也挺长的。
二、树状数组套权值线段树
如果没有修改,可以直接开n棵权值线段树,每一棵线段树都是在前一棵的基础上只修改一位。
每一棵线段树也都是每一个前缀所代表的“权值线段树”。
所以两棵线段树相减代表的就是一个区间的"权值线段树",就能在上面二分。
但是有修改了,每一次修改就会影响它后面的所有线段树。
所以使用BIT来维护这个前缀和,每次只会修改O(logn)棵,就没问题了。
每个区间也通过O(logn)棵线段树的加加减减得到。
现在来计算时间复杂度:
每次修改影响O(logn)课线段树,每棵线段树修改需要影响O(logn)个节点,所以是O(log^2n)的。
而线段树的节点也可以动态的开,每次修改需要的空间也是O(log^2n)级别的。
Code1:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include <map>
using namespace std;
#define N 10005
#define M 20005
int n , m , a[N];
int d[M] , D;
pair<int , int> w[N];
int r[N];
int c[N];
#define Node 2097152
#define MID int mid = l + r >> 1
#define Left l , mid
#define Right mid + 1 , r
int ch[Node][2] , sum[Node] , nodecnt;
int newnode()
{
++ nodecnt;
ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0;
return nodecnt;
}
void add(int& p , int l , int r , int x , int w)
{
if (!p) p = newnode();
if (l == r)
sum[p] += w;
else
{
MID;
if (x <= mid)
add(ch[p][0] , Left , x , w);
else
add(ch[p][1] , Right , x , w);
sum[p] = sum[ch[p][0]] + sum[ch[p][1]];
}
}
void add(int x , int w , int val)
{
for (int i = x ; i <= n ; i += i & -i)
add(c[i] , 1 , D , w , val);
}
int PP[20] , MM[20] , sp , sm;
int query(int l , int r , int K)
{
if (l == r) return l; MID;
int cnt = 0;
for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][0]];
for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][0]];
if (cnt >= K)
{
for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0];
for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0];
return query(Left , K);
}
else
{
for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1];
for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1];
return query(Right , K - cnt);
}
}
void work()
{
int i , j , k , x , y;
char str[5];
scanf("%d%d",&n,&m);
for (i = 1 ; i <= n ; ++ i)
scanf("%d",&a[i]) , d[D ++] = a[i];
for (i = 1 ; i <= m ; ++ i)
{
scanf("%s%d%d",str,&x,&y);
if (*str == 'Q')
scanf("%d",&k);
else k = 0 , d[D ++] = y;
w[i] = make_pair(x , y) , r[i] = k;
}
sort(d , d + D) , D = unique(d , d + D) - d;
for (i = 1 ; i <= n ; ++ i)
{
a[i] = lower_bound(d , d + D , a[i]) - d + 1;
add(i , a[i] , 1);
}
for (i = 1 ; i <= m ; ++ i)
{
x = w[i].first , y = w[i].second;
if (r[i])
{
sp = 0 ; for (j = y ; j ; j -= j & -j) PP[sp ++] = c[j];
sm = 0 ; for (j = x-1 ; j ; j -= j & -j) MM[sm ++] = c[j];
printf("%d\n" , d[query(1 , D , r[i]) - 1]);
}
else
{
y = lower_bound(d , d + D , y) - d + 1;
add(x , a[x] , -1);
a[x] = y;
add(x , a[x] , 1);
}
}
}
int main()
{
work();
return 0;
}
以上代码是离散化的,但如果强制在线就无法对新修改的权值进行离散化。
但实际上由于线段树的节点是完全动态开的,它的range完全可以设为数字的值域。
这样只会增加线段树的时空常数,从O(logn)变为O(log(range)),能简化不少代码。
但空间是很宝贵的……能离散化的时候尽量离散化吧。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include <map>
using namespace std;
#define N 10005
int n , m , a[N];
int D = 1e9;
int c[N];
#define Node 8388608
#define MID int mid = l + r >> 1
#define Left l , mid
#define Right mid + 1 , r
int ch[Node][2] , sum[Node] , nodecnt;
int newnode()
{
++ nodecnt;
ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0;
return nodecnt;
}
void add(int& p , int l , int r , int x , int w)
{
if (!p) p = newnode();
if (l == r)
sum[p] += w;
else
{
MID;
if (x <= mid)
add(ch[p][0] , Left , x , w);
else
add(ch[p][1] , Right , x , w);
sum[p] = sum[ch[p][0]] + sum[ch[p][1]];
}
}
void add(int x , int w , int val)
{
for (int i = x ; i <= n ; i += i & -i)
add(c[i] , 0 , D , w , val);
}
int PP[50] , MM[50] , sp , sm;
int query(int l , int r , int K)
{
if (l == r) return l; MID;
int cnt = 0;
for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][0]];
for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][0]];
if (cnt >= K)
{
for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0];
for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0];
return query(Left , K);
}
else
{
for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1];
for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1];
return query(Right , K - cnt);
}
}
void work()
{
int i , j , k , x , y;
char str[5];
scanf("%d%d",&n,&m);
for (i = 1 ; i <= n ; ++ i)
scanf("%d",&a[i]) , add(i , a[i] , 1);
for (i = 1 ; i <= m ; ++ i)
{
scanf("%s%d%d",str,&x,&y);
if (*str == 'Q')
{
scanf("%d",&k);
sp = 0 ; for (j = y ; j ; j -= j & -j) PP[sp ++] = c[j];
sm = 0 ; for (j = x-1 ; j ; j -= j & -j) MM[sm ++] = c[j];
printf("%d\n" , query(0 , D , k));
}
else
{
add(x , a[x] , -1);
a[x] = y;
add(x , a[x] , 1);
}
}
}
int main()
{
work();
return 0;
}
Query过程写的比较暴力随性……怎么方便怎么来……
然后今天推翻一切自己重写才发现出乎意料地好想好写……原来写的那是什么玩意啊 - -
再记一下关于树上链第K大值的体会……
http://www.lydsy.com:808/JudgeOnline/problem.php?id=1146
前几天刚写完暴力的树链剖分线段树套平衡树 = =
修改查询的复杂度分别是O(log^2n)和O(log^4n),很吓人。
也能使用可持久化线段树卖空间来提高时间效率……
每棵线段树记录的是它到根所有经过的节点的权值。
这样一条从x到y的链就可以用X+Y-LCA(x,y)-father(LCA(x,y))得到(这里是点权)。
考虑修改,修改一个点权影响的是它的子树,考虑DFS序列就是一个区间。
所以使用另外一棵线段树来维护DFS序列,实现区间增和单点查询,这个是很好做的,也不用增加标记。
每一条从根出发的链的"权值线段树"都能利用这棵线段树,成为O(logn)棵"权值线段树"的和,对"权值线段树"查询修改也需要O(logn)时间。
询问和修改的时空复杂度就也是O(log^2n)级别的。
这道题空间非常紧……需要卡好空间限制才能过,如果不先离散化的话。
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")
using namespace std;
#define N 80005
int n , m , a[N] , pre[N] , mcnt;
struct edge
{
int x , next;
}e[N << 1];
int dep[N] , f[17][N] , L[N] , R[N] , ncnt;
void dfs(int x , int fa)
{
f[0][x] = fa , dep[x] = dep[fa] + 1 , L[x] = ++ ncnt;
for (int i = pre[x] ; ~i ; i = e[i].next)
if (e[i].x != fa)
dfs(e[i].x , x);
R[x] = ncnt;
}
int LCA(int x , int y)
{
if (dep[x] < dep[y]) swap(x , y);
for (int i = 16 ; i >= 0 ; -- i)
if (dep[x] - dep[y] >> i & 1)
x = f[i][x];
if (x == y) return y;
for (int i = 16 ; i >= 0 ; -- i)
if (f[i][x] != f[i][y])
x = f[i][x] , y = f[i][y];
return f[0][x];
}
int Down = -1 , Up = 1e8 , t[N << 1];
#define Node 12582912
int id(int l , int r) {return l + r | l != r;}
#define MID int mid = l + r >> 1
#define Left l , mid
#define Right mid + 1 , r
int ch[Node][2] , sum[Node] , nodecnt;
int newnode()
{
++ nodecnt;
ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0;
return nodecnt;
}
void add(int& p , int l , int r , int x , int w)
{
if (!p) p = newnode();
if (l == r)
sum[p] += w;
else
{
MID;
if (x <= mid)
add(ch[p][0] , Left , x , w);
else
add(ch[p][1] , Right , x , w);
sum[p] = sum[ch[p][0]] + sum[ch[p][1]];
}
}
int PP[100] , MM[100] , sp , sm;
int query(int l , int r , int K)
{
if (l == r) return l; MID;
int cnt = 0;
for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][1]];
for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][1]];
if (cnt >= K)
{
for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1];
for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1];
return query(Right , K);
}
else
{
for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0];
for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0];
return query(Left , K - cnt);
}
}
void ADD(int l , int r , int top , int bot , int x , int w)
{
int p = id(l , r);
if (top <= l && r <= bot)
add(t[p] , Down , Up , x , w);
else
{
MID;
if (top <= mid)
ADD(Left , top , bot , x , w);
if (bot > mid)
ADD(Right , top , bot , x , w);
}
}
void QUERY(int l , int r , int x , bool flag)
{
int p = id(l , r);
if (flag) PP[sp ++] = t[p]; else MM[sm ++] = t[p];
if (l != r)
{
MID;
if (x <= mid)
QUERY(Left , x , flag);
else
QUERY(Right , x , flag);
}
}
void work()
{
int i , j , k , x , y , z;
scanf("%d%d",&n,&m);
for (i = 1 ; i <= n ; ++ i)
scanf("%d",&a[i]);
memset(pre , -1 , sizeof(pre)) , mcnt = 0;
for (i = 1 ; i < n ; ++ i)
{
scanf("%d%d",&x,&y);
e[mcnt] = (edge) {y , pre[x]} , pre[x] = mcnt ++;
e[mcnt] = (edge) {x , pre[y]} , pre[y] = mcnt ++;
}
dfs(1 , 0);
for (j = 1 ; 1 << j <= n ; ++ j)
for (i = 1 ; i <= n ; ++ i)
f[j][i] = f[j - 1][f[j - 1][i]];
for (i = 1 ; i <= n ; ++ i)
ADD(1 , n , L[i] , R[i] , a[i] , 1);
for (i = 1 ; i <= m ; ++ i)
{
scanf("%d%d%d",&k,&x,&y);
if (k)
{
sp = sm = 0 , z = LCA(x , y);
QUERY(1 , n , L[x] , 1);
QUERY(1 , n , L[y] , 1);
QUERY(1 , n , L[z] , 0);
if(f[0][z]) QUERY(1 , n , L[f[0][z]] , 0);
k = query(Down , Up , k);
if (~k) printf("%d\n" , k); else puts("invalid request!");
}
else
{
ADD(1 , n , L[x] , R[x] , a[x] , -1);
a[x] = y;
ADD(1 , n , L[x] , R[x] , a[x] , 1);
}
}
}
int main()
{
work();
return 0;
}