首页 > 其他分享 >2023暑假集训杂题

2023暑假集训杂题

时间:2023-07-15 21:24:02浏览次数:40  
标签:rt int sum back fib vct 2023 杂题 集训

2023暑假集训杂题解题报告

UOJ NOI Round #7 Day1 那些你不要的

题目链接

题目描述

给定长度为 \(n\) 的序列 \(A\),保证 \(n\) 为奇数,你是先手,每次先手与后手分别取相邻的 \(2\) 个数,并将剩下的数合并。先手希望最后剩下的数最大,后手希望剩下的数最小,在最优策略下,最后剩下的数是多少?

题目分析

发现操作是对于相邻的元素进行的,可以想到对序列进行黑白染色。一次操作相当于删除一个黑色元素与一个白色元素,故偶数位的元素最后一定会被删完,所以只需要考虑奇数位的元素。奇数位元素是相互独立的,故答案就是奇数位元素的中位数。

JOISC 2022 Day4 鱼2

题目链接

题目描述

定义对于长度为 \(n\) 的序列 \(A\) 的一次实验为:对于两只相邻的鱼 \(x\) 与 \(y\),若 \(A_x \le A_y\) 那么鱼 \(y\) 可以吃掉鱼 \(x\),若鱼 \(y\) 吃掉了鱼 \(x\),那么 \(A_y=A_y+A_x\)。经过 \(n-1\) 次这样的操作,最后会剩下一条鱼 \(E\)。定义一个实验的结果为 \(E\) 不同的取值数量,即最后可能活下来的鱼的种类数。现在你有一长度为 \(n\) 的序列 \(a\),需要支持一下两个操作:

  • 将某个数 \(a_x\) 改变为 \(y\)。

  • 询问区间 \(L\) 到 \(R\) 的子串的实验结果。

题目分析

考虑对长度为 \(n\) 的序列 \(A\) 进行实验。

若鱼 \(x\) 可以存活,其充要条件为:在任意时刻,鱼 \(x\) 可以吃掉它左右两边至少一边的鱼。

假设鱼 \(x\) 贪心的尽量将左右两边能吃的鱼全部吃掉后,能吃掉的区间为 \(L_x\) 到 \(R_x\)。考虑 \(L_x\) 的不同的取值个数。则有 \(\sum_{i=L_x}^{R_x} A_i < A_{L_x-1}\) 那么不同的 \(L_x\) 的取值只有 \(\log(|A|)\) 个。同理可得,不同的 \(R_x\) 的个数同样只有 \(\log(|A|)\) 个。

回到原问题,因为是区间查询,考虑使用线段树维护。因为我们只需要最后能存活的鱼的信息,故在线段树上,不需要维护 \(L_x\) 与 \(R_x\) 都在段内的鱼,所以我们维护左端点从 \(L_x\) 开始,当前右端点为整段右端点的鱼以及右端点从 \(R_x\) 开始,当前左端点为整段左端点的鱼。

考虑怎么合并区间的信息。不难发现,合并后的区间的 \(L_x\) 与 \(R_x\) 一定为子区间的子集。所以可以使用双指针进行合并。

