Codeforces 选做

CF878E Numbers on the blackboard

好题。看到这个东西考虑打表一下最后每个点系数,需要一定的眼力才能看出相当于一个除了只有第一个是 1,后面的点每次最多比前面一个乘二的序列。知道结论证明就可以简单归纳了。

然后考虑答案的形态,发现如果我们称每次乘二的一段为连续段,那么对于最后一个连续段的开头,我们可以发现它要么直接取 \(2\) 要么还不如跟上一个连续段接起来,所以答案一定是若干个从一开始的连续段。

于是我们可以扫描右端点拿单调栈维护这个东西,区间询问就离线一下用并查集搞,复杂度 \(O(n\alpha(n))\)

#include <cstdio>
#include <vector>
using namespace std;
int read(){/*...*/}
const int N=100003,P=1000000007,Lim=1000000010;
typedef long long ll;
int n,q,a[N],f[N];
int pos[N],res[N],id[N];
int s[N],bn[N],sb[N];
vector<int> vec[N];
int calc(int l,int r){
	int tmp=(s[l]-(ll)s[r]*bn[r-l])%P;
	if(tmp<0) return tmp+P;
	return tmp;
}
int rt(int x){
	if(f[x]==x) return x;
	return f[x]=rt(f[x]);
}
int stk[N],val[N],vv[N],sm[N],tp;
int main(){
	n=read();q=read();sb[0]=bn[0]=1;
	for(int i=1;i<=n;++i){
		a[i]=read();
		sb[i]=min(sb[i-1]<<1,Lim);
		bn[i]=bn[i-1]<<1;
		if(bn[i]>=P) bn[i]-=P;
	}
	for(int i=1;i<=n+1;++i) f[i]=i;
	for(int i=1;i<=q;++i){
		pos[i]=read();
		res[i]=a[pos[i]++];
		if(res[i]<0) res[i]+=P;
		vec[read()].emplace_back(i);
	}
	for(int i=n;i;--i){
		s[i]=s[i+1]<<1;
		if(s[i]>=P) s[i]-=P;
		s[i]+=a[i];
		if(s[i]<0) s[i]+=P;
		if(s[i]>=P) s[i]-=P;
	}
	for(int i=1;i<=n;++i){
		int cur=a[i],cc=a[i],las=i;
		if(cc<0) cc+=P;
		while(tp&&cur>=0){
			int x=stk[tp];
			f[las]=las+1;
			cur=min(val[tp]+(ll)cur*sb[las-x],(ll)Lim);
			cc=(vv[tp]+(ll)cc*bn[las-x])%P;
			las=x;--tp;
		}
		stk[id[las]=++tp]=las;
		val[tp]=cur;vv[tp]=cc;
		sm[tp]=sm[tp-1]+vv[tp];
		if(sm[tp]>=P) sm[tp]-=P;
		id[i+1]=tp+1;
		for(int x:vec[i]){
			int pp=rt(pos[x]);
			int tmp=calc(pos[x],pp);
			tmp+=sm[tp];
			if(tmp>=P) tmp-=P;
			tmp-=sm[id[pp]-1];
			if(tmp<0) tmp+=P;
			tmp<<=1;
			if(tmp>=P) tmp-=P;
			res[x]+=tmp;
			if(res[x]>=P) res[x]-=P;
		}
	}
	for(int i=1;i<=q;++i) printf("%d\n",res[i]);
	return 0;
}

CF1879F Last Man Standing

Easy Problem?首先很容易得到一个做法,直接爆枚 \(x\),相当于每个数乘上 \(\lceil\frac{a_i}{x}\rceil\) 然后求最大次大,直接调和级数枚举+ST表可以通过此题。

但我是从 Enucai 博客那看到这道题的,所以还有一个更好的做法,考虑类似某道联考放了两遍的经典题的技巧放缩一下查询区间变成查后缀可以省下 ST 表。

#include <cstdio>
#include <algorithm>
using namespace std;
int read(){/*...*/}
const int N=200003,T=200000;
typedef long long ll;
int n,h[N];
void chmx(ll &x,ll v){if(x<v) x=v;}
struct Info{
	ll mx;int id;ll smx;
	Info(ll M=0,int I=0,ll S=0):mx(M),id(I),smx(S){}
	void operator+=(const Info x){
		if(id==x.id) return chmx(mx,x.mx),chmx(smx,x.smx);
		if(x.mx>mx){chmx(smx=mx,x.smx);mx=x.mx;id=x.id;}
		else chmx(smx,x.mx);
	}
	Info operator*(const int x){return Info(mx*x,id,smx*x);}
}suf[N];
ll res[N];
int main(){
	int tc=read();
	while(tc--){
		n=read();
		for(int i=1;i<=n;++i) h[i]=read(),res[i]=0;
		for(int i=1;i<=T+1;++i) suf[i]=Info();
		for(int i=1;i<=n;++i) suf[read()]+=Info(h[i],i,0);
		for(int i=T;i;--i) suf[i]+=suf[i+1];
		for(int x=1;x<=T;++x){
			Info cur;
			for(int p=1,t=1;p<=T;p+=x,++t) cur+=(suf[p]*t);
			chmx(res[cur.id],cur.mx-cur.smx);
		}
		for(int i=1;i<=n;++i) printf("%lld ",res[i]);
		putchar('\n');
	}
	return 0;
}

CF887F Row of Models

这种题怎么只有 2500*……看着难度做自闭了。

这是一道非常好的贪心题,评分低可能只是场上被各种数据结构爆过了。

先从做数据结构题的角度入手:

