考试题
此为第三次出题的题解,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\)
- \(mx<0\)
\(a=mx,b=smx\)
- \(mx=0\)
易证最大值最大为 \(0\)。
\(a=mx(0),b=smx\)
- \(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;
}
算法:
整体二分,\(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\)
那么我们开四棵线段树分别维护
在 \(l\) 位置维护 \(l×d\)
在 \(l−t0\) 位置维护 \((l−t0)×d\)
在 \(l\) 位置维护 \(d\)
在 \(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';
}

考试题
浙公网安备 33010602011771号