Loading

考试题

此为第三次出题的题解,by \(dfgz\), \(Zelensky\)

我们抽的知识点为 :贪心,分治,模拟,乱搞,杂项

A:

算法:贪心(贪心地选两个数,然后尝试去证它),分治(众所周知,线段树是一个类似于分治的结构)

\(ps\) : 自己花十分钟诌的,感觉像是 \(little\) \(red\) 老师喜欢的题。

推式子:

\[\begin{align*} &2a^3 + 4a^2b + 2ab^2\\ =&a^3+b^3+3a^2b+3ab^2+a^3-b^3+a^2b-ab^2\\ =&(a+b)^3+(a-b)(a^2+ab+b^2)+ab(a-b)\\ =&(a+b)^3+(a-b)(a+b)^2\\ =&2a(a+b)^2 \end{align*} \]

然后分讨就行,只涉及到了区间最大值,次大值,最小值,(这不 \({\color{green}绿题}\)

然后对于上面有四种情况,设最大值,次大值,最小值分别为 \(mx\),\(smx\),\(mi\)

  1. \(mx<0\)

\(a=mx,b=smx\)

  1. \(mx=0\)

易证最大值最大为 \(0\)

\(a=mx(0),b=smx\)

  1. \(mx>0\)

这个需要分讨;

\(①\). \(|mx+smx| \ge |mx+mi|\) : \(a=mx,b=smx\)

\(②\). \(|mx+smx|<|mx+mi|\)\(a=mx,b=mi\)

然后还有一个东西需要证,就是选择 \(a=smx(>0),b=mi\) 的时候,是否成立.

点击查看无敌大证 设 $b=mx,c=smx,a=mi(a<0)$

我们需要证 \(2b(a+b)^2>2c(a+c)^2\)

因为\(a<0\)所以我们可以把式子简单调整一下,证明:\(2B(A-B)^2>2C(A-C)^2\) \((A,B,C \in \mathbb N^* ,A>2B+C(对于②来说))\)

\(P=A-B,D=B-C\) 带入原式:

\[(C+D)P^2>C(P+D)^2 \]

展开得到:\(DP^2>2CPD+CD^2\)

然后同除 \(D(D>0)\) 得到:\(P^2>2CP+CD\)

移项得到:\(P^2-2CP-CD>0\)

设函数 \(f(x)=x^2-2Cx-CD\)

根据初中知识可得当 \(x=C\) 时,\(f(x)\) 取到最小值,但是 \(P \ge B+C+1\)

所以在定义域为 $x \in [B+C+1,+\infty] \cap \mathbb N $ 时,\(f(x)\) 单调递增。

\(x=B+C+1\) 时取到最小值:

\[\begin{align*} f(B+C+1)&=B^2-BC+2B+1\\ &=B(B-C)+2B+1>0 \end{align*} \]

得证!!!!!!!!!!!!!!



点击查看代码

#include<bits/stdc++.h>
const int N = 5e6+10, inf = 1e9;
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
	return x*f;
}
inline void write(__int128 x){
    if(x<0){x=-x;putchar('-');}
    if(x>9)write(x/10);
	putchar(x%10+'0');
}
struct info {
	int mx,mn,smx;
	inline info operator +(const info &f)const{
		info g;g.mx=max(mx,f.mx),g.mn=min(mn,f.mn);
		g.smx=max({min(mx,f.mx),f.smx,smx});
		return g;
	}
};
int n,q,a[N];
struct Seg{
	#define ls i<<1
	#define rs i<<1|1
	info T[(int)8e6];
	void build(int i,int l,int r){
		if(l==r)return T[i]={a[l],a[l],-inf},void();int mid=(l+r)>>1;
		build(ls,l,mid),build(rs,mid+1,r);T[i]=T[ls]+T[rs];
	}
	void add(int i,int l,int r,int x,int k){
		if(l==r)return T[i]={k,k,-inf},void();int mid=(l+r)>>1;
		x<=mid?add(ls,l,mid,x,k):add(rs,mid+1,r,x,k);
		T[i]=T[ls]+T[rs];
	}
	info query(int i,int l,int r,int x,int y){
		if(x<=l&&y>=r)return T[i];int mid=(l+r)>>1;
		if(y<=mid)return query(ls,l,mid,x,y);
		if(x>mid)return query(rs,mid+1,r,x,y);
		return query(ls,l,mid,x,y)+query(rs,mid+1,r,x,y);
	}
}T1;
signed main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	// ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read(),q=read();
	for(int i=1;i<=n;i++)a[i]=read();
	T1.build(1,1,n);
	for(int i=1,opt,l,r;i<=q;i++){
		opt=read(),l=read(),r=read();
		if(opt==1)T1.add(1,1,n,l,r);
		else {
			info ans1=T1.query(1,1,n,l,r);
			int a=ans1.mx;
			if(a<0){
				__int128 b=ans1.smx;
				write(2*a*(a+b)*(a+b));
				putchar('\n');
			}else if(a==0)cout<<0<<'\n';
			else {
				__int128 b;
				if(abs(a+ans1.mn)<=abs(a+ans1.smx))b=ans1.smx;
				else b=ans1.mn;
				write(2*a*(a+b)*(a+b));
				putchar('\n');
			}
		}
	}return 0;
}

