首页 > 其他分享 >纪念某模拟赛 8.6 k 代码

纪念某模拟赛 8.6 k 代码

时间:2024-09-04 09:24:49浏览次数:1  
标签:R1 8.6 int 纪念 ask2 Seg1 Seg2 L1 模拟

夏虫(summer)

题意简述:

用 \(n\) 个虫子,每个虫子有一个狡猾值 \(a_i\),一开始你会站在一个虫子 \(x\) 前,将初始能力值设为 \(a_x\),并捕捉它,接下来你可以重复执行三种操作,直到捕捉完所有昆虫:
设当前捕捉到了区间 \([l,r]\) 的昆虫,能力值为 \(v\)

  1. 若 \(l \ne 1\) 并且 \(a_{l−1} \le v\) ,那么可以花费 \(1\) 单位精力捕捉 \(l−1\) 号夏虫;
  2. 若 \(r \ne n\) 并且 \(a_{r+1} \le v\) ,那么她们可以花费 \(1\) 单位精力捕捉 \(r + 1\) 号夏虫;
  3. 花费 \(k\) (\(k\) 是定值)单位精力使得捕虫网的能力值为 \(min(a_{l−1}, a_{r+1})\)。在这里定义 \(a_0 = a_{n+1} = inf\)。

有 \(Q\) 次询问,每次给定 \(x,l,r\) 表示交换 \(x,x+1\) 两只昆虫的位置后,询问你初始站在 \([l,r]\) 内的每只昆虫前的最小花费精力的和。注意交换操作是永久的,即会对后面的询问产生影响。

题解:

首先操作 \(1\) 和操作 \(2\) 肯定加起来要做 \(n-1\) 次,所以只需要看操作 \(3\) 要几次。
容易用单调栈维护处 \(L[i]/R[i]\) 表示 \(a[i]\) 作为最大值的区间。
那么设 \(f[i]\) 表示以 \(i\) 为开始需要多少次操作 \(3\),不妨设 \(a[L[i]-1] < a[R[i]+1]\),则 \(f[i]=f[L[i]-1] + 1\)。
所以按照值域从大到小 \(dp\) 即可做到 \(O(n)\) 求解单次询问,那么时间复杂度 \(O(nQ)\),有 40 分。

但是如果按照这种转移的求法,不是很好在每次修改后维护 \(f\),考虑换一个求法:
设 \(A_x\) 表示 \(a[1],a[2],...,a[x]\) 的后缀最大值的集合。\(B_x\) 表示 \(a[x],a[x+1],...,a[n]\) 的前缀最大值的集合。
那么答案就是 \(|A_x \cup B_x| - 1\),-1 是因为 \(a[x]\) 自己是不用算的,这个式子应该还好理解,就不证了。

我们维护两棵线段树 \(Seg1,Seg2\)。\(Seg1\) 维护 \(a\) 序列,\(Seg2\) 维护 \(f\) 。
注意:我们是无法去维护 \(A,B\) 集合的,即不能对每个数开两个 \(multiset\),当然因为无法维护,我们也不需要真的在集合里删掉某些数(这是好的)