称满足 \(p_i-i>k\) 的元素为坏元素,考虑你交换的那个 \(x,y\) 会怎么影响序列的好坏。

对于在 \(x\) 左边的所有数,由于小的数更靠近了所以会有一些数从坏变好,对于 \(x,y\) 中间的所有元素的状态只有可能变坏,因为一个小的数被换出去了。

\(x,y\) 本身的好坏变化似乎不太确定。首先我们发现 \(x\) 一定原来是好的,否则 \(y\) 一换到这不可能变好。可以考虑枚举一下 \(y\) 的好坏性,由于我们想将所有元素变好,所以除了 \(y\) 以外所有坏数都在 \(x\) 左边。

然后考虑一下什么样的 \(x,y\) 可以将所有 \(x\) 左边的数变好,这需要满足 \(a_y<\min_{i\text{ is bad}\land i<y} a_i,x\leq k+\min_{i\text{ is bad}} i\)

同时还要防止中间的数变坏,所以要对所有 \(p_i=y\) 的数求一个次大值一类的东西,剩下关于 \(x,y\) 的限制都是一些关于位置与值域的偏序限制,于是就可以用线段树二分疯狂叠不等式。一开始做的时候写了好久这个硬维护做法,后来发现似乎很不可写……

接下来考虑更细致地分析性质:

上面那个数据结构做法很多地方憨了,但是许多分析都是有一定启发性意义的。

比如说枚举 \(y\) 的好坏性这一步就很憨,事实上我们最后可以发现 \(x,y\) 都是好元素一定更优。为此我们要找到一个比较好的切入点。

切入点就是 \(a_y<\min_{i\text{ is bad}\land i<y}\) 这个偏序限制。我们可以发现最小的坏元素具有一些美妙的性质。考虑这样一个过程,从小到大加入 \(a_i\) 直到加入第一个坏元素,发现在这个过程种最小值加入时想要变好只能是其坐标 \(\geq n-k+1\),继续分析下去,剩下的元素加入时,它能变好的位置一定是一段后缀!第一个不在这个后缀里的元素便是最小的坏元素。

此时我们就可以发现 \(y\) 不可能坏了,因为 \(a_y\) 比在它前面的最小的坏元素还小,说明它就是最小的坏元素,能够让它变好的元素都在它后面,现在它还往左边挪,怎么可能变好?

我们称比最小的坏元素还小的集合为 \(\text{Min}\) 集合。

我们顺水推舟就可以完成最后的分析了。剩下的 \(x,y\) 只需要满足的限制是:能让 \(x\) 左边的元素变好,能让中间的元素不变坏,\(y\) 挪到前面去依然好。

对于第一个限制,我们可以贪心取 \(x\)\(k+\min_{i\text{ is bad}} i\)。容易发现根据我们接下来的一大堆分析,\(a_x\) 的值多少已经不重要了。

对于第三个限制,我们贪心取 \(y\)\(\text{Min}\) 中值最大的任意一个元素。这样更容易在后面找到 \(p_i\)

此时我们发现如果一、三限制同时满足,第二个限制一定满足。首先对于在中间的 \(\text{Min}\) 集合元素,由于拿走的是 \(\text{Min}\) 集合的最大值,一定不影响。对于不是 \(\text{Min}\) 集合里的如果它的坐标比我们挖去了最大值后的 \(\text{Min}\) 集合中最小的元素减 \(k\) 还大肯定能找剩下的 \(\text{Min}\) 集合元素,否则它如果找不到了,那么我们挪到前面去的 \(a_y\) 比它前还比它小,而到它之前还没有任何的 \(\text{Min}\) 集合的元素,只能到它的后面去找,它不是好的那么 \(y\) 也不会依然好。

综上结论证完了!其实求出 \(x,y\) 之后不用再求一遍 \(p_i\),根据上面的讨论我们实际上只用判一下交换后第三个限制是否成立就行了。

// wssb
// You can greedy instead of ds!
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int INF=0x3f3f3f3f;
int read(){
	char c=getchar();int x=0;
	while(c<48||c>57) c=getchar();
	do x=(x<<1)+(x<<3)+(c^48),c=getchar();
	while(c>=48&&c<=57);
	return x;
}
const int N=500003;
int n,k;
int a[N],p[N];
int stk[N],tp;
int main(){
	n=read();k=read();
	for(int i=1;i<=n;++i) a[i]=read();
	stk[0]=n+1;
	for(int i=n;i;--i){
		while(tp&&a[stk[tp]]>=a[i]) --tp;
		p[i]=stk[tp];
		stk[++tp]=i;
	}
	int cur=0x3f3f3f3f,mnpos=0,mxpos=0;
	for(int i=1;i<=n;++i)
		if(p[i]-i>k){
			if(!mnpos) mnpos=i;
			mxpos=i;
			cur=min(cur,a[i]);
		}
	if(cur==0x3f3f3f3f){puts("YES");return 0;}
	int pos=mnpos+k;
	if(pos<=mxpos){puts("NO");return 0;}
	int mx=-0x3f3f3f3f;
	for(int i=n;i;--i)
		if(a[i]<cur&&a[i]>mx) mx=a[i];
	if(mx<0){puts("NO");return 0;}
	if(pos+k>n){puts("YES");return 0;}
	for(int i=pos+1;i<=pos+k;++i)
		if(a[i]<mx){puts("YES");return 0;}
	puts("NO");
	return 0;
}
posted @ 2023-10-20 14:47  yyyyxh  阅读(53)  评论(0编辑  收藏  举报