B:AT_agc002_d

算法:整体二分\(Kruskal\) 重构树,二分

不是哥们,你是说在考试前一天的模拟赛上,把我这题的所有算法爆了???(\(update\) \(on\) \(2025年11月7日20:25\)

原题其实是不强在的,但是这题的整体二分太典了,所以我就加了个强在,把整体二分变成了部分分。

先看不强在的:

看到最大值最小一眼二分,多组询问考虑整体二分,可持久化按秩合并并查集维护连通性,顺带维护集合大小。

然后就是整体二分板子,二分答案上界,把编号在整体二分的 \([l,mid]\) 的边连上,可持久化是为了维护可撤销的。

然后是强在的:

应该都记得有 \(Kruskal\) 重构树这么个东西吧,这个有一个很好的性质:在 \(Kruskal\) 重构树上的一条链除了叶子节点的权值是单调的,这个性质就可以让我们直接在重构树上倍增了。

实现就是先把重构树建出来(这题边的权重是顺次的直接按照他给的顺序建就行了),依旧是并查集维护联通性,然后一遍 \(DFS\) 处理倍增数组就行了。

点击查看代码

bool M1;
#include<bits/stdc++.h>
// #define int long long
const int N = 2e6+10, mod = 998244353;
using namespace std;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') f=ch=='-'?-1:1,ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x*f;
}
int f[21][N],fa[N];
int n,m,Q,ty,siz[N],dis[N],cnt; vector<int>e[N];
int find(int u) {return fa[u]==u?u:fa[u]=find(fa[u]);}
void add(int u,int v) {e[u].push_back(v);}
void dfs(int u,int la) {
	f[0][u]=la;
	for(int i=1;i<=19;i++)
		f[i][u]=f[i-1][f[i-1][u]];
	if(e[u].size()==0) return siz[u]=1,void();
	siz[u]=0;for(auto v:e[u]) dfs(v,u),siz[u]+=siz[v];
}
int check(int x,int y,int k) {
    for(int i=19;i>=0;i--) {
		if(dis[f[i][x]]<=k) x=f[i][x];
		if(dis[f[i][y]]<=k) y=f[i][y];
	} return (x^y)?siz[x]+siz[y]:siz[x];
}
bool M2;
signed main(){
	cerr<<(&M2-&M1)/(1024*1024)<<'M'<<'B'<<endl;
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	cnt=n=read();m=read();ty=read();
    for(int i=1;i<=(n<<1);i++) fa[i]=i,dis[i]=0;
    dis[0]=mod;
    for(int i=1;i<=m;i++) {
		int u=read(),v=read();
		int fu=find(u),fv=find(v);
		if(fu^fv) {
			dis[++cnt]=i;
			fa[fu]=fa[fv]=cnt;
			add(cnt,fu);add(cnt,fv);
		}
	} dfs(cnt,0); Q=read();int lans=0;
	while(Q--) {
		int x=read()^(lans*ty),y=read()^(lans*ty),z=read()^(lans*ty);
		int l=0,r=m;lans=m;
		while(l<=r) {
			int mid=(l+r)>>1;
            if(check(x,y,mid)>=z) r=mid-1,lans=mid;
            else l=mid+1;
		} printf("%d\n",lans);
	} return 0;
}



C:P12399 少年游

算法:交互题,乱搞/随机化(题解区有随机化解法,但是我没学)

对于 $ \sum_{i=1}^{n} \left | a_i \right | = \left | \sum_{i=1}^{n} a_i \right | $ 的情况是简单的,虽然原题没有这档分。