假设现在交换了 \(a[x],a[x+1]\),去考虑会影响哪些数的 \(A,B\) 集合。

  • \(a[x]<a[x+1]\)
  1. 对于 \(1 \le i \le x-1\) 的 \(i\),它们的 \(A\) 集合显然是不变的,但是 \(B\) 集合里有可能原来有 \(a[x]\),现在就没了(因为 \(a[x+1]\) 跑到 \(a[x]\) 前面了)。
    首先我们需要知道哪些数的 \(B\) 集合原来有 \(a[x]\),容易知道一定是 \([l,x-1]\) 这一段区间的数,其中每个数都 \(<a[x]\),注意如果 \(=a[x]\) 是不算的,因为 \(=a[x]\) 的话他的 \(B\) 集合里仍然有 \(a[x]\)。
    求这个 \(l\) 在 \(Seg1\) 上线段树二分即可,但是要知道 \(i \in [l,x-1]\) 的 \(f[i]\) 是否改变还要看 \(A[i]\) 里是否有 \(a[x]\),这里不用也不能去每个数判断一遍,注意到对于 \(A[i]\),他在 \(a[l],a[l+1],..,a[i]\) 的后缀\(max\)里肯定是没有 \(a[x]\) 的,所以只需要去看 \(a[l-1]\) 是否 \(=a[x]\) 即可。
    如果 \(a[l-1] \ne a[x]\)的话他们的 \(f[i]-1\),即在 \(Seg2\) 上区间 \(-1\)。
  2. 对于 \(i\ge x+2\),他们的 \(B\) 集合不变,\(A\) 集合要改变的是一段区间 \([x+2,r]\),他们都 \(<a[x]\),它们的 \(A\) 集合里会多出 \(a[x]\)。
    同理求 \(r\) 只需要线段树二分。判断 \(f\) 是否要 \(+1\) 只需要看 \(a[r+1]\) 是否 \(=a[x]\)。
  3. 对于 \(i=x,i=x+1\),注意到这个时候我已经求出了其他 \(i\) 的 \(f\) 值,所以直接按照最初的 \(f[i]=f[(L[i]-1) / (R[i]+1)] + 1\) 转移即可,\(L[i],R[i]\) 也可线段树二分求。注意要按照从大到小的顺序 DP
  • \(a[x]>a[x+1]\) ,类似分类讨论,不想写了。

用上面的写法是 \(O(n + Qlogn)\) 但是因为不知道怎么写这个线段树二分,所以这边想到了有两种做法:

  1. 来自大佬 @RiceFruit 的: 在 \([l,r]\) 中查询第一个满足 \(max(a[i],a[i+1],..,a[r]) \le val\) 的位置 \(i\),就先把 \([l,r]\) 拆成 \(log\) 个区间,然后先从后往前扫一遍判断在哪一个子树内,这样对于一整个子树的求解是好做的。
  2. 暴力二分用线段树查询的做法,所以是双 \(log\) 的。

一看就方法\(2\)好写,所以一开始写 \(O(Q \times log^2n)\),但是这样要跑 \(1.6\)s,当时看着自己TLE的 \(8k\) 代码人都炸了
所以还是写第一种吧。

代码有亿点点长,个人码风问题,但真的感觉写出来也是挺不容易的。
Swap函数那里码风突变是因为不加空格看着太恶心了。

code

