校内集训11.1小结


前言:这次做了一套提高难度的题,总分反而比上次高了。这次考试除了第二题没想出单调栈有些可惜外,也没有什么遗憾了。为了增强本人的考场思维能力,特地写一篇来总结一下。

T1:腿部挂件

题目描述
Jim是一个热爱打游戏的小伙子,可惜他的游戏水平不太行,以至于经常在游戏里被别人欺负。而且Jim不仅游戏玩的菜,他还很爱喷人,但是由于自己的垃圾操作,他又喷不过别人。为了改善这种局面,Jim决定成为一个腿部挂件(俗称抱大腿)。
已知现在有N个选手可供Jim选择,每位选手的能力值为 ai。,N位选手不一定每位选手都有时间陪Jim玩游戏而且Jim的状态也时好时坏,所以Jim有Q个询问,每个询问是3个数X、L、R,求第L个人到第R个人这R-L+1个人中与Jim配合后的能力值最大是多少,Jim 与第i位选手配合后的能力值为ai与X进行异或运算(Xor)。

输入
第1行:2个数N, Q中间用空格分隔,分别表示选手个数及查询的数量(1≤N≤200000,
1≤Q≤200000)。
第2 行:共N个数为每个选手能力值中间用空格分隔,对应ai(0≤a[i]≤10^9)。
第N+2 - N+Q+1行:每行3个数X, L, R,中间用空格分隔。(0≤X≤10^9,0≤L≤R<N)

输出
输出共Q行,对应每次询问所能得到的最大能力值。

样例输入

9 8
2 15 4 12 0 6 0 16 12
2 2 5
4 8 8
16 5 8
16 6 8
1 0 5
12 3 4
15 1 1
5 0 4

样例输出

14
8
28
28
14
12
0
10

提示
样例解释
对于第一个询问 2 与{4 12 0} 最大的能力值组合为 2 xor 12 = 14 注意 L R标号从 0 开始
对于20%的数据保证 (1≤N≤5000, 1≤Q≤5000)。
对于45%的数据保证 (1≤N≤50000, 1≤Q≤50000)。
对于100%的数据保证 (1≤N≤200000, 1≤Q≤200000)。

简要思路:这题需要用可持久化01Trie,将N个选手的能力值拆成二进制并将位数从高到低存在Trie中(有个不计数的根节点)。对于当前的X值,最大异或值就是根据X的二进制表示来尽可能地走反边,因为位数从高到低,所以在前面走反边肯定更优。
我们用cnt记录当前节点数来判定走反边是否可行,因为涉及区间操作,要用到前缀和,在预处理时要将上一棵树各节点的计数传到这一棵树上,代码实现如下:

inline void update( int & root , int dep , int num ) {
	int last = root;
	seg[root = ++scnt] = seg[last];//加上前面的结果
	++seg[root].cnt;
	if ( dep == -1 ) {
		return;
	}
	update( seg[root].son[( num >> dep ) & 1] , dep - 1 , num ); 
}

在main函数中:

for ( int i = 1 ; i <= n ; ++i ) {
		read(num);//快读
		root[i] = root[i - 1];//记录存贮每个选手能力值的树的根节点
		update( root[i] , maxd - 1 , num );
	}

如果觉得不好理解,那就画幅图吧,假设我们先后插入3,2,1,3(为了方便,深度设为3)。
图丑请见谅qwq
图片
根节点的计数意义不大,不用理会。本图中未标明的点就是计数为零的点。注意到,本图画出了在构建Trie树时我们根本没涉及到的点,在查询时如果访问到这些点,会自动进入零点,这对前缀和判断可行性没有影响,还节省了空间。
如果还有不懂的,那我只好上代码了。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 200005
#define maxd 30
using namespace std;
int n , q , scnt , root[N];
struct Trie{
	int cnt;
	int son[2];
}seg[N * (maxd + 1)];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline void update( int & root , int dep , int num ) {
	int last = root;
	seg[root = ++scnt] = seg[last];
	++seg[root].cnt;
	if ( dep == -1 ) {
		return;
	}
	update( seg[root].son[( num >> dep ) & 1] , dep - 1 , num ); 
}
inline int query( int ls , int rs , int dep , int num ) {
	if ( dep == -1 ) {
		return 0;
	}
	int now = ( num >> dep ) & 1;
	if ( seg[seg[rs].son[now ^ 1]].cnt - seg[seg[ls].son[now ^ 1]].cnt > 0 ) {
		return query( seg[ls].son[now ^ 1] , seg[rs].son[now ^ 1] , dep - 1 , num ) | ( 1 << dep );
	} else {
		return query( seg[ls].son[now] , seg[rs].son[now] , dep - 1 , num );
	}
}
int main () {
	//freopen("hugclose.in","r",stdin);
	//freopen("hugclose.out","w",stdout);
	read(n);
	read(q);
	int num;
	scnt = 0;
	for ( int i = 1 ; i <= n ; ++i ) {
		read(num);
		root[i] = root[i - 1];
		update( root[i] , maxd - 1 , num );
	}
	int l , r;
	while ( q-- ) {
		read(num);
		read(l);
		read(r);
		l++;
		r++;
		printf("%d\n",query( root[l - 1] , root[r] , maxd - 1 , num ));
	}
	return 0;
}