\(A.\) 上面这种情况,两次判状态,非负的时候 \(n-1\) 次得答案,这种情况是简单的,所以我们考虑把原序列变成非负的来处理。

有一个很显然的结论:在原序列上,我们如果翻转一个数,这个序列的最大子段和变大了,则证明这个数为负数,且这个数在这个序列的最大子段和的边界上,因为最大子段和一定是一段区间,然后这个区间的两端要么是边界,要么是两个负数,翻转其中一个负数一定会使最大子段和变大。

\(B.\) 这非常显然,找到这个数也是简单的,设 \(s\) 为最大子段和,我们从左往右每个数每个数地扫这个序列,\(s\) 变大直接跳出,否则再把这个数翻回去,这样暴力扫的级别是 \(2 \times n\) 的。

考虑优化,花费只与次数有关,每次翻一个数还是太浪费了,所以我们每次翻当前这个数的时候顺带把上个数翻回去,这样操作次数就减半了。

\(C.\) 我们从这个数的两边开始每次翻一个数,\(s\) 变大证明这个数原来是负的,不需要翻回去,变小则证明这个数原来是正的,需要把它翻回去,如果不变就无所谓了,说明这个数是 \(0\),翻不翻都行,

这个的优化,也和上面一样,可以降成 \(n\) 级别的。

然后这个序列就全部非负了,可以沿用第一种情况,这样就解决了。

我们细算一下操作次数: 初始 \(2\) 次判断正负,\(A\) 用了 \(n-1\) 次,\(B\) 用了 \(n\) 次,\(C\) 用了 \(n+1\) 次(边界需要额外翻回去)总共用了 \(3n+2\) 次,但是 \(B\)\(C\) 中的一步冲突了,所以卡不满。

点击查看代码

#include "Zelensky.h"
#include<bits/stdc++.h>
const int N = 1e3+10;
using namespace std;
vector<int>ans(1001);
int a[N],ty[N];
void get_ans(int res,int n) {
    for(int i=1;i<n;i++) {
        int nw=update(i,i);
        ans[i]=(res-nw)*ty[i];res=nw;
    } ans[n]=res*ty[n];
}
void upd(int l,int r) {for(int i=l;i<=r;i++) ty[i]*=-1;}
vector<int> solve(int n) {
    for(int i=1;i<=n;i++) ty[i]=1;
    int res1=update(1,n),res2=update(1,n);
    if(res1==0) {
        get_ans(res2,n);
        return ans;
    } else if(res2==0) {
        update(1,n);upd(1,n);
        get_ans(res1,n);
        return ans;
    } 
	int nw=res2,pos;
    for(int i=1,tmp;i<=n;i++) {
        if(i==1) tmp=update(1,1),upd(1,1);
        else tmp=update(i-1,i),upd(i-1,i);
        if(tmp>nw) {pos=i;nw=tmp;break;}
    } int opt=0;
    for(int i=pos-1,tmp;i>=1;i--) {
        tmp=update(i,i+opt),upd(i,i+opt);
        if(tmp>=nw) nw=tmp,opt=0;
        else opt=1;
    } if(opt) nw=update(1,1),upd(1,1);
    opt=0;
    for(int i=pos+1,tmp;i<=n;i++) {
        tmp=update(i+opt,i),upd(i+opt,i);
        if(tmp>=nw) nw=tmp,opt=0;
        else opt=-1;
    } if(opt==-1) nw=update(n,n),upd(n,n);
	get_ans(nw,n);
    return ans; 
}


D:火灾/Fire

by \(Zelensky\)

