用来维护堆的集合;
为什么不能直接暴力,因为这样子会退化成是一条链,然后就报废了;
那么如果我们要及时完成合并,就需要平衡;
两种方法,第一种是随机数,第二种是通过一些奇怪的方法维护平衡的性质;、
左偏树,因为他总是能保证树的一边,也就是右边是平衡的;
这要得益于他设计出来的dist性质以及,左偏性质的结合,使得链只有可能出现在右边,然后我们就方便干事情了;合并的时候维护左偏性质就可以了;
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 200010;
int n;
int v[N], dist[N], l[N], r[N], idx;
int p[N];
bool cmp (int x,int y) {
if(v[x] != v[y]) return v[x] < v[y];
return x < y;
}
int find(int x) {
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int merge(int x,int y) {
if(!x || !y ) return x + y;
if(cmp(y,x)) swap(x,y);
r[x] = merge(r[x],y);
if(dist[r[x]] > dist[l[x]] ) swap(l[x], r[x]);
dist[x] = dist[r[x]] + 1;
return x;
}
int main()
{
scanf("%d",&n);
v[0] = 2e9;
while (n -- ) {
int t, x, y;
scanf("%d%d",&t,&x);
if (t == 1) {
v[++idx] = x;
dist[idx] = 1;
p[idx] = idx;
} else if (t == 2) {
scanf("%d",&y);
x = find(x) , y = find(y);
if (x != y) {
if( cmp(y,x) ) swap(x,y);
p[y] = x;
merge(x,y);
}
}else {
if(t == 3) {
printf("%d\n", v[find(x)]);
}
}else {
x = find(x);
if (cmp(r[x], l[x])) swap(l[x], r[x]);
p[x] = l[x], p[l[x]] = l[x];
merge(l[x], r[x]);
}
}
return 0;
}
P4331
这题是真的很牛逼很巧妙;
利用了从区间贪心扩展成整体最优解的形式,用了两个显而易见的结论草出来这道题;
我提一下这题值得借鉴的几个很巧妙的点就在于;
①化递增为非递减的方法,让我们的b可以重复从而搞出中位数;
②区间贪心到整体贪心;
③区间贪心时依然不满足题目的条件的,在对区间进行合并的时候又用到了新的贪心,以上;
#include<bits/stdc++.h>
using namespace std;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define ls(x) ch[0][x]
#define rs(x) ch[1][x]
#define int long long
int read() {
char cc = getchar(); int cn = 0, flus = 1;
while(cc < '0' || cc > '9') { if( cc == '-' ) flus = -flus; cc = getchar(); }
while(cc >= '0' && cc <= '9') cn = cn * 10 + cc - '0', cc = getchar();
return cn * flus;
}
const int N = 1e6 + 5;
int n, top, ans;
int a[N], b[N], ch[2][N], dis[N];
struct node {
int rt, l, r, sz, val;
} s[N];
int merge(int x,int y) {
if(!x || !y ) return x + y;
if(a[x] < a[y]) swap(x,y);
rs(x) = merge(rs(x), y);
if( dis[rs(x) > dis[ls(x)]] ) swap(rs(x), ls(x));
dis[x] = dis[rs(x)] + 1 ; return x;
}
int del(int x) { return merge(ls(x), rs(x)); }
signed main()
{
n = read(); dis[0] = -1;
rep(i, 1, n) a[i] = read() - i;
rep(i, 1, n) {
s[++top] = (node ){ i,i,i,1,a[i]};
while(top != 1 && s[top - 1].val > s[top].val ) {
-- top; s[top].rt = merge(s[top].rt, s[top + 1].rt);
s[top].sz += s[top + 1].sz; s[top].r = s[top +1].r;
while(s[top].sz > (s[top].r - s[top].l + 2) / 2) {
-- s[top].sz , s[top].rt = del(s[top].rt);
}
s[top].val = a[s[top].rt];
}
}
}
POJ3016
个人认为是相当具有思维含量的一道左偏树的例题;深刻的揭示了左偏树的本质,还套上了一层DP,很考验模型化思维的一道题目;
题目中要求把序列分成是几个单调序列。
然后这一步却是非常难想到,因为我们发现数据范围很小,所以可以采用DP来解最后一步,然后列出方程我们发现自己只需要求一下把每个序列给转变成最小那啥序列的一个花费即可。
然后对于求花费,我们能比较自然的想到可以用左偏树来求,因为左偏树的上一个模板题,就是让你做这个事情,然后就可以了;
那么我们开始考虑这个求花费的过程怎么用左偏树来实现;
我认为有两种实现方法;
第一种就是跟上面一样直接套那个板子;
第二种就是利用一些比较奇怪的性质;
如这篇题解里面的内容能够减少一些时间,于是到这里,我想左偏树能够干什么的问题应该是明了了;
copy来的代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int f[N][11]; // f[i, j] 把序列从1到i分为j个单调区间的最小代价
int cost[N][N]; // cost[i][j]表示将从i到j的序列变成单调序列的cost
int w[N], v[N], dist[N], l[N], r[N];
struct Segment
{
int root; // 左偏树的根编号
int tot_size; // 左偏树所维护的序列的元素总个数(包括大于左偏树树顶的元素)
}stk[N];
int merge(int x, int y)
{
if (!x || !y) return x + y;
if (v[x] < v[y]) x ^= y ^= x ^= y;
r[x] = merge(r[x], y);
if (dist[r[x]] > dist[l[x]]) l[x] ^= r[x] ^= l[x] ^= r[x];
dist[x] = dist[r[x]] + 1;
return x;
}
int pop(int x)
{
return merge(l[x], r[x]);
}
void get_cost(int u)
{
int top = 0, res = 0; // res保存的是单调栈内存在的树的总cost
for (int i = u; i <= n; ++i) // 从u到n构造序列
{
// 根编号, 总元素个数
auto cur = Segment({i, 1});
l[i] = r[i] = 0; // 初始化, 要正求一次负求一次所以要这样
// 合并两颗左偏树
while (top && v[cur.root] < v[stk[top].root])
{
if (cur.tot_size & 1) res += v[stk[top].root] - v[cur.root];
// 如果个数都是奇数, 那么合并两个堆之后要保持中位数性质, 需要再丢掉1个数
// 比如一个5里面都取3, 两个5就是10里面只能取第5个
bool is_pop = cur.tot_size % 2 && stk[top].tot_size % 2;
// 合并2个左偏树
cur.root = merge(cur.root, stk[top].root);
cur.tot_size += stk[top].tot_size;
if (is_pop) cur.root = pop(cur.root);
top -- ;
}
stk[ ++ top] = cur;
cost[u][i] = min(cost[u][i], res);
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &w[i]);
memset(cost, 0x3f, sizeof cost);
for (int i = 1; i <= n; ++i) v[i] = w[i] - i;
for (int i = 1; i <= n; ++i) get_cost(i);
for (int i = 1; i <= n; ++i) v[i] = -w[i] - i;
for (int i = 1; i <= n; ++i) get_cost(i);
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
for (int k = 1; k <= i; ++k)
f[i][j] = min(f[i][j], f[i - k][j - 1] + cost[i - k + 1][i]);
printf("%d\n", f[n][m]);
return 0;
}