时间复杂度 \(O(n \log^2(n))\) 。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define inf vector<node>
using namespace std;
template <typename T> inline void read(T &x)
{
	x=0;T f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-')f=-1;
	for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	x*=f;
}
template <typename T,typename ...Args>void read(T &x,Args&...args){read(x),read(args...);}
template <typename T> void print(T x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9) print(x/10);
	putchar(x%10+48);
}
template <typename T> void print(T x,char c){print(x); putchar(c);}
template<typename T>inline void output(T x){print(x,' ');}
template<typename T,typename ...Arg>inline void output(T x,Arg ...arg){output(x);output(arg...);}
const int N=100007;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,q,a[N];
struct node{ll sum,val;int num;};
struct segment_tree
{
	#define ls (rt<<1)
	#define rs (rt<<1|1)
	inf fl[N<<2],fr[N<<2];
	pair<inf,inf>merge(inf Ll,inf Lr,inf Rl,inf Rr)
	{
		ll suml=Ll.back().sum,sumr=Rl.back().sum,il=0,ir=0,tot=0;
		Ll.pop_back(); Rr.pop_back();
		vector<pair<ll,int>>vct;
		for(auto x:Rl) if(x.val>suml)
			Ll.push_back((node){x.sum+suml,x.val-suml,0});
		for(auto x:Lr) if(x.val>sumr)
			Rr.push_back((node){x.sum+sumr,x.val-sumr,0});
		for(;il<Lr.size();il++)
		{
			tot+=Lr[il].num;
			while(ir<Rl.size()&&Rl[ir].val<=Lr[il].sum) ir++;
			if(Lr[il].val>Rl[ir].sum)
			{
				if(ir==Rl.size()-1)
					vct.push_back({Lr[il].sum+sumr,tot});
				if(il==Lr.size()-1) for(auto &it:Ll)
					if(it.sum==Rl[ir].sum+suml) it.num+=tot;
				tot=0;
			}
		}
		for(int i=Rr.size()-1;i>=0;i--)
			if(vct.size()&&Rr[i].sum==vct.back().first)
				Rr[i].num+=vct.back().second,vct.pop_back();
		for(il=0,ir=0;ir<Rl.size();ir++)
		{
			tot+=Rl[ir].num;
			while(il<Lr.size()&&Lr[il].val<=Rl[ir].sum) il++;
			if(Rl[ir].val>Lr[il].sum)
			{
				if(il==Lr.size()-1) vct.push_back({Rl[ir].sum+suml,tot});
				if(ir==Rl.size()-1) for(auto &it:Rr)
					if(it.sum==Lr[il].sum+sumr) it.num+=tot;
				tot=0;
			}
		}
		for(int i=Ll.size()-1;i>=0;i--)
			if(vct.size()&&Ll[i].sum==vct.back().first)
				Ll[i].num+=vct.back().second,vct.pop_back();
		return make_pair(Ll,Rr);
	}
	pair<inf,inf>merge(pair<inf,inf>L,pair<inf,inf>R){return merge(L.first,L.second,R.first,R.second);}
	void pushup(int rt)
	{
		auto res=merge(fl[ls],fr[ls],fl[rs],fr[rs]);
		fl[rt]=res.first,fr[rt]=res.second;
	}
	void init(int rt,int x)
	{
		fl[rt].clear(); fr[rt].clear();
		fl[rt].push_back((node){0,x,0});
		fl[rt].push_back((node){x,INF,1});
		fr[rt].push_back((node){0,x,0});
		fr[rt].push_back((node){x,INF,1});
	}
	void build(int rt,int l,int r)
	{
		if(l==r) return init(rt,a[l]);
		int mid=(l+r)>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(rt);
	}
	void update(int rt,int l,int r,int p,int x)
	{
		if(l==r) return init(rt,x);
		int mid=(l+r)>>1;
		if(p<=mid) update(ls,l,mid,p,x);
		else update(rs,mid+1,r,p,x);
		pushup(rt);
	}
	pair<inf,inf>ask(int rt,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R) return make_pair(fl[rt],fr[rt]);
		int mid=(l+r)>>1;
		if(L<=mid&&R>mid) return merge(ask(ls,l,mid,L,R),ask(rs,mid+1,r,L,R));
		if(L<=mid) return ask(ls,l,mid,L,R);
		else return ask(rs,mid+1,r,L,R);
	}
	#undef ls
	#undef rs
}Tree;
int main()
{
	read(n);
	for(int i=1;i<=n;i++)
		read(a[i]);
	read(q); Tree.build(1,1,n);
	for(int i=1,opt,l,r;i<=q;i++)
	{
		read(opt,l,r);
		if(opt==1) Tree.update(1,1,n,l,r);
		else print(Tree.ask(1,1,n,l,r).first.back().num,'\n');
	}
	return 0;
}

CEOI 2018 斐波那契表示法

题目链接

题目描述

给定长度为 \(n\) 的序列 \(a\)。对于每个 \(i\) 询问 \(X=\sum_{j=1}^{i} fib(a_i)\) 表示为若干个不同的斐波那契数的和的方案数。其中 \(fib(i)\) 表示斐波那契数列的第 \(i\) 项。即 \(fib(1)=1\),\(fib(2)=2\),\(fib(i)=fib(i-1)+fib(i-2)\)。

题目分析

设数 \(X\) 的任意一种表示方法为 \(\sum fib(c_i)\) 其不同表示方法有两种产生方式:

  • 若存在 \(c_i=c_j+1\) 则可合并 \(c_i\) 与 \(c_j\) 形成 \(c_i+1\)。

  • 若存在 \(c_i > 2\) 则可以拆分 \(c_i\) 形成 \(c_i - 1\) 与 $c_i -2 $。

考虑先一直使用方法 \(1\) 合并 \(c\) 形成新的表示方法 \(X=\sum fib(d_i)\)。可以证明 \(d_i\) 互不相同。证明详见 Zeckendorf表达

考虑拆分 \(d_i\) 。发现有如下性质:

  • 若存在 \(d_i=d_j+1\) 那么 \(d_i\) 一定不能拆解。

证明:
若 \(d_i\) 拆解则有 \(1\) 个 \(d_i-2\) 与 \(2\) 个 \(d_i-1\) 。因为必须使用不同的数,所以至少需要拆解 \(1\) 个 \(d_i-1\)。拆解后即有 \(1\) 个 \(d_i-3\) 与 \(2\) 个 \(d_i-2\) 与 \(1\) 个 \(d_i-1\) 变为原问题,故可归纳证明,不可拆解 \(d_i\) 。

  • 若存在至少 \(3\) 个 \(d_i\) 无论经过多少次拆分,一定不合法。