不是个们去原题看呗ლ(′◉❥◉`ლ)

算法:离线处理,线段树

考虑每个数对答案的贡献。

如果把每个数向前面第一个比他大的数连边,那么构成一个树形结构

可以发现对于一个 \(x\) 一直到下一个大于等于他的数之前的区间都会被他所影响,即都是他的子树,那么也就是说每个结点的子树是一个连续区间

因为是连续区间,所以他们被改变的时间也是连续的,我们考虑维护随时间变化的增量

在这个树形结构中他的父亲比他的父亲的父亲先影响到他,所以我们维护儿子与父亲的增量即可

其实上面这些废话只是告诉我们可以对每个位置得到一个四元组 \((t0​,l,r,d)\)

表示在 \([l,r]\) 区间, 从 \(t0​\) 时刻开始, 按照从左到右的位置顺序每个时刻都会在对应位置多 \(d\) 的贡献

三元组 \((t0​,l,d)\) 表示 \(l\) 位置从 \(t0\)​ 时刻开始每时刻向后面有 \(d\) 的增量

考虑对一个前缀 \(p\) 进行 \(t\) 时刻的询问, 他的贡献可以写成 \((min(p,l+t−t0​)−min(p,l−1))×d\)

这样对于 \(p<l\) 我们也不需要特殊处理,因为一减就变成 \(0\)

也就是 \((t+min(p−t,l−t0​)−min(p,l−1))×d\)

那么我们开四棵线段树分别维护

  1. \(l\) 位置维护 \(l×d\)

  2. \(l−t0\)​ 位置维护 \((l−t0​)×d\)

  3. \(l\) 位置维护 \(d\)

  4. \(l−t0\)​ 位置维护 \(d\)

询问对 \(min\) 取的是哪个值分开讨论即可

点击查看代码


#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define getchar() (S==_&&(_=(S=fsr)+fread(fsr,1,1<<15,stdin),S==_)?EOF:*S++)
char fsr[1<<20],*S=fsr,*_=fsr;
inline int read( ){
	int x = 0 ; short w = 0 ; char ch = 0;
	while( !isdigit(ch) ) { w|=ch=='-';ch=getchar();}
	while( isdigit(ch) ) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return w ? -x : x;
}
struct qry{int pos,t,id,opt;}Q[(int)5e5];
struct Add{int l,t,d;}C[(int)5e5];
bool cmpq(qry x,qry y){return x.t<y.t;}
bool cmpc(Add x,Add y){return x.t<y.t;}
struct Seg{
	#define ls i<<1
	#define rs i<<1|1
	#define mid ((l+r)>>1)
	ll sum[(int)2e6];
	void add(int i,int l,int r,int x,ll k){
		sum[i]+=k;if(l==r)return ;
		x<=mid?add(ls,l,mid,x,k):add(rs,mid+1,r,x,k);
	}
	ll query(int i,int l,int r,int x,int y){
		if(x<=l&&y>=r)return sum[i];ll ans=0;
		if(x<=mid)ans+=query(ls,l,mid,x,y);
		if(y>mid)ans+=query(rs,mid+1,r,x,y);return ans;
	}
}T[4];
int n,q,a[(int)5e5],cnt,tot,M;ll ans[(int)5e5],s[(int)5e5];
int stk[(int)5e5],top,R[(int)5e5],L[(int)5e5];
signed main(){
	freopen("d.in","r",stdin),freopen("d.out","w",stdout);
	// ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read(),q=read();for(int i=1;i<=n;i++)a[i]=read(),s[i]=s[i-1]+a[i];
	for(int i=1,l,r,t;i<=q;i++){
		t=read(),l=read(),r=read();
		Q[++cnt]={l-1,t,i,-1},Q[++cnt]={r,t,i,1};
	}sort(Q+1,Q+cnt+1,cmpq);
	for(int i=1;i<=n;i++){
		while(top&&a[stk[top]]<a[i])R[stk[top]]=i,top--;
		L[i]=stk[top];stk[++top]=i;
	}
	// while(top)R[stk[top--]]=n+1;
	for(int i=1;i<=n;i++){
		if(L[i]){
			C[++tot]={i,i-L[i],a[L[i]]-a[i]};
			if(R[i])C[++tot]={R[i],R[i]-L[i],a[i]-a[L[i]]};
		}
	}sort(C+1,C+tot+1,cmpc);int nw=1;
	for(int i=1;i<=cnt;i++){
		auto [pos,t,id,opt]=Q[i];
		while(nw<=tot&&C[nw].t<=t){
			auto [l,t0,d]=C[nw];
			T[0].add(1,0,n,l-1,1ll*(l-1)*d),T[1].add(1,-n,n,l-t0,1ll*(l-t0)*d);
			T[2].add(1,0,n,l-1,d),T[3].add(1,-n,n,l-t0,d);nw++;
		}ll res=0;
		if(pos){
			res+=s[pos];
			res+=T[1].query(1,-n,n,1,pos-t);
			res+=T[2].query(1,0,n,1,n)*t;
			res+=T[3].query(1,-n,n,pos-t+1,n)*(pos-t);
			res-=T[0].query(1,0,n,1,pos);
			res-=T[2].query(1,0,n,pos+1,n)*pos;
		}ans[id]+=opt*res;
	}for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
}

posted @ 2025-09-30 18:09  dfgz  阅读(22)  评论(0)    收藏  举报