bzoj4552(线段树合并+stl||二分+线段树)

Description
在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q
位置上的数字。
Input
输入数据的第一行为两个整数n和m。n表示序列的长度,m表示局部排序的次数。1 <= n, m <= 10^5第二行为n个整
数,表示1到n的一个全排列。接下来输入m行,每一行有三个整数op, l, r, op为0代表升序排序,op为1代表降序
排序, l, r 表示排序的区间。最后输入一个整数q,q表示排序完之后询问的位置, 1 <= q <= n。1 <= n <= 10^5
,1 <= m <= 10^5
Output
输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第q位置上的数字。
Sample Input
6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3
Sample Output
5


s o l u t i o n solution solution:
这题有两种做法,复杂度与思路都不同,但都是比较经典的做法。

做法①:
想法比较暴力
初始对每个点开一棵权值线段树
每次询问的时候,把 [ l , r ] [l,r] [l,r]两端的块分裂,再把区间内所有的线段树合并
最后暴力查询一下,用 s e t set set维护所有的块

复杂度?
可以发现,我们新建的块是 O ( n ) O(n) O(n)
那么我们合并以及分裂的操作涉及的块不难发现也是 O ( n ) O(n) O(n)
总复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

分裂的时候还要分块的种类,本人使用了 m a p map map
代码细节较复杂。

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define rept(i,x) for(int i = linkk[x];i;i = e[i].n)
#define P pair<int,int>
#define Pil pair<int,ll>
#define Pli pair<ll,int>
#define Pll pair<ll,ll>
#define pb push_back 
#define pc putchar
#define mp make_pair
#define file(k) memset(k,0,sizeof(k))
#define ll long long
#define hash Hash
#define fr first
#define se second
#define IT set<P>::iterator
int rd()
{
	int num = 0;char c = getchar();bool flag = true;
	while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
	while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
	if(flag) return num;else return -num;
}
int n,m;
int a[101000];
set<P>h;
set<P>::iterator it;
map<P,int>hash;
map<P,int>op;
namespace tree
{
	stack<int>st;
	int cnt_id = 0;
    struct Tree{int ls,rs,num;}tr[10100000];
	int New(){int x;if(!st.empty()) x=st.top(),st.pop();else x=++cnt_id;return x;}
	void free(int x){tr[x].ls = tr[x].rs = tr[x].num = 0;st.push(x);}
    void update(int rt){tr[rt].num = tr[tr[rt].ls].num + tr[tr[rt].rs].num;}
	void build(int &rt,int l,int r,int v)
	{
		rt = New();tr[rt].num++;
		if(l == r) return;
		int mid = l+r>>1;
		if(v <= mid) build(tr[rt].ls,l,mid,v);
		else build(tr[rt].rs,mid+1,r,v);
	}
	int merge(int x,int y)
	{
		if(!x) return y;
		if(!y) return x;
		int t = New();
		tr[t].ls = merge(tr[x].ls,tr[y].ls);
		tr[t].rs = merge(tr[x].rs,tr[y].rs);
		tr[t].num = tr[tr[t].ls].num + tr[tr[t].rs].num;
		free(x);free(y);
		return t;
	}
	void split1(int &x,int y,int k)
	{//分裂出前k大 
	    x = New();
	    if(k > tr[tr[y].rs].num) split1(tr[x].ls,tr[y].ls,k-tr[tr[y].rs].num),swap(tr[x].rs,tr[y].rs);
	    else if(k == tr[tr[y].rs].num) swap(tr[x].rs,tr[y].rs);
	    else split1(tr[x].rs,tr[y].rs,k);
	    update(x);update(y);
	}
	void split2(int &x,int y,int k)
	{//分裂出前k小
	    x = New();
	    if(k > tr[tr[y].ls].num) split2(tr[x].rs,tr[y].rs,k-tr[tr[y].ls].num),swap(tr[x].ls,tr[y].ls);
	    else if(k == tr[tr[y].ls].num) swap(tr[x].ls,tr[y].ls);
	    else split2(tr[x].ls,tr[y].ls,k);
	    update(x);update(y);
	}
	int query(int rt,int l,int r,int k)
	{//第k小
	    if(l == r) return l;
	    int mid = l+r>>1;
		if(k <= tr[tr[rt].ls].num) return query(tr[rt].ls,l,mid,k);
		else return query(tr[rt].rs,mid+1,r,k-tr[tr[rt].ls].num); 
	}
}using namespace tree;
IT fuckit(IT it,int l,int r)
{
	int _l = it->fr,_r = it->se,to,_rt = hash[mp(_l,_r)],_op = op[mp(_l,_r)];
	//_l   l   r  _r -> [_l,l-1] [l,_r]
	if(_op == 1)  split1(to,_rt,l-_l); else split2(to,_rt,l-_l);
	hash[mp(_l,l-1)] = to;   op[mp(_l,l-1)] = _op;
	hash[mp(l,_r)  ] = _rt;  op[mp(l,_r)  ] = _op;
	hash.erase(mp(_l,_r));   op.erase(mp(_l,_r));
	h.erase(h.lower_bound(mp(_l,_r))); h.insert(mp(_l,l-1)); h.insert(mp(l,_r));
	it = h.lower_bound(mp(l,_r));
	return it;
}
int work(int k,int l,int r)
{
	int tmp = tree::New();
	while(1)
	{
		it = h.lower_bound(mp(l,0));
		if(it == h.end() || it->second > r) break;
		tmp = tree::merge(tmp,hash[(*it)]);
		hash.erase(mp(it->first,it->second));
		op.erase(mp(it->fr,it->se));
		h.erase(it);
    }//合并所有子区间 
    it = h.lower_bound(mp(l,0));
    if((it == h.end() || it->first > r) && it != h.begin())
    {
    	it--;
	    if(it->first < l && it->second > r) it = fuckit(it,l,r);
        else it++;
	}
    if(it != h.end() && it->first <= r)
    {
    	int to;
    	int _op = op[mp(it->fr,it->se)],_l = it->fr,_r = it->se,_rt = hash[mp(_l,_r)];
    	if(_op == 1) split1(to,_rt,r-_l+1);
    	else split2(to,_rt,r-_l+1);
    	hash[mp(r+1,_r)] = _rt;  op[mp(r+1,_r)] = _op;
		op.erase(mp(_l,_r));     hash.erase(mp(_l,_r));
        h.insert(mp(r+1,_r));    h.erase(it);
        tmp = tree::merge(tmp,to);
	}//处理右端区间
    it = h.lower_bound(mp(l,0));
    if(it == h.begin());
    else
    {
    	it--;
    	if(it->second >= l)
    	{
    	    if(it->second > r) it = fuckit(it,l,r);
    		int to;
    		int _l = it->fr,_r = it->se,_op = op[mp(_l,_r)],_rt = hash[mp(_l,_r)];
    		if(_op == 1) split2(to,_rt,_r-l+1);
    		else split1(to,_rt,_r-l+1);
    		hash[mp(_l,l-1)] = _rt;  op[mp(_l,l-1)] = _op;
    		op.erase(mp(_l,_r));     hash.erase(mp(_l,_r));
    		h.insert(mp(_l,l-1));    h.erase(it);
    		tmp = tree::merge(tmp,to);
    	}
    }//处理左端区间
    h.insert(mp(l,r)); op[mp(l,r)] = k; hash[mp(l,r)] = tmp;
}
void init()
{
	n = rd();m = rd();
	rep(i,1,n) a[i] = rd();
	rep(i,1,n) h.insert(mp(i,i)),tree::build(hash[mp(i,i)],1,n,a[i]);
	rep(i,1,m)
	{
		int k = rd(),l = rd(),r = rd();
		work(k,l,r);
	}
	int pl = rd();
	for(it = h.begin();it != h.end();it++) if(pl >= it->fr && pl <= it->se) break;
	int _rt = hash[mp(it->fr,it->se)],_op = op[mp(it->fr,it->se)],l = it->fr,r = it->se;
	printf("%d\n",tree::query(hash[mp(it->fr,it->se)],1,n, _op?r-pl+1:pl-l+1 ));
}
int main()
{
	init();
	return 0;
}