#include<bits/stdc++.h>
#define PII pair<int,int>
#define fi first
#define se second 
#define int long long 
using namespace std;
const int N=1e5+5,inf=1e18;
inline int read(){
    int w = 1, s = 0;
    char c = getchar();
    for (; c < '0' || c > '9'; w *= (c == '-') ? -1 : 1, c = getchar());
    for (; c >= '0' && c <= '9'; s = 10 * s + (c - '0'), c = getchar());
    return s * w;
}
int n,k,T,a[N];
PII t[N];
int L[N],R[N],f[N];
stack<int> st;
struct node1{
	int l,r,maxn;
};
struct SegmentTree1{
	node1 t[N<<2];
	vector<int> v;  //存区间用 
	void pushup(int p){
		if(t[p<<1].maxn>t[p<<1|1].maxn) t[p].maxn=t[p<<1].maxn;
		else t[p].maxn=t[p<<1|1].maxn;
	}
	void build(int p,int l,int r){
		t[p].l=l,t[p].r=r;
		if(l==r){
			t[p].maxn=a[l];
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	int ask(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].maxn;
		}
		int mid=(t[p].l+t[p].r)>>1,maxn=0;
		if(l<=mid) maxn=max(maxn,ask(p<<1,l,r));
		if(r>mid) maxn=max(maxn,ask(p<<1|1,l,r));
		return maxn;
	}
	void Get_range(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			v.push_back(p);
			return;
		}
		int mid=(t[p].l+t[p].r)>>1;
		if(l<=mid) Get_range(p<<1,l,r);
		if(r>mid) Get_range(p<<1|1,l,r);
	}
	int lower1(int p,int val,int op){
		if(t[p].l==t[p].r){
			if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) return t[p].l;
			else return n+1; 
		}
		if( (op==0&&t[p<<1|1].maxn<val) || (op==1&&t[p<<1|1].maxn<=val) ) return min(lower1(p<<1,val,op) , t[p<<1|1].l);
		else return lower1(p<<1|1,val,op);
	}
	int lower2(int p,int val,int op){
		if(t[p].l==t[p].r){
			if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) return t[p].l;
			else return 0; 
		}
		if( (op==0&&t[p<<1].maxn<val) || (op==1&&t[p<<1].maxn<=val) ) return max(t[p<<1].r , lower2(p<<1|1,val,op));
		else return lower2(p<<1,val,op);
	}
	int ask1(int l,int r,int val,int op){  //在 l~r 中查询第一个满足 max(a[i],a[i+1],..,a[r]) </<=  val 的位置 
		if(l>r) return n+1;
		if( (op==0&&a[r]>=val) || (op==1&&a[r]>val) ) return n+1;  //先保证有解 
		v.clear();  //子树下标放这个里面,从左往右放 
		Get_range(1,l,r);
		int ans=r;
		for(int i=v.size()-1;i>=0;i--){
			int p=v[i];
			if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) ans=min(ans,t[p].l);  //这里只需要比当前子树的最大值就好了,扫过的那些已经满足了 
			else{
				ans=min(ans,lower1(p,val,op));
				break;
			}
		}
		return ans;
	}
	int ask2(int l,int r,int val,bool op){  //在 l~r 中查询最后一个满足 max(a[l],a[l+1],..,a[i]) </<=  val 的位置 
		if(l>r) return 0;
		if( (op==0&&a[l]>=val) || (op==1&&a[l]>val) ) return 0; 
		v.clear(); 
		Get_range(1,l,r);
		int ans=l;
		for(int i=0;i<v.size();i++){
			int p=v[i];
			if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) ans=max(ans,t[p].r); 
			else{
				ans=max(ans,lower2(p,val,op));
				break;
			}
		}
		return ans;	
	}
	void change(int p,int x,int val){
		if(t[p].l==t[p].r){
			t[p].maxn=val;
			return;
		}
		int mid=(t[p].l+t[p].r)>>1;
		if(x<=mid) change(p<<1,x,val);
		else change(p<<1|1,x,val);
		pushup(p);
	}
}Seg1;
struct node2{
	int l,r,sum,add;
	void tag(int d){
		sum+=(r-l+1ll)*d,add+=d;
	}
};
struct SegmentTree2{
	node2 t[N<<2];
	void pushup(int p){
		t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
	}
	void spread(int p){
		if(t[p].add){
			t[p<<1].tag(t[p].add);
			t[p<<1|1].tag(t[p].add);
			t[p].add=0;
		}
	}
	void build(int p,int l,int r){
		t[p].l=l,t[p].r=r;
		if(l==r){
			t[p].sum=f[l];
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	void change(int p,int x,int val){  
		if(t[p].l==t[p].r){
			t[p].sum=val;
			return;
		}
		spread(p);
		int mid=(t[p].l+t[p].r)>>1;
		if(x<=mid) change(p<<1,x,val);
		else change(p<<1|1,x,val);
		pushup(p);
	}
	void modify(int p,int l,int r,int d){  
		if(l<=t[p].l&&t[p].r<=r){
			t[p].tag(d);
			return;
		}
		spread(p);
		int mid=(t[p].l+t[p].r)>>1;
		if(l<=mid) modify(p<<1,l,r,d);
		if(r>mid) modify(p<<1|1,l,r,d);
		pushup(p);	
	}
	int ask(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].sum;
		}
		spread(p);
		int mid=(t[p].l+t[p].r)>>1,res=0;
		if(l<=mid) res+=ask(p<<1,l,r);
		if(r>mid) res+=ask(p<<1|1,l,r);
		return res;
	}
	int ask2(int p,int x){
		if(t[p].l==t[p].r) return t[p].sum;
		spread(p);
		int mid=(t[p].l+t[p].r)>>1;
		if(x<=mid) return ask2(p<<1,x);
		else return ask2(p<<1|1,x);
	}
}Seg2;
void Init(){
	st.push(0);
	for(int i=1;i<=n;i++){
		while(st.size()&&a[st.top()]<=a[i]) st.pop();
		L[i]=st.top()+1ll;
		st.push(i);
	}
	while(st.size()) st.pop();
	st.push(n+1);
	for(int i=n;i>=1;i--){
		while(st.size()&&a[st.top()]<=a[i]) st.pop();
		R[i]=st.top()-1ll;
		st.push(i);
	}	
	
	f[0]=f[n+1]=-1ll;
	sort(t+1,t+n+1,[](PII x,PII y){return x.fi>y.fi;});
	for(int i=1;i<=n;i++){
		if(a[L[t[i].se]-1] < a[R[t[i].se]+1]) f[t[i].se]=f[L[t[i].se]-1]+1ll;
		else f[t[i].se]=f[R[t[i].se]+1]+1ll;
	}
	
	Seg1.build(1,0,n+1);
	Seg2.build(1,0,n+1);     //虽然查不到 0/n+1 但是可能要用 
}
void Swap(int x){
	if(a[x]==a[x+1]) return;
	if(a[x]<a[x+1]){
		int l = Seg1.ask1(1 , x-1 , a[x] , 0);
		if(l<=x-1 && a[l-1] != a[x]) Seg2.modify(1 , l , x-1 , -1ll);
		
		int r=Seg1.ask2(x+2 , n , a[x] , 0);
		if(r>=x+2 && a[r+1] != a[x]) Seg2.modify(1 , x+2 , r , 1ll);
		
		Seg1.change(1 , x , a[x+1]) , Seg1.change(1 , x+1 , a[x]);  
		int maxn=a[x+1],ming=a[x]; 
		swap(a[x],a[x+1]);  
		//先更新大的再更新小的 
		int L1 = Seg1.ask1(1 , x , maxn , 1) , R1 = Seg1.ask2(x , n , maxn , 1) , F;  //注意这个时候 a[x+1] 在 x 的位置
		if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
		else F = Seg2.ask2(1 , R1+1) + 1ll;
		Seg2.change(1 , x , F);
		L1 = Seg1.ask1(1 , x+1 , ming , 1) , R1 = Seg1.ask2(x+1 , n , ming , 1) , F;  //注意这个时候 a[x] 在 x+1 的位置
		if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
		else F = Seg2.ask2(1,R1+1) + 1ll;
		Seg2.change(1 , x+1 , F);
		
	}
	else{  //a[x]>a[x+1]
		int l = Seg1.ask1(1 , x-1 , a[x+1] , 0);   //此时可能多出来一个 a[x+1] 
		if(l<=x-1 && a[l-1] != a[x+1]) Seg2.modify(1 , l , x-1 , 1ll);
		
		int r = Seg1.ask2(x+2 , n , a[x+1] , 0);
		if(r>=x+2 && a[r+1] != a[x+1]) Seg2.modify(1 , x+2 , r , -1ll);
		
		Seg1.change(1 , x ,a[x+1]) , Seg1.change(1 , x+1 , a[x]); 
		int maxn=a[x],ming=a[x+1];
		swap(a[x],a[x+1]);  		
		int L1 = Seg1.ask1(1 , x+1 , maxn , 1) , R1 = Seg1.ask2(x+1 , n , maxn , 1) , F;  
		if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
		else F = Seg2.ask2(1 , R1+1) + 1ll;
		Seg2.change(1 , x+1 , F);
		L1 = Seg1.ask1(1 , x , ming , 1) , R1 = Seg1.ask2(x , n , ming , 1) , F; 
		if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
		else F = Seg2.ask2(1 , R1+1) + 1ll;
		Seg2.change(1 , x , F);
		
	}
}
signed main(){
	freopen("summer.in","r",stdin);
	freopen("summer.out","w",stdout);
	n=read(),k=read();
	for(int i=1;i<=n;i++) a[i]=read(),t[i].fi=a[i],t[i].se=i; 
	a[0]=a[n+1]=inf;
	Init();
	T=read();
	while(T--){
		int x=read(),l=read(),r=read();
		Swap(x);
		printf("%lld\n",Seg2.ask(1,l,r)*k + (n-1)*(r-l+1));
	}
	return 0;
}

