2025-11-08 NOIP 模拟赛4 赛后总结
Record
- 8:06 会了 T1。特殊性质立大功。
- 8:22 过掉 T1 大洋里。开 T2。
- 8:30 没有任何思路。
- 9:16 思考 T2 思考了一个小时但还是没有任何头绪。放弃 T2。听说 T3 比 T2 可做。
- 10:23 写完 T3 了。直接过掉大洋里。
- 11:36 拍了拍 T3,发现了一个细节错误。这下应该没错了。
- (忘了几点)开 T4。打了个表然后啥也看不出来。遂放弃。
最后是 100+18+100+0。
T1 限速
题意
给定一张图,求这张图的生成树中权值最小的一个。
定义生成树的权值为:
- 若树中最大权值的边 \(w_{\max}\le k\),则权值为 \(k-w_{\max}\);
- 否则权值为 \(\sum_{w_i\ge k}|w_i-k|\)。
赛时
对于两个特殊性质,也就是 \(w_i\le k\) 和 \(w_i\ge k\) 而言,都是简单的。
所以考虑把两种情况合起来考虑,所以做完了。
题解
首先将原图所有边权 \(\ge k\) 的边去掉,跑一次 Kruskal。
如果图已经连通了,那么我们再把小于等于 \(k\) 的 \(w_{\max}\) 这条边加上去,那么答案就是 \(k-w_{\max}\)。
另一种情况,我们可以选择 \(\gt k\) 的最小边,让这棵树计算答案变成第二种。所以这种情况的答案就是两种取 \(\min\)。
如果图不连通,那么我们再在刚才的图的基础上加上 \(\gt k\) 的边再跑一次 Kruskal。然后答案计算就很显然了。
时间复杂度 \(O(m\log m+m\alpha(n))\)。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,m;
long long k;
struct node{
int x,y;
long long v;
bool operator<(const node&_Q)const{return v<_Q.v;}
};
vector<node> v_le,v_gt;
int fa[200010],siz[200010];
void pathzip(int&x){while(fa[x]!=x)x=fa[x]=fa[fa[x]];} // 黑科技
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
for(int i=1;i<=m;i++){
int x,y;cin>>x>>y;
long long v;cin>>v;
if(v<=k) v_le.push_back({x,y,v});
else v_gt.push_back({x,y,v});
}
int addcnt=0;
long long v_le_max=-infll;
for(auto pr:v_le){
int x=pr.x;
int y=pr.y;
pathzip(x);
pathzip(y);
if(x!=y){
if(siz[x]>siz[y]) swap(x,y);
fa[x]=y,siz[y]+=siz[x];
addcnt++;
}
v_le_max=max(v_le_max,pr.v);
}
sort(v_gt.begin(),v_gt.end());
if(addcnt==n-1){
long long ans=0;
if(v_gt.size()==0) ans=k-v_le_max;
else ans=min(k-v_le_max,v_gt[0].v-k);
cout<<ans<<"\n";
}else{
long long ans=0;
for(auto pr:v_gt){
int x=pr.x;
int y=pr.y;
pathzip(x);
pathzip(y);
if(x!=y){
if(siz[x]>siz[y]) swap(x,y);
fa[x]=y,siz[y]+=siz[x];
ans+=pr.v-k;
}
}
cout<<ans<<"\n";
}
# ifndef ONLINE_JUDGE
cerr<<"\nUsed time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
# endif
return 0;
}
T3 单峰数列(别样的分块大战。)
题意
维护一个序列,支持以下操作:
- 操作 \(1\):将在区间 \([l,r]\) 中的数加上 \(x\)。
- 操作 \(2\):查询区间 \([l,r]\) 的所有数是否全都相等。
- 操作 \(3\):查询区间 \([l,r]\) 是否严格单调递增。
- 操作 \(4\):查询区间 \([l,r]\) 是否严格单调递减。
- 操作 \(5\):查询区间 \([l,r]\) 是否呈单峰,即存在一个位置 \(l\lt p\lt r\),\([l,p]\) 严格单调递增,\([p,r]\) 严格单调递减。
赛时
T3 比 T2 可做是真的。
题解
序列问题,一眼神秘数据结构。
但是线段树似乎没办法直接维护这东西。
所以考虑分块。
其实存在非常天才的线段树维护差分的做法,但是我赛时没想出来,所以这里介绍我的神秘分块做法。
给每个块三个标签,分别代表块内所有元素满足全部相等、单调递增、单调递减。
然后带上懒标记就解决了前四个操作。
考虑操作 \(5\),不难注意到,位置 \(p\) 是且仅能是区间内的最大值。
所以再多维护一个区间最大值和对应的位置,则操作 \(5\) 成立的条件为 \([l,p]\) 满足操作 \(3\)、\([p,r]\) 满足操作 \(4\)。
因为懒了所以查询区间最大值直接上了线段树。
时间复杂度 \(O(q\log n+q\sqrt{n})\)。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;
class segmenttree{
private:
struct node{
int l,r;
pair<long long,int> val_max;
long long lz_add;
}t[400010];
public:
void pushdown(int p){
if(!t[p].lz_add) return;
t[p<<1].lz_add+=t[p].lz_add;
t[p<<1|1].lz_add+=t[p].lz_add;
t[p<<1].val_max.first+=t[p].lz_add;
t[p<<1|1].val_max.first+=t[p].lz_add;
t[p].lz_add=0;
}
void pushup(int p){
t[p].val_max=max(t[p<<1].val_max,t[p<<1|1].val_max);
}
void build(int l,int r,long long *a,int p=1){
t[p]={l,r,{-infll,0},0};
if(l==r) t[p].val_max={a[l],l};
else{
int mid=(l+r)>>1;
build(l,mid,a,p<<1);
build(mid+1,r,a,p<<1|1);
pushup(p);
}
}
void modify(int l,int r,long long v,int p=1){
if(l<=t[p].l&&t[p].r<=r){
t[p].lz_add+=v;
t[p].val_max.first+=v;
}else{
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) modify(l,r,v,p<<1);
if(r>mid) modify(l,r,v,p<<1|1);
pushup(p);
}
}
pair<long long,int> query(int l,int r,int p=1){
if(l<=t[p].l&&t[p].r<=r) return t[p].val_max;
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
pair<long long,int> res={-infll,0};
if(l<=mid) res=max(res,query(l,r,p<<1));
if(r>mid) res=max(res,query(l,r,p<<1|1));
return res;
}
};
segmenttree segt;
int n,q;
long long a[100010];
int block_length,belong[100010],block_first[320];
long long block_lz_add[320];
int block_tag[320];
void pushdown(int x){
if(!block_lz_add[x]) return;
for(int i=block_first[x];i<block_first[x+1];i++) a[i]+=block_lz_add[x];
block_lz_add[x]=0;
}
void rebuild(int x){
bool is_inc=true;
bool is_dec=true;
bool is_allsame=true;
for(int p=block_first[x]+1;p<block_first[x+1];p++){
is_inc&=(a[p-1]<a[p]);
is_dec&=(a[p-1]>a[p]);
is_allsame&=(a[p-1]==a[p]);
}
if(is_inc) block_tag[x]=1;
else if(is_dec) block_tag[x]=2;
else if(is_allsame) block_tag[x]=3;
else block_tag[x]=0;
}
void operation1(int l,int r,long long x){
if(belong[l]==belong[r]){
for(int i=l;i<=r;i++) a[i]+=x;
pushdown(belong[l]);
rebuild(belong[l]);
}else{
for(int i=l;i<block_first[belong[l]+1];i++) a[i]+=x;
for(int i=belong[l]+1;i<belong[r];i++) block_lz_add[i]+=x;
for(int i=block_first[belong[r]];i<=r;i++) a[i]+=x;
pushdown(belong[l]);
pushdown(belong[r]);
rebuild(belong[l]);
rebuild(belong[r]);
}
}
bool operation2(int l,int r){
bool is_allsame=true;
if(belong[l]==belong[r]){
pushdown(belong[l]);
rebuild(belong[l]);
for(int i=l+1;i<=r;i++) is_allsame&=(a[i-1]==a[i]);
}else{
pushdown(belong[l]);
pushdown(belong[r]);
rebuild(belong[l]);
rebuild(belong[r]);
long long same_value=a[l];
for(int i=l;i<block_first[belong[l]+1];i++) is_allsame&=(a[i]==same_value);
for(int i=belong[l]+1;i<belong[r];i++) is_allsame&=(block_tag[i]==3&&block_lz_add[i]+a[block_first[i]]==same_value);
for(int i=block_first[belong[r]];i<=r;i++) is_allsame&=(a[i]==same_value);
}
return is_allsame;
}
bool operation3(int l,int r){
bool is_inc=true;
if(belong[l]==belong[r]){
pushdown(belong[l]);
rebuild(belong[l]);
for(int i=l+1;i<=r;i++) is_inc&=(a[i-1]<a[i]);
}else{
pushdown(belong[l]);
pushdown(belong[r]);
rebuild(belong[l]);
rebuild(belong[r]);
for(int i=l+1;i<block_first[belong[l]+1];i++) is_inc&=(a[i-1]<a[i]);
for(int i=belong[l]+1;i<belong[r];i++) is_inc&=(block_tag[i]==1&&block_lz_add[i-1]+a[block_first[i]-1]<block_lz_add[i]+a[block_first[i]]);
is_inc&=(block_lz_add[belong[r]-1]+a[block_first[belong[r]]-1]<a[block_first[belong[r]]]);
for(int i=block_first[belong[r]]+1;i<=r;i++) is_inc&=(a[i-1]<a[i]);
}
return is_inc;
}
bool operation4(int l,int r){
bool is_dec=true;
if(belong[l]==belong[r]){
pushdown(belong[l]);
rebuild(belong[l]);
for(int i=l+1;i<=r;i++) is_dec&=(a[i-1]>a[i]);
}else{
pushdown(belong[l]);
pushdown(belong[r]);
rebuild(belong[l]);
rebuild(belong[r]);
for(int i=l+1;i<block_first[belong[l]+1];i++) is_dec&=(a[i-1]>a[i]);
for(int i=belong[l]+1;i<belong[r];i++) is_dec&=(block_tag[i]==2&&block_lz_add[i-1]+a[block_first[i]-1]>block_lz_add[i]+a[block_first[i]]);
is_dec&=(block_lz_add[belong[r]-1]+a[block_first[belong[r]]-1]>a[block_first[belong[r]]]);
for(int i=block_first[belong[r]]+1;i<=r;i++) is_dec&=(a[i-1]>a[i]);
}
return is_dec;
}
bool operation5(int l,int r){
pair<long long,int> wmax=segt.query(l,r);
if(wmax.second==l||wmax.second==r) return false;
return operation3(l,wmax.second)&&operation4(wmax.second,r);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
block_length=max((int)sqrt(n),1);
for(int i=1;i<=n;i++){
belong[i]=(i-1)/block_length+1;
if(!block_first[belong[i]]) block_first[belong[i]]=i;
}
block_first[belong[n]+1]=n+1;
for(int i=1;i<=belong[n];i++){
bool is_inc=true;
bool is_dec=true;
bool is_allsame=true;
for(int p=block_first[i]+1;p<block_first[i+1];p++){
is_inc&=(a[p-1]<a[p]);
is_dec&=(a[p-1]>a[p]);
is_allsame&=(a[p-1]==a[p]);
}
if(is_inc) block_tag[i]=1;
else if(is_dec) block_tag[i]=2;
else if(is_allsame) block_tag[i]=3;
else block_tag[i]=0;
}
segt.build(1,n,a);
cin>>q;
while(q--){
int op,l,r;cin>>op>>l>>r;
if(op==1){
long long x;cin>>x;
operation1(l,r,x);
segt.modify(l,r,x);
}
else if(op==2) cout<<operation2(l,r)<<"\n";
else if(op==3) cout<<operation3(l,r)<<"\n";
else if(op==4) cout<<operation4(l,r)<<"\n";
else if(op==5) cout<<operation5(l,r)<<"\n";
}
# ifndef ONLINE_JUDGE
cerr<<"\nUsed time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
# endif
return 0;
}
总结
T2 是什么啥比 Ad-hoc。。我为什么看不懂题解在说啥。。
T4 不会。

浙公网安备 33010602011771号