海亮 7.6 数据结构2
Hailiang 7.6 数据结构2
P2596 [ZJOI2006] 书架
优化了\(rotate\)函数的写法
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
#define int long long
#define mid ((l+r)>>1)
const int N = 4e5 + 5;
const int inf = 0x3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , ans , a[N] , pos[N] , rt;//pos[x]表示x这个节点在树中的节点编号
int tot;
struct node { int son[2] , fa , val , sz; } t[N];
void up ( int x ) { t[x].sz = t[ls(x)].sz + t[rs(x)].sz + 1; }
int get ( int x ) { return x == rs(t[x].fa); }
void add ( int x , int y , int k ) //x和y固定边 且x为y的父亲 左面还是右面
{
t[y].fa = x;
t[x].son[k] = y;
}
void rotate ( int x )
{
int y = t[x].fa , z = t[y].fa , chk = get(x) , chky = get(y);
add ( y , t[x].son[chk^1] , chk );
add ( x , y , chk ^ 1 );
add ( z , x , chky );
up(y) , up(x);
}
void splay ( int x , int goal = 0 )
{
for ( int f = t[x].fa ; ( f = t[x].fa ) != goal ; rotate(x) )
if ( t[f].fa != goal ) rotate ( get(f) == get(x) ? f : x );
if ( !goal ) rt = x;
}
//三种情况:
//1.三点共线:转fa再转x
//2.三点不共线:转两次x
//3.如果当前的爷爷节点是根 直接单旋x
int build ( int l , int r , int fa )
{
if ( l > r ) return 0;
int cur = ++tot;
t[cur] = { { 0 , 0 } , fa , a[mid] , 1 };
pos[a[mid]] = cur;
ls(cur) = build ( l , mid - 1 , cur );
rs(cur) = build ( mid + 1 , r , cur );
up(cur);
return cur;
}
int rk ( int x )
{
splay(pos[x]);
return t[ls(rt)].sz;
}
int kth ( int k )
{
int cur = rt;
while(1)
{
if ( k < t[ls(cur)].sz + 1 ) cur = ls(cur);
else if ( k == t[ls(cur)].sz + 1 ) return splay(cur) , cur;
else k -= t[ls(cur)].sz + 1 , cur = rs(cur);
}
}
void tb ( int x , int k )
{
splay(pos[x]);
if ( !t[rt].son[k] ) return;
if ( !t[rt].son[!k] ) { t[rt].son[!k] = t[rt].son[k] , t[rt].son[k] = 0; return; }
int cur = t[rt].son[!k];
while ( t[cur].son[k] ) cur = t[cur].son[k];
add ( cur , t[rt].son[k] , k );
t[rt].son[k] = 0;
splay(cur);
}
void ins ( int x , int k )
{
if ( k == 0 ) return;
if ( k == -1 ) k = 0;//0是前驱 1是后继
splay(pos[x]);
int cur = t[rt].son[k];
while ( t[cur].son[!k] ) cur = t[cur].son[!k];
swap ( t[cur].val , t[rt].val ) , swap ( pos[t[cur].val] , pos[t[rt].val] );
}
void print ( int cur )
{
if ( ls(cur) ) print(ls(cur));
cout << t[cur].val << ' ';
if ( rs(cur) ) print(rs(cur));
}
string s;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
rt = build ( 1 , n , 0 );
for ( int i = 1 , x , val ; i <= m ; i ++ )
{
cin >> s; x = read();
if ( s[0] == 'T' ) tb ( x , 0 );
if ( s[0] == 'B' ) tb ( x , 1 );
if ( s[0] == 'I' ) val = read() , ins ( x , val );
if ( s[0] == 'A' ) cout << rk(x) << endl;
if ( s[0] == 'Q' ) cout << t[kth(x)].val << endl;
// print(rt); cout << endl;
}
return 0;
}
P4602 [CTSC2018] 混合果汁
题意为求混合果汁中最小美味值最大的果汁 显然想到二分
那么我们对于一个最小美味值\(d\) 只能考虑\(d_i>d\)的所有果汁 那么我们贪心地取价格最小的果汁 这个果汁用完了再取第二小的果汁 以此类推
所以我们对于\(d\)值进行持久化 建立以价格为下标的主席树 节点记录两个信息:子树中的总价格和总的体积
主席树总是坑点很多
- 注意特判整棵树都没有\(n\)升果汁的情况
- 注意区分体积和权值 查询的时候左面必须查询 左边不够了再去查询右面 去右面的话当前体积需要减去左面的体积
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
#define lson ls(p),l,mid
#define rson rs(p),mid+1,r
#define mid ((l+r)>>1)
#define int long long
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , maxp = -inf , tot , root[N];
struct juice { int d , p , l; } a[N];
struct node { int son[2] , sumcost , sumv; } t[N<<6];//总花费 总体积
int new_node ( int p ) { t[++tot] = t[p]; return tot; }
void upd ( int &p , int l , int r , int x , int val )
{
p = new_node(p);
t[p].sumcost += x * val;
t[p].sumv += val;
if ( l == r ) return;
if ( x <= mid ) upd ( ls(p) , l , mid , x , val );
else upd ( rs(p) , mid + 1 , r , x , val );
}
int query ( int p , int l , int r , int x ) //凑出来x个体积需要多少权值
{
if ( l == r ) return l * x;
if ( t[ls(p)].sumv >= x ) return query ( ls(p) , l , mid , x );
else return t[ls(p)].sumcost + query ( rs(p) , mid + 1 , r , x - t[ls(p)].sumv );//这里减的是sumv
}
int check ( int d , int g , int L )
{
int res = query ( root[d] , 1 , N , L );
return res <= g && t[root[d]].sumv >= L; //这里要判断是否整棵树都不够果汁量
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].d = read() , a[i].p = read() , a[i].l = read() , maxp = max ( a[i].p , maxp );
sort ( a + 1 , a + n + 1 , [](const juice a , const juice b) { return a.d > b.d; } );
for ( int i = 1 ; i <= n ; i ++ )
root[i] = root[i-1] , upd ( root[i] , 1 , N , a[i].p , a[i].l );
for ( int i = 1 , g , L ; i <= m ; i ++ )
{
g = read() , L = read();
int l = 1 , r = n;
while ( l <= r )
{
if ( check ( mid , g , L ) ) r = mid - 1;
else l = mid + 1;
}
cout << ( l == n + 1 ? -1 : a[l].d ) << endl;
}
return 0;
}
Hossam and Range Minimum Query
比上一题还裸的主席树问题
我们考虑随机异或来赋值 对于每一个数 用\(map\)存每一个值对应的随机数字
由于异或的美妙性质 那么区间内只要不是奇数出现次数的值 异或起来都是0
用值域作为下标的主席树来维护即可
\(upd \ on\ 16:28\): 这题是真nmlgbdnb 一道题来两个很难调的大错误
- \(mid\)写法必须为\((l+(r-l)/2)\)
- 异或和不等号连用也需要打括号
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
#define lson ls(p),l,mid
#define rson rs(p),mid+1,r
#define mid (l+(r-l)/2)
const int N = 2e5 + 5;
const int inf = 2e9;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , tot , root[N] , lastans , a[N] , q;
map<int,int> mp;
struct tree
{
struct node { int son[2] , val; } t[N<<5];
int tot = 0;
int new_node ( int x ) { t[++tot] = t[x]; return tot; }
void upd ( int &p , int l , int r , int x , int val )
{
p = new_node(p);
t[p].val ^= val;
if ( l == r ) return;
if ( x <= mid ) upd ( ls(p) , l , mid , x , val );
else upd ( rs(p) , mid + 1 , r , x , val );
}
int query ( int u , int v , int l , int r )
{
if ( l == r ) return l;
if ( ( t[ls(u)].val ^ t[ls(v)].val ) != 0 ) return query ( ls(u) , ls(v) , l , mid );
else return query ( rs(u) , rs(v) , mid + 1 , r );
}
}T;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
mt19937 rand(time(0));
n = read();
for ( int i = 1 ; i <= n ; i ++ )
{
a[i] = read();
if ( !mp[a[i]] ) mp[a[i]] = rand();//这里如果a[i]已经有哈希值了就直接跳过
root[i] = root[i-1];
T.upd ( root[i] , 0 , inf , a[i] , mp[a[i]] );
}
q = read();
for ( int i = 1 , l , r ; i <= q ; i ++ )
{
l = read() ^ lastans , r = read() ^ lastans;
lastans = T.query ( root[l-1] , root[r] , 0 , inf );
if ( lastans == inf ) lastans = 0;
cout << lastans << endl;
}
return 0;
}
P5854 【模板】笛卡尔树
笛卡尔树 为一棵树中节点的第一权值按照平衡树规律排列 第二权值按照小根堆排列的树
那么我们考虑构建这棵树
因为下标满足单调递增 那么一定按照顺序插入即可满足平衡树规律
也就是先插入的节点必定为后插入节点的左子树 或者后插入的节点为先插入的节点的右子树
所以我们需要维护一个从根节点一直走右儿子形成的链(用单调栈实现)
图解大法好!
\
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5;
inline int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , ls[N] , rs[N] , a[N] , sta[N] ;
long long ansl , ansr;
signed main ()
{
n = read();
for ( int i = 1 , pos = 0 , top = 0 ; i <= n ; i ++ )
{
a[i] = read();
while ( pos && a[sta[pos]] > a[i] ) pos --;
//弹出后面的东西直到比最右链上的某一个点大
if ( pos ) rs[sta[pos]] = i;//更新那个小点的右儿子
if ( pos < top ) ls[i] = sta[pos+1];//如果这个节点不是最后面的节点 那么把这些后面的节点全给左子树
sta[top=++pos] = i;//继续维护最右链
}
for ( int i = 1 ; i <= n ; i ++ ) ansl ^= 1LL * i * ( ls[i] + 1 ) , ansr ^= 1LL * i * ( rs[i] + 1 );
printf ( "%lld %lld" , ansl , ansr );
return 0;
}
P1377 [TJOI2011] 树的序
因为插入顺序上儿子比父亲晚插入 满足堆的性质 在权值上满足平衡树的限制 所以我们将权值和下标倒过来 就可以转化为板子 最后我们输出前序遍历即可(输出的是权值 满足平衡树限制)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5;
inline int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , ls[N] , rs[N] , a[N] , sta[N];
long long ansl , ansr;
void dfs ( int x )
{
cout << x << ' ';
if ( ls[x] ) dfs ( ls[x] );
if ( rs[x] ) dfs ( rs[x] );
}
signed main ()
{
n = read();
for ( int i = 1 , x ; i <= n ; i ++ ) x = read() , a[x] = i;
//这里“把权值当做下标,以下标为权值输入a数组,就可以转化成板子
for ( int i = 1 , top = 0 , pos = 0 ; i <= n ; i ++ )//这里的i是权值 我们要让权值满足平衡树性质 下标满足小根堆限制
{
while ( pos && a[sta[pos]] > a[i] ) pos --;
if ( pos ) rs[sta[pos]] = i;
if ( pos < top ) ls[i] = sta[pos+1];
sta[top=++pos] = i;
}
dfs ( sta[1] );
return 0;
}
可持久化Trie裸题
第一个版本:
第二个版本:
第三个版本:
第四个版本:
和主席树思想类似()
维护前缀异或和 如果询问是\(l,r\) 那么答案就是$s[p-1] 异或s[n]异或x $ 可以看到我们的\(s[n] 异或 x\)是定值
对于每一次加异或和建立一个版本 版本的root维护这个版本是第几个版本
那么在查询的时候 我们要在r-1个版本中 查询大于等于l-1的所有数 在跳的时候优先跳和\(s[n]异或x\)的这一位相反的节点即可
主席树动态开点的时候需要提前给儿子开点而不是自己开点 这一点与主席树不同
#include <bits/stdc++.h>
using namespace std;
const int N = 5e7 + 5;
inline int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int latest[N] , s[N] , ch[N][2] , tot , n , m , root[N];
int new_node ( int x )
{
tot ++;
ch[tot][0] = ch[x][0];
ch[tot][1] = ch[x][1];
latest[tot] = latest[x];
return tot;
}
void up ( int p ) { latest[p] = max ( latest[ch[p][0]] , latest[ch[p][1]] ); }
void insert ( int p , int num , int pos )//在父亲新建儿子 你说得对
{
if ( num < 0 ) return latest[p] = pos , void();
int st = ( s[pos] >> num ) & 1;
ch[p][st] = new_node(ch[p][st]);
insert ( ch[p][st] , num - 1 , pos );
up(p);
}
int query ( int p , int num , int l , int val )
{
if ( num < 0 ) return s[latest[p]] ^ val;
int st = ! ( ( val >> num ) & 1 );
if ( latest[ch[p][st]] >= l ) return query ( ch[p][st] , num - 1 , l , val );
return query ( ch[p][!st] , num - 1 , l , val );
}
char c;
signed main ()
{
n = read() , m = read();
memset ( latest , -1 , sizeof latest );
root[0] = new_node(root[0]);
insert ( root[0] , 30 , 0 );
for ( int i = 1 ; i <= n ; i ++ )
{
int x = read();
s[i] = s[i-1] ^ x;
root[i] = new_node(root[i-1]);
insert ( root[i] , 30 , i );
}
for ( int i = 1 , l , r , x ; i <= m ; i ++ )
{
cin >> c;
if ( c == 'A' )
{
++n;
s[n] = s[n-1] ^ read();
root[n] = new_node(root[n-1]) , insert ( root[n] , 30 , n );
}
else
{
l = read() , r = read() , x = read();
printf ( "%d\n" , query ( root[r-1] , 30 , l - 1 , s[n] ^ x ) );
}
}
return 0;
}
MinimizOR
一个结论:区间内的最小代价为区间内的前31小数中的某两个
证明:采用数学归纳法 原命题等价于小于\(2^k\)的数中的或运算最小值出现在前\(k+1\)小数中的两个数之间
也就是说如果区间中每一个数都小于\(2^k\) 那么每次询问只取区间前\(k+1\)小值 两两或起来一定是正确答案
对于\(k=1\) 取\(2^1=2\) 则取前两小的数就是答案
归纳步骤:如果序列中所有数小于
那么对于\(k>1\)
- 如果序列中最高位\(\ge2个0\) 高位贪心 当前位选\(0\) 最小值一定在前\(k\)小中
- 如果有\(1\)个\(0\) 那么当前位置一定为\(1\) 那么也在前\(k\)小中可以找到
- 如过没有\(0\) 那么只能选\(1\) 结果是\(k-1\)位中的最小值 那么也在前\(k\)小中
口胡证明:每一位都需要贪心地选尽可能多的\(0\) 那么选很多高位为\(0\)的最好 不过有可能底下位无法满足\(0\) 那么我们尽量让\(1\)往底下去即可
效率低下地调题 注意不能两个相同的数异或起来
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mkp make_pair
#define mid ((l+r)>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e5 + 5;
const int inf = 2e9;
inline int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , q , a[N] , s[N];//temp下标数组
pii t[N<<2];//<权值,下标>
void up ( int p ) { t[p] = min ( t[ls] , t[rs] ); }
void build ( int p , int l , int r )
{
if ( l == r ) return t[p] = mkp ( a[l] , l ) , void();
build ( lson ) , build ( rson ) , up(p);
}
void upd ( int p , int l , int r , int x , int val )
{
if ( l == r ) return t[p] = mkp ( val , l ) , void();
if ( x <= mid ) upd ( lson , x , val );
else upd ( rson , x , val );
up(p);
}
pii query ( int p , int l , int r , int x , int y )
{
if ( x <= l && r <= y ) return t[p];
pii ans = mkp ( inf , inf );
if ( x <= mid ) ans = min ( ans , query ( lson , x , y ) );
if ( mid + 1 <= y ) ans = min ( ans , query ( rson , x , y ) );
return ans;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
while ( T -- )
{
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
q = read();
build ( 1 , 1 , n );
for ( int i = 1 ; i <= q ; i ++ )
{
int l = read() , r = read();
int cntt = min ( r - l + 1 , 31 );
for ( int j = 1 ; j <= cntt ; j ++ )
{
s[j] = query ( 1 , 1 , n , l , r ).se;
upd ( 1 , 1 , n , s[j] , inf );
}
for ( int j = 1 ; j <= cntt ; j ++ ) upd ( 1 , 1 , n , s[j] , a[s[j]] );
int minn = inf;
for ( int j = 1 ; j < cntt ; j ++ )
for ( int k = j + 1 ; k <= cntt ; k ++ )//注意不能两个相同的数异或起来
minn = min ( minn , a[s[j]] | a[s[k]] );
cout << minn << endl;
}
}
return 0;
}
Lexicographically Small Enough
我们枚举第一个串和第二个串的公共前缀长度\(len\) 那么对于每一个位置\(i\) 我们维护出从这个\(i\)位置才能判断出来大小的\(s'\)和\(t\)串的最小操作次数之和
即\([1,i-1]\)这些位置都相同 而\(i\)位置\(s\)小于\(t\)的最小操作次数
对于前者 我们找到后面最近的相同字符移动过来 对于后者 我们找到最近的\(c<t[i]\)移动过来