标签:R1,8.6,int,纪念,ask2,Seg1,Seg2,L1,模拟
From: https://www.cnblogs.com/FloatingLife/p/18395818

相关文章

  • 部分库函数及其模拟
     前言:当我们学习c/c++库函数的时候,我们可以用网站cplusplus.com-TheC++ResourcesNetwork来进行查阅,学习。 库函数:1.字符串函数字符串函数一般要用到头文件:<string.h> 1.1求字符串长度strlensize_tstrlen(constchar*str);1.首先,字符串是以\0来......
  • 8.30 上午 becoder 模拟赛总结 & 题解
    T1密码当时想到解法了,却依然认为自己不会做,我真是个人才。结论:对于$\foralli\in[1,n)$,满足密码不是$a_i$的因数,且密码是$a_k$的因数,设满足条件的最小值为$g$则答案为$\frac{n}{g}$。一种最好想的做法:枚举$\gcd(a_k,n)$的因数作为$g$,并枚举$i\in[1,n)$,判断是......
  • 8.31 上午 becoder 模拟赛总结 & 题解
    T1四个质数的和赛场亲测搜索+小剪枝可以得到70pts。考虑$O(p(V)^2)$枚举任意两个质数的和,其中$p(V)$表示$V$以内质数的个数。然后开个数组记录下对于每种和的记录有多少种情况,查询时for循环扫一遍即可,详见代码。复杂度去掉质数筛$O(p(V)^2+tn)$,代码贴在下面(100pts)......
  • 8.31 下午 梦熊联盟 NOIP 模拟赛总结 & 题解
    T1北极星一个比较好想到的点是从后往前枚举数,计算得出它需要的操作次数,然后给所有前面的数都加上这个操作次数,这样就把每个数独立出来了。所以这道题就变成了如何快速通过这些操作得到一个指定的数。观察大样例的输出,发现每一个数都是11?1?1?的形式,其中问号为+或c,我们可......
  • 9.1 上午 becoder 模拟赛总结 & 题解
    T1货车运输Kruskal重构树模板,没什么好说的,不会的把自己重构了算了,跳过。T2Slagalica可以发现拼图1和2、3拼起来还是拼图1,拼图4和2、3拼起来也还是拼图4,这两种拼图还都不能和自己拼,所以我们可以看作只有拼图1和拼图4来做。对于边界拼图分别是5、7的情况,只有......
  • 9.2 上午 becoder 模拟赛总结 & 题解
    T1加法最开始看了好久没想出来,先做T2去了,然后回来想了一会儿发现自己可能智商有点问题。看到求最小值最大,第一反应肯定是二分,那我们怎么应该怎么check呢?考虑顺次枚举序列$A$中的每一个数,然后如果这个数没有达到mid的要求,我们肯定是要添加区间的。那么我们怎么添加区......
  • 9.3 上午 becoder 模拟赛总结 & 题解
    T1能量获取简单的树形DP,设$dp_{i,j}$表示向$i$节点传递了$j$点能量并全部花费完后能激活的封印石的数量。显然有:$ans=\sum\max_{j=0}^{j\leqW_i}{dp_{i,j}}(i\inson_0)$,转移的初始状态为$dp_{i,E_i}=E_i$。设当前枚举到的节点为$x$,子节点为$y$,有经典树上背包转......
  • 调用azure的npm实现outlook_api模拟查看邮件、发送邮件(实现web版接受outlook邮件第一
    文章目录⭐前言⭐注册azure应用......
  • 初赛模拟题一
    初赛模拟题一T1(C++%)C++"%"的规则\[a\%b=c\]\[c=a-\lfloor\frac{a}{b}\rfloor*b\]\(\lfloor\rfloor\)的规则:向0取整,如\((-9)/5=-1\),\(9/5=1\)T3(GDB)GDB全称“GNUsymbolicdebugger”,从名称上不难看出,它诞生于GNU计划(同时诞生......
  • 一文读懂蒙特卡洛算法:从概率模拟到机器学习模型优化的全方位解析
    爱德华·蒙克(EdvardMunch)的"蒙特卡洛赌场的轮盘桌"(1892)蒙特卡洛方法的起源与发展1945年,在第二次世界大战即将结束之际,一场看似简单的纸牌游戏引发了计算领域的重大突破。这项突破最终导致了蒙特卡洛方法的诞生。参与曼哈顿计划的科学家斯坦尼斯劳·乌拉姆在康复期间深入思......