证明:
若存在 \(3\) 个 \(d_i\) 则至少需要拆解 \(2\) 个,即拥有 \(2\) 个 \(d_i-1\) 与 \(2\) 个 \(d_i-2\),发现比上文更劣,故一定不合法。

根据性质 \(2\) 我们发现不论在什么中间状态,我们都最多只有 \(2\) 个 \(d_i\)。一个为原本 Zeckendorf表达 分解出的 \(d_i\) 一个为高位经过分解后退位下来的 \(d_i\)。

故我们可以设计出一个很简单的动态规划:

设 \(f[i][0/1]\) 表示考虑到 \(d_i\),有无退位的 \(d_i\) 的方案数。转移根据发现的性质 \(1\) 可以做到只与 \(d_i - d_{i-1}\) 有关且为 \(O(1)\)。

总时间复杂度 \(O(n^2)\) 。

考虑动态规划第二维状态为 \(0/1\) 且转移只与 \(d_i - d_{i-1}\) 有关。可以使用自己喜欢的数据结构维护矩阵乘法即可优化到 \(O(n \log n k ^ 3)\)。

标签:rt,int,sum,back,fib,vct,2023,杂题,集训
From: https://www.cnblogs.com/slenbol/p/17556792.html

相关文章

  • 2023-07-09~07-15第一周暑假生活
    本周平均学习时间应该是2小时每天,代码时间要短一点在1个小时的样子,解决问题总时长估计是三个小时学习内容和代码内容大部分是js的知识,也有在学习Linux的操作和搭载大数据环境。下周计划重心仍然是放在熟练掌握javaweb目标上——继续学习练习HTML、学习Springboot。下个月再把......
  • 太爆了!阿里最新出品2023版JDK源码学习指南,Github三天已万赞
    近后台收到很多粉丝私信,说的是程序员究竟要不要去读源码?当下行情,面试什么样的薪资/岗位才会被问到源码?对此,我的回答是:一定要去读,并且要提到日程上来!据不完全统计,现在市面上不管是初级,中级,还是高级岗,面试的时候都有可能会问到源码中的问题,它已经成为程序员常规必备的一个技术点。如......
  • 剑指offer_20230715
    剑指Offer67.把字符串转换成整数题目说明写一个函数StrToInt,实现把字符串转换成整数这个功能。不能使用atoi或者其他类似的库函数。首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。当我们寻找到的第一个非空字符为正或者负号时,则将该符......
  • 2023上半年阅读记录
    阅读半年了,也该有个小回顾了。半年前,因为焦虑,在罗翔和欧丽娟老师的视频下,慢慢觉得解决问题的方法思路可能还是得到书中去找,也算慢慢读到一些书,似乎解决了一些疑惑、迷茫。人生是自己的,所以跟着自己的心去寻找,寻找那种为之奋斗一辈子的目标,设计以心流框架,在具体实现中慢慢调整。另......
  • 2023.7.15
    学习java中的类面向对象与面向过程面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象:强调具备了功能的对象,以类/对象为最小单位类与对象的关系类:对一类事物的描述,是抽象的、概念上的定义对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)面向对象......
  • 2023.7.15
    早晨又下雨,被凉醒了进伏天以后咋还嗷嗷冷呢,还天天下雨,离谱东三省退出高温群聊下午去练车,练了两圈就可以撤了,今天是平平淡淡的一天,明天是最后一天练车啦后天就考科目二,再次祈祷一下希望我可以一把就过!科目二考完以后就可以有更多的时间啦!可以想想旅游和学习的事情!......
  • Python 潮流周刊第 11 期(2023-07-15)
    查看全文:Python潮流周刊#11:如何使用Golang运行Python代码?......
  • 2023年7月15日 天气:晴
       今天早上起来背了10个单词,然后出去打了两个小时的羽毛球,然后看了一小时的电视剧,再就是练了一个小时的字,然后学习了一个小时的java,最后看了一会儿构建之法,编程了一个小时的C语言。  明天打算早上起来看一小时的英语课本,然后出去玩一个小时,再看一小时的java课本,然后练......
  • UNR2023 退役记
    全真模拟.jpg由于全程校内所以没啥太多的有意思的。更新中......Day0按照惯例是要打UNR的。但是有一个很大的问题。UNR的时间安排和NOI是一致的。这也就意味着不得不牺牲一下午休时间了。另外,午饭也需要自行解决。目前的安排是教练统一安排泡面。然后征集口味。......
  • 2023ACM暑期集训 DAY 1
    目前进度——动态规划1:线性dp、背包问题,区间好题1003可爱の星空标签递推分治思路记\(dp_i\)表示将\(i\)颗点合并为一个连通块所需的最小代价。根据贪心思想:若目前的总点数\(n\)为偶数,则\(dp_n=2*dp_{\frac{n}{2}}\);若目前的总点数\(n\)为奇数,则\(dp_n=dp_{[\fr......