T2:走夜路

题目描述
Jim是一个胆小的男生,可是每天他都要学习到很晚才能回家,而且他回家的路上没有路灯。Jim非
常怕黑,万幸的是他还有一个手电筒可以用,我们设手电筒的电量上限为T。在Jim回家的路上有
(N+1)个充电站,0是起点N是终点,Jim每走一个单位距离消耗1个单位的电量.给出每个充电站到
下一个充电站的距离D,以及冲单位电量的花费P,求整个旅途的最少花费。如果Jim无法保证全程
手电筒都亮着输出-1.
对于30%的数据 N<=50
对于100%的数据 N<=500000

输入
第1行: 2个数N, T中间用空格分隔,N + 1为充电站的数量,T为手电筒的电池容量(2≤N≤500000,
1≤T≤10^9)。
第2至N + 1行:每行2个数D[i], P[i],中间用空格分隔,分别表示到下一个充电站的距离和充电的
单价(1≤D[i], P[i]≤1000000)。

输出
输出走完整个旅程的最小花费,如果无法保证手电筒全程照亮,输出-1

样例输入

3 15
10 2
9 1
8 3

样例输出

41

提示
样例解释
D = {10, 9, 8}, P = {2, 1, 3},T = 15,最小花费为41:在0冲10个单位的电,在1冲15个单位的电,
在2冲2个单位的电,刚好到家用完所有的电。
对于30%的数据 N<=50
对于100%的数据 N<=500000