做法②:
直接对数字排序不好维护。
对01的排序非常好维护
考虑二分
二分一个答案 m i d mid mid后把 ≥ m i d \geq mid mid的值设为1, &lt; m i d &lt;mid <mid的值设为0,剩下维护的操作就只涉及区间修改,区间和查询了
复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

代码简短

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define rept(i,x) for(int i = linkk[x];i;i = e[i].n)
#define P pair<int,int>
#define Pil pair<int,ll>
#define Pli pair<ll,int>
#define Pll pair<ll,ll>
#define pb push_back 
#define pc putchar
#define mp make_pair
#define file(k) memset(k,0,sizeof(k))
#define ll long long
#define ls rt<<1
#define rs rt<<1|1
int rd()
{
	int num = 0;char c = getchar();bool flag = true;
	while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
	while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
	if(flag) return num;else return -num;
}
int n,m;
int a[101000],b[101000];
int num[401000],det[401000];
struct data{int op,l,r;}q[101000];
void pushdown(int rt,int l,int r)
{
	int mid = l+r>>1;
	if(det[rt] == 1)
		num[ls] = mid-l+1,
		num[rs] = r-mid,
		det[ls] = det[rs] = 1,
		det[rt] = 0;
	else if(det[rt] == 2) num[ls] = num[rs] = 0,det[ls] = det[rs] = 2,det[rt] = 0;
}
void build(int rt,int l,int r)
{
	if(l == r)
	{
		num[rt] = b[l];
		return;
	}
	int mid = l+r>>1;
	build(ls,l,mid);build(rs,mid+1,r);
	det[rt] = 0;num[rt] = num[ls] + num[rs];
}
int query(int rt,int l,int r,int x,int y)
{
	if(y < l || x > r) return 0;
	if(x <= l && r <= y) return num[rt];
	int mid = l+r>>1;
	pushdown(rt,l,r);
	return query(ls,l,mid,x,y)+query(rs,mid+1,r,x,y);
}
void change(int rt,int l,int r,int x,int y,int v)
{
	if(x <= l && r <= y)
	{
		det[rt] = 2-v;
		num[rt] = (r-l+1) * v;
		return;
	}
	pushdown(rt,l,r);
	int mid = l+r>>1;
	if(mid >= x) change(ls,l,mid,x,y,v);
	if(mid <  y) change(rs,mid+1,r,x,y,v);
	num[rt] = num[ls] + num[rs];
}
void work()
{
	build(1,1,n);
	rep(i,1,m)
	{
		int op = q[i].op,l = q[i].l,r = q[i].r;
		int num1 = query(1,1,n,l,r);
		if(op == 1)
		{
			if(num1>0)    change(1,1,n,l,l+num1-1,1);
			if(l+num1<=r) change(1,1,n,l+num1,r,0);
		}
		else
		{
			if(r-num1>=l) change(1,1,n,l,r-num1,0);
			if(num1>0)    change(1,1,n,r-num1+1,r,1);
		}
	}
}
int main()
{
	n = rd();m = rd();
	rep(i,1,n) a[i] = rd();
	rep(i,1,m) q[i].op = rd(),q[i].l = rd(),q[i].r = rd();
	int k = rd();
	int l = 0,r = n+1;
	while(l+1<r)
	{
		int mid = l+r>>1;
		rep(i,1,n) b[i] = a[i] > mid;
		work();
		if(query(1,1,n,k,k) == 1) l = mid;
		else r = mid;
	}
	printf("%d\n",r);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值