POJ2104【主席树】【整体二分】

题目链接 kth number
主 席 树 主席树
主席树模板题
关于主席树没啥好讲的,其实就是动态开点+线段树

#pragma GCC optimize(2)
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int mod = 1e9+9;
int Case = 1;
int n, m, root[maxn], tot;
struct node{
	int l, r;
	int sum;
}tr[maxn<<5];
void pushup(int rt) {
	tr[rt].sum = tr[tr[rt].l].sum + tr[tr[rt].r].sum;
}
void build(int &rt, int l, int r) {
	rt = ++tot;
	if(l == r) {tr[rt].sum = 0;return;}
	int mid = (l + r)/2;
	build(tr[rt].l, l, mid);
	build(tr[rt].r, mid+1, r);
	pushup(rt);
}
void insert(int &rt, int pr, int pos, int l, int r) {
	rt = ++tot;
	if(l == r) {
		tr[rt].sum++;
		return;
	}
	tr[rt] = tr[pr];
	int mid = (l + r)/2;
	if(pos <= mid) insert(tr[rt].l, tr[pr].l, pos, l, mid);
	else insert(tr[rt].r, tr[pr].r, pos, mid+1, r);
	pushup(rt);
}
vector<int>ve;
int cc[maxn];
int getid(int x) {
	return lower_bound(ve.begin(), ve.end(), x)-ve.begin()+1;
}
int query(int lc, int rc, int k, int l, int r) {
	if(l == r) return l;
	int sum = tr[tr[rc].l].sum-tr[tr[lc].l].sum, mid = (l+r)/2;
	if(sum >= k) return query(tr[lc].l, tr[rc].l, k, l, mid);
	else return query(tr[lc].r, tr[rc].r, k-sum, mid+1, r);
}

void solve() {
	tot = 0;
	scanf("%d%d", &n, &m);
	build(root[0], 1, n);
	for(int i = 1; i <= n; i++) {
		scanf("%d", &cc[i]);
		ve.push_back(cc[i]);
	}
	sort(ve.begin(), ve.end());
	ve.erase(unique(ve.begin(), ve.end()), ve.end());
	int s = ve.size();
	for(int i = 1; i <= n; i++) {
		int id = getid(cc[i]);
		insert(root[i], root[i-1], id, 1, s);
	}
	for(int i = 1; i <= m; i++) {
		int l, r, k;scanf("%d%d%d", &l, &r, &k);
		printf("%d\n", ve[query(root[l-1], root[r], k, 1, s)-1]);
	}
    return;
}

int main() {
    while(Case--) {
        solve();
    }
    return 0;
}

整 体 二 分 整体二分
整体二分中提现了分治的思想,但不是算法的关键,关键在分治的过程中对权值二分。

对于小于mid的更新就用树状数组更新,并且对于已经可以确定范围的询问也进行划分。把小于mid的询问和更新分成一组,把大于mid的询问减去左半部分已知的询问和大于mid的询问放在一起。
	if(cc[i].op) {如果是询问
            int tmp=tree.query(cc[i].y)-tree.query(cc[i].x-1);
            if(tmp>=cc[i].k) t1[++cnt1]=cc[i];
            else cc[i].k-=tmp, t2[++cnt2]=cc[i];
        }
        else {如果是更新
            if(cc[i].x<=mid) t1[++cnt1]=cc[i], tree.update(cc[i].pos, cc[i].y);
            else t2[++cnt2]=cc[i];
            
        }
直到递归到二分的权值上界等于下界的时候在这个范围内的所有询问的答案也必然是此时的权值。
f(l>r || L>R) return;
    if(L==R)
    {
        for(int i=l;i<=r;i++) if(cc[i].op) res[cc[i].pos]=L;
        return;
    }
最后就是分组后分治继续二分
for(int i=1;i<=cnt1;i++) if(!t1[i].op) tree.update(t1[i].pos, -t1[i].y);
    for(int i=1;i<=cnt1;i++) cc[l+i-1]=t1[i];
    for(int i=1;i<=cnt2;i++) cc[l+cnt1+i-1]=t2[i];
    cal(L, mid, l, l+cnt1-1);cal(mid+1, R, l+cnt1, r);
细节:
树状数组每次清零别用memset
权值的上下界和序列的指针变量别搞混了。。。
#pragma GCC optimize(2)
#include<cstdio>
using namespace std;
const int maxn = 5e5+5;
typedef long long ll;
const ll mod = 1e9+7;
int Case = 1;
int n, m;
struct node{
    int x, y, k, pos, op;
}cc[maxn], t1[maxn], t2[maxn];
int res[maxn];
struct Tree{
    int c[maxn], up;
    int lowbit(int x) {
        return (x&(-x));
    }
    void update(int pos, int val) {
        for(int i = pos; i <= up; i += lowbit(i)) {
            c[i] += val;
        }
    }
    int query(int pos) {
        int res  = 0;
        for(int i = pos; i >= 1; i -= lowbit(i)) {
            res += c[i];
        }
        return res;
    }
}tree;
void cal(int L, int R, int l, int r) {
    if(l>r || L>R) return;
    if(L==R)
    {
        for(int i=l;i<=r;i++) if(cc[i].op) res[cc[i].pos]=L;
        return;
    }
    int mid=(L+R)>>1, cnt1=0, cnt2=0;
    for(int i = l; i <= r; i++) {
        if(cc[i].op) {
            int tmp=tree.query(cc[i].y)-tree.query(cc[i].x-1);
            if(tmp>=cc[i].k) t1[++cnt1]=cc[i];
            else cc[i].k-=tmp, t2[++cnt2]=cc[i];
        }
        else {
            if(cc[i].x<=mid) t1[++cnt1]=cc[i], tree.update(cc[i].pos, cc[i].y);
            else t2[++cnt2]=cc[i];
            
        }
    }
    for(int i=1;i<=cnt1;i++) if(!t1[i].op) tree.update(t1[i].pos, -t1[i].y);
    for(int i=1;i<=cnt1;i++) cc[l+i-1]=t1[i];
    for(int i=1;i<=cnt2;i++) cc[l+cnt1+i-1]=t2[i];
    cal(L, mid, l, l+cnt1-1);cal(mid+1, R, l+cnt1, r);
}
void solve() {
    scanf("%d%d", &n, &m);
    tree.up = n;
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        cnt++;
        scanf("%d", &cc[cnt].x);
        cc[cnt].y = 1;cc[cnt].k = 0;cc[cnt].pos = i;cc[cnt].op = 0;
    }
    for(int i = 1; i <= m; i++) {
        cnt++;
        scanf("%d%d%d", &cc[cnt].x, &cc[cnt].y, &cc[cnt].k);
        cc[cnt].pos = i;cc[cnt].op = 1;
    }
    cal(-1e9, 1e9, 1, cnt);
    for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
    return;
}
int main() {
    //g++ -std=c++11 -o2 1.cpp -o f && ./f < in.txt
    //ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt","w",stdout);
#endif
//scanf("%d", &Case);
while(Case--) {
    solve();
    }
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值