简要思路:这题有一个显然的贪心,那就是如果在手电筒电量范围内能走到的最近的比当前电站电费少的电站,那么在当前电站充刚好可以到达那个电站的电量,到了那个电站再另作决定;否则,就在当前电站充满电,因为后面T范围内的电站都更昂贵,尽量少在那里充电。
我们设 n x t [ i ] nxt[i] nxt[i]为点 i i i在手电筒电量范围内能走到的最近的比当前电站电费少的电站,可以用单调栈去求,详见代码(这么简单,当时我却没想出来qwq )。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define N 500005 
using namespace std;
int d[N] , p[N] , q[N] , nxt[N];
ll len , now , cost , dis[N];
int n , t;
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline ll min( ll a , ll b ) {
	return a < b ? a : b;
}
int main () {
	//freopen( "wayhome.in" , "r" , stdin );
	//freopen( "wayhome.out" , "w" , stdout );
	read(n);
	read(t);
	len = cost = now = 0;
	for ( int i = 1 ; i <= n ; ++i ) {
		read(d[i]);
		read(p[i]);
		dis[i] = dis[i - 1] + 1ll * d[i];
	}
	int top = 0;
	for ( int i = n ; i >= 1 ; --i ) {
		while ( top && p[q[top]] > p[i] ) {
			top--;
		}
		nxt[i] = q[top];
		q[++top] = i;
		if ( !nxt[i] ) {
			nxt[i] = n + 1;
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		now -= d[i - 1];
		if ( now < 0 ) {
			puts("-1");
			return 0;
		}
		len = min( dis[nxt[i] - 1] - dis[i - 1] , t );
		if ( len > now ) {
			cost = cost + 1ll * ( len - now ) * p[i];
			now = len;
		}
	}
	printf("%lld",cost);
	return 0;
}

T3:宝石专家

题目描述
Jim是一位宝石收藏品行家,在他的收藏室里保存着许多珍贵的宝石,磷叶石、钻石、摩根石、透
绿柱石….,已知Jim有n个宝石,现在他将这n个宝石从1到n排开编号从1到n。Jim发现他所有的宝
石中竟然有不少是完全相同的的,我们规定每个宝石都有一个特征值ai,当两个宝石特征值相等时
及认为两个宝石相同。Jim发现两个相同的宝石离得越接近越明显。Jim现在有m个问题,他想问你
在.编号l到r这一区间里的所有宝石中,两个相同宝石的最近距离是多少,(两个宝石的距离是它们
编号的绝对值之差)。
保证 l < r ,对于 ax和ay 若ax=ay 它们的距离为|x-y|。

输入
单组测试数据。
第一行有两个整数n, m (1≤n,m≤2*10^5),表示宝石序列的长度和查询的次数。
第二行有n个整数a1,a2,…,an (-10^9 ≤ai≤10^9),ai表示第i个宝石的特征值。
接下来有m行,每一行给出两个整数lj,rj (1≤lj≤rj≤n)表示一个查询。
对于 10%的数据 保证 n<=20
对于 50%的数据 保证 n<=50000
对于 100%的数据 保证 n<=200000

输出
对于每一个查询,输出最近的距离,如果没有相等的元素,输出-1。

样例输入

5 3
1 1 2 3 2
1 5
2 4
3 5

样例输出

1
-1
2

提示
样例解释
第一个询问 第一个和第二个宝石最近且相同 距离为 1
第二个询问 第二个宝石到第四个宝石之间没有相同宝石 输出 -1
第三个询问 第三个宝石和第五个宝石最近且相同 距离为2
对于 10%的数据 保证 n<=20
对于 50%的数据 保证 n<=50000
对于 100%的数据 保证 n<=200000

简要思路:这题不用想复杂了。我们先将输入的宝石的值离散化,然后将每对同宝石中相邻的两个宝石用结构体保存,按左边坐标为第一关键字,右边坐标为第二关键字排序。然后对询问用同样的方法排序。
倒序处理询问,当当前询问左坐标小于当前点对的左坐标时,将当前点按“在位置为当前点对的右坐标的地方插入值(R-L)”插入线段树。处理询问时访问区间[0,R]即可。本题主要是看代码更好理解。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200005
#define ls root << 1
#define rs root << 1 | 1
using namespace std;
int n , m , mcnt , ncnt;
int num[N] , nxt[N] , last[N] , ans[N];
struct Query{
	int l , r;
	int id;
}q[N];
struct Node{
	int l , r;
}node[N];
struct A{
	int x , id;
}a[N];
struct ST{
	int minn;
	int l , r;
}tree[N << 2];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline bool cmpq( Query a , Query b ) {
	return a.l < b.l;
}
inline bool cmpn( Node a , Node b ) {
	return a.l < b.l;
}
inline bool cmpa( A a , A b ) {
	return a.x < b.x;
}
inline int min( int a , int b ) {
	return a < b ? a : b; 
}
inline void build( int root , int l , int r ) {
	tree[root].l = l;
	tree[root].r = r;
	tree[root].minn = 0x3f3f3f3f;
	if ( l == r ) {
		return;
	}
	int mid = ( l + r ) >> 1;
	build( ls , l  , mid );
	build( rs , mid + 1 , r );
	return;
}
inline void modify( int root , int pos , int val ) {
	if ( tree[root].l == tree[root].r ) {
		tree[root].minn = val;
		return;
	}
	int mid = ( tree[root].l + tree[root].r ) >> 1;
	if ( pos <= mid ) {
		modify( ls , pos , val );
	} else {
		modify( rs , pos , val );
	}
	tree[root].minn = min( tree[ls].minn , tree[rs].minn );
	return;
}
inline int query( int root , int l , int r ) {
	if ( l <= tree[root].l && tree[root].r <= r ) {
		return tree[root].minn;
	}
	int ans = 0x3f3f3f3f;
	int mid = ( tree[root].l + tree[root].r ) >> 1;
	if ( l <= mid ) {
		ans = min( ans , query( ls , l , r ) );
	}
	if ( r >= mid + 1 ) {
		ans = min( ans , query( rs , l , r ) );
	}
	return ans;
}
int main () {
	freopen( "jewel.in" , "r" , stdin );
	freopen( "jewel.out" , "w" , stdout );
	read(n);
	read(m);
	mcnt = ncnt = 0;
	//↓是离散化
	for ( int i = 1 ; i <= n ; ++i ) {
		read(a[i].x);
		a[i].id = i;
	}
	sort( a + 1 , a + 1 + n , cmpa );
	num[a[1].id] = ++mcnt;
	for ( int i = 2 ; i <= n ; ++i ) {
		if ( a[i].x != a[i - 1].x ) {
			num[a[i].id] = ++mcnt;
		} else {
			num[a[i].id] = mcnt;
		}
	}
	//↑是离散化
	for ( int i = 1 ; i <= n ; ++i ) {
		if ( !last[num[i]] ) {
			last[num[i]] = i;
		} else {
			node[++mcnt].l = last[num[i]];
			node[mcnt].r = i;
			last[num[i]] = i;
		}
	}
	for ( int i = 1 ; i <= m ; ++i ) {
		read(q[i].l);
		read(q[i].r);
		q[i].id = i;
	}
	build( 1 , 1 , n );
	sort( node + 1 , node + 1 + mcnt , cmpn );
	sort( q + 1 , q + 1 + m , cmpq );
	int pn = mcnt;
	for ( int i = m ; i >= 1 ; --i ) {
		while ( pn >= 1 && node[pn].l >= q[i].l ) {
			modify( 1 , node[pn].r , node[pn].r - node[pn].l );
			pn--;
		}
		ans[q[i].id] = query( 1 , 1 , q[i].r );
		if ( ans[q[i].id] == 0x3f3f3f3f ) {
			ans[q[i].id] = -1;
		}
	}
	for ( int i = 1 ; i <= m ; ++i ) {
		printf("%d\n",ans[i]);
	}
	return 0;
}

就总结到这了~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值