NOIP 2025 总结 & 题解

NOIP 2025 总结 & 题解

考场

八点十分左右进考场。坐在我熟悉的位置上,心态已经放平了。

比赛开始,我先不急着写 Start,而是优先想题。

首先看 T1,想到每种商品可以拆成选最多一个 \(x_i\),和选任意多个 \(x_i+y_i\)。选任意多个可以选最小的 \(x_i+y_i\),而选最多一个 \(x_i\) 可以枚举选了哪些商品,将 \(x_i\) 排序后选的就是一个前缀。

感觉思路很对,而此时才过去十五分钟,我不准备先写代码,而是先想 T2,一开始 T2 的题意还读错了,还好看了样例解释。

想到九点钟,觉得此时有些浮躁了,于是转而去实现 T1 放松一下,也是立马实现完并且过大样例了,于是我接着想 T2。

我开始想什么情况下会导致选出的不等于最大值,准备先写 \(2^n\) 的暴力,模拟题意然后扫序列,此时发现了充要:找到第一次满足 \(m=1\) 且代价为 \(2\) 的物品,此时把上一个和下一个代价为 \(1\) 的物品替换为这个代价为 \(2\) 的物品会更优。把暴力写出来,发现它过了 \(n=5\) 的大样例!我很欣喜,开始冲正解。

由于 \(n\le5000\),考虑枚举上述上一个代价为 \(1\) 的位置,以及这个代价为 \(2\) 的位置,然后中间会分成很多段,每段都有一些限制,我们二分找到这些限制的分界点,然后贡献可以用组合数算。由于我对组合数学可能不是很擅长,这部分写了比较久,中途一直拿我的暴力对拍、修改。

时间接近十点半了,我还没过拍,此时有点慌了,但是我觉得我的思路很有前途,决定再冲一把,因为我有对拍。

在约十点半时,发现有个地方的贡献想错了,还好在代码上只要去掉一个二分即可,修改这个地方后,发现就过大样例了!接着我把二分换成双指针,通过了这道题。

此时还有两个小时十五分钟。

第三题想到一些贪心,但显然不对,不过很快就想到了 \(O(n^3)\) 的 DP,即子树内一些点可以任选,把这个记进状态里。写完后还剩一个半小时,我决定先做 T4,回来再看 T3。

接着我去想 T4,感觉十分难,尝试了几种方法,最终选择维护单调队列实现了一个 \(O(n\sum (R-L+1))\) 的做法。

放到虚拟机过了编译。剩下的时间继续看 T3,结果是一无所获。

如果没有挂分应该有 \(100+100+48+40=288\)

据说 T3 的 \(m=2\) 的档可以用 \(O(n^3)\) 的做法修改一下就能过,但我没去写。

upd on 12-03:T4 常数大只有 25pts(只能过前两个档和 A 性质),其他都和估的一样。

总结

这次发挥比较稳定,赛前和比赛时也不紧张。

T1 想到后没有立马实现而是去想 T2,当 T2 想得比较浮躁后可以回来写代码调整状态。

而 T2 做了比较久,最后能写出来的关键在于快速想到了贪心的充要并把暴力写了出来方便对拍。

T3 和 T4 没有做出来,但是获得了中档部分分,前两题能够在前半场过掉使我的心态比较好,并且使我获得充足时间,这些都是获得较多部分分的关键。

试题分析

这次的 NOIP 难度较以往更大,主要体现在 T2 更难,T3 和 T4 也稍微更难。

知识点方面,按照惯例都是较基础的算法,弱化了考察冷门、偏难的算法。而重在考察思维与观察能力,与做题经验有很大关系。

题解:candy

每种商品可以拆成选最多一个 \(x_i\),和选任意多个 \(x_i+y_i\)。选任意多个可以选最小的 \(x_i+y_i\),而选最多一个 \(x_i\) 可以枚举选了哪些商品,将 \(x_i\) 排序后选的就是一个前缀。

复杂度 \(O(n\log n)\)

题解:sale

考虑 \(2^n\times n\) 的做法,按照题意模拟后,我们依次选糖果,考虑什么时候此时的选择方案不是最大值。

可以发现充要:找到第一个满足剩余 \(m=1\)\(w=2\) 的位置,这个位置取不到,考虑上一个和下一个 \(w=1\) 的位置,可以把这两个 \(w=1\) 的位置换成这个 \(w=2\) 的位置,可能会更优。

考虑正解,将 \(a\) 从大到小排序后,我们枚举上述 \(w=2\) 的位置 \(i\),和上一个 \(w=1\) 的位置 \(j\),显然有 \(i<j\)。考虑其他位置怎么填,形如:

  • \([1,i-1]\) 的部分,这部分可以随便填。要求都被选到。

  • \([i+1,j-1]\) 的部分,这部分可以随便填,要求当且仅当 \(w_i=1\) 的被选到。

  • \([j+1,n]\) 的部分,存在一个界 \(lim\) 使得 \([j+1,lim-1]\) 内都必有 \(w_i=2\),而 \([lim+1,n]\) 可以任选。

    \(lim\) 即最小的满足 \(w_{lim}=1\) 时优先度劣于 \(i\) 且满足 \(w_{lim}+w_{j}<w_i\)\(lim\)

前两部分可以算出选多少个 \(w_i=2\),然后就可以组合数算贡献;第三个部分直接二分 \(lim\) 可以做到 \(O(n^2\log n)\),双指针即可 \(O(n^2)\)

题解:tree

赛时写了 \(O(n^3)\) 的 DP,设 \(f_{x,i,j}\) 表示点 \(x\) 子树内当前的 mex\(i\),且保留了 \(j\) 个点任选,转移就是先把子树的 \(i\) 取最值,\(j\) 相加。最后可以 \(f_{x,i,j}\to f_{x,i+1,j-1}\)。由于 \(i\) 一维可以前缀和优化,\(j\) 一维是树上背包,因此复杂度是 \(O(n^3)\),可以获得 48 分。

接下来这一步转化比较神:把上述 \(i\) 取到最大值的儿子作为重儿子,建出树剖结构。考虑如果已知树剖结构(即已知每个点的 \(i\) 取到最大值的 \(x\))怎么计算答案,结论就是,对于每个点 \(x\) 的贡献为它到根的路径上交集最大的重链的交集大小。

于是可以设出 DP,\(f(x,i,j)\) 表示当前点 \(x\) 作为链尾时链长为 \(j\),祖先最长链为 \(i\),此时的答案。

转移枚举重儿子 \(v\)\(f(x,i,j)\gets f(v,\max(i,j+1),j+1)+i+\sum _{z\ne v,z\in son(x)}f(z,i,1)\)。可以做到 \(O(nm^2)\),可以获得 76 分。

可以优化,考虑到当 \(j\) 加子树高度 \(hei_x\) 不超过 \(i\) 时,答案恒为 \(i\times sz_i\),于是 \(j\) 的范围压缩到了 \(O(hei_x)\) 级别。

考虑长剖:

  • 对于长儿子就是继承后,对全局加一个数,同时 \(f(x,i,i)\) 要特殊处理。不妨记录 \(s_{x,i}\) 表示 \(f(x,i,*)\) 要加上多少才是原来的值。
  • 对于非长儿子,当 \(j\) 小于 \(i-hei_v\) 时一定没有 \(j=i-hei_v\) 时更优,因此枚举范围限制在了 \(O(hei_v)\) 级。

总复杂度 \(O(nm)\)

题解:query

赛时写了 \(O(\sum (R-L)n)\) 的做法,有人拿了 45 分,但我的实现是每次都用单调队列做一遍,时间是严格的所以只有 25 分。

赛后 100 分写的是分治做法的优化。

考虑分治,每次处理分治区间 \([l,r]\) 内的且跨过 \(mid\) 的区间的贡献,不妨考虑对左半边的贡献,枚举左端点,那么右端点取在一个区间内,用 ST 表求最值即可,由于右端点都跨过了 \(mid\) 因此求出每个左端点的贡献后只需求前缀最值即可求出对左半边每个点的贡献。

复杂度 \(O(qn\log n)\)。如果每次限制枚举左端点的范围,且及时 return,不难发现复杂度实际上是 \(O(\frac {nR}L)\),即有 \(\frac nL\) 个叶子,每个点是 \(O(R)\)

由上述复杂度,可以考虑倍增分块,预处理 \(L=2^i,R=2^{i+1}-1\) 的所有答案,然后对于左右端不完整的块再跑上面的算法,复杂度 \(O(n\log ^2n+qn)\),求分块后一个区间的答案可以对每个点开 \(O(\log ^2n)\) 的数组然后暴力求区间最值。

代码

T1

namespace fastio {
    const int sz=1<<20;
    char buf[sz],*p1=buf,*p2=buf;
    #define getc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,sz,stdin), p1==p2) ? EOF : *p1++)
    template<typename T> inline void read(T &x) {
        x=0; char ch=getc(),neg=0;
        while(ch<'0'||ch>'9') {
            if(ch=='-') neg=1;
            ch=getc();
        }
        while(ch>='0'&&ch<='9') x=(x*10)+(ch^48),ch=getc();
        if(neg) x=-x;
    }
};
using fastio::read;

const int N=1e5+5;
int n; ll m;
int a[N],mn=2e9+5;

signed main() {
    freopen("candy.in","r",stdin);
    freopen("candy.out","w",stdout);
    read(n),read(m);
    fo(i,1,n) {
        int x,y; read(x),read(y);
        mn=min(mn,x+y);
        a[i]=x;
    }
    ll s=0,ans=m/mn*2;
    sort(a+1,a+1+n);
    fo(i,1,n) {
        s+=a[i];
        if(s>m) break;
        ans=max(ans,i+(m-s)/mn*2);
    }
    cout<<ans<<'\n';
}

T2

const int mod=998244353;
const int N=5005;
void inc(int &x,const int &y) {x+=y; if(x>=mod) x-=mod; }
int mul(const int &x,const int &y) {return (ull)x*y%mod;}
int add(const int &x,const int &y) {return x+y>=mod  ? x+y-mod :x+y;}
int pow1(int x,int y) {
	int res=1;
	for(;y;y>>=1,x=mul(x,x)) if(y&1) res=mul(res,x);
	return res;
}
int fac[N],inv[N],pw[N];
int binom(int x,int y) {
	if(x<y||x<0||y<0) return 0;
	return mul(mul(fac[x],inv[y]),inv[x-y]);
}
int cid;
int n,m,a[N];
void Main() {
    read(n), read(m);
    fo(i,1,n) read(a[i]);
	sort(a+1,a+1+n,greater<>());
	fac[0]=inv[0]=pw[0]=1;
	fo(i,1,n) {
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(inv[i-1],fac[i]);
		pw[i]=mul(2,pw[i-1]);
	}
	inv[n]=pow1(inv[n],mod-2);
	fd(i,n,1) {
		int t=inv[i-1];
		inv[i-1]=mul(inv[i],fac[i]);
		inv[i]=mul(inv[i],t);
	}
	int ans=0;
	fo(i,1,n) {
		int l=i+1,r=n,L=n+1;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(a[i]>=2*a[mid]) L=mid,r=mid-1;
			else l=mid+1;
		}
		int res=n+1;
		fo(j,i+1,n) {
			while(res>i+1&&a[res-1]+a[j]<a[i]) --res;
			if(a[i]<2*a[j]&&a[j]<a[i]) 
				inc(ans,mul(binom(j-2,m-1-i),pw[n-max(j+1,max(res,L))+1]));
		}

	}
	ans=(pw[n]-ans+mod)%mod;
	printf("%d\n",ans);
}

T3

int n,m;
vi G[N];
template<typename T> inline void ckmax(T &x,const T &y) {if(y>x) x=y;}

int son[N],hei[N],sz[N];
void init(int x) {
	sz[x]=1;
	hei[x]=0;
	for(int v:G[x]) {
		init(v);
		if(hei[v]>hei[x]) hei[x]=hei[v],son[x]=v;
		sz[x]+=sz[v];
	}
	++hei[x];
}

int tot;
ll F[805][N],*f[N][805],s[N][805];
ll getval(int x,int i,int j) {
	if(j+hei[x]<=i) return ll(sz[x])*i;
	return f[x][i][j]+s[x][i];
}

void dfs(int x,int top) {
	if(G[x].empty()) {
		fo(i,1,m) {
			f[x][i]=F[i]+tot-i;
			f[x][i][i]=i;
		}
		tot+=hei[top];
		return;
	}
	ll sm[805];
	memset(sm,0,sizeof sm);
	for(int v:G[x]) {
		dfs(v,son[x]==v?top:v);
		fo(i,1,m) sm[i]+=getval(v,i,1);
	}
	fo(i,1,m) {
		f[x][i]=f[son[x]][i]+1;
		s[x][i]=s[son[x]][i]+i+sm[i]-getval(son[x],i,1);
		if(i<m) f[x][i][i]=getval(son[x],i+1,i+1)-s[son[x]][i];
	}
	for(int v:G[x]) if(v!=son[x]) {
		fo(i,1,m) {
			if(i<m) {
				ll t=getval(v,i+1,i+1)+sm[i]-getval(v,i,1)+i;
				if(t>getval(x,i,i)) f[x][i][i]=t-s[x][i];
			}
			int dnto=max(1,i-hei[v]);
			fd(j,i-1,dnto) {
				ll t=getval(v,i,j+1)+sm[i]-getval(v,i,1)+i;
				if(t>getval(x,i,j)) f[x][i][j]=t-s[x][i];
			}
		}
	}
}

void Main() {
	cin>>n>>m;
	fo(i,1,n) G[i].clear();
	++m;
	fo(i,2,n) {
		int pa; cin>>pa;
		G[pa].pb(i);
	}
	init(1);
	tot=0;
	memset(F,192,sizeof F);
	memset(s,0,sizeof s);
	memset(f,0,sizeof f);
	dfs(1,1);
	cout<<getval(1,1,1)<<'\n';
}

T4

int n,Q;
int A[N];
ll a[N];
ll mx[16][N],mn[16][N];
ll getmn(int l,int r) {
	int lg=__lg(r-l+1);
	return min(mn[lg][l],mn[lg][r-(1<<lg)+1]);
}
ll getmx(int l,int r) {
	int lg=__lg(r-l+1);
	return max(mx[lg][l],mx[lg][r-(1<<lg)+1]);
}
int L,R;
ll res[N];
ll t[N][16][16];
template<typename T> void ckmax(T &x,const T &y) {if(y>x) x=y;}
void solve(int l,int r) {
	if(r-l+1<L) return;
	int mid=(l+r)>>1;
	int bg=max(l,mid-R+2);
	ll cur=-inf;
	fo(i,bg,mid) {
		if(i+L-1<=min(i+R-1,r)) ckmax(cur,getmx(max(mid+1,i+L-1),min(i+R-1,r))-a[i-1]);
		ckmax(res[i],cur);
	}
	bg=min(r,mid+R-1);
	cur=-inf;
	fd(i,bg,mid+1) {
		if(max(l-1,i-R)<=i-L) ckmax(cur,a[i]-getmn(max(l-1,i-R),min(mid-1,i-L)));
		ckmax(res[i],cur);
	}
	solve(l,mid),solve(mid+1,r);
}
void Main() {
	cin>>n;
	fo(i,1,n) cin>>A[i],a[i]=a[i-1]+A[i];
	fo(i,0,n) mx[0][i]=mn[0][i]=a[i];
	fo(i,1,__lg(n+1)) fo(j,0,n-(1<<i)+1) {
		mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<i-1)]);
		mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
	}
	memset(t,192,sizeof t);
	for(int i=2;i<=n;i*=2) {
		L=i,R=min(n,i*2-1);
		memset(res,192,sizeof res);
		solve(1,n);
		int lg=__lg(i);
		fo(j,1,n) t[j][lg][lg]=res[j];
	}
	fo(i,1,n) {
		fo(len,2,15) {
			fo(j,1,15-len+1) {
				int k=j+len-1;
				ckmax(t[i][j][k],t[i][j+1][k]);
				ckmax(t[i][j][k],t[i][j][k-1]);
			}
		}
	}
	cin>>Q;
	while(Q--) {
		int ql,qr; cin>>ql>>qr;
		int bg=0,ed=0;
		memset(res,192,sizeof res);
		if(ql==1) {
			fo(i,1,n) res[i]=A[i];
			ql=2;
		} 
		if(ql<=qr) {
			for(int i=2;i<=n;i*=2) {
				int to=min(n,i*2-1);
				if(ql<=i&&to<=qr) {
					if(!bg) bg=__lg(i);
					ed=__lg(i);
				}
			}
			if(!bg) {
				L=ql,R=qr,solve(1,n);
			}
			else {
				fo(i,1,n) ckmax(res[i],t[i][bg][ed]);
				if(ql<(1<<bg)) L=ql,R=(1<<bg)-1,solve(1,n);
				if(qr>=(1<<ed+1)) L=1<<ed+1,R=qr,solve(1,n);
			}
		}
		ull ans=0;
		fo(i,1,n) ans^=ull(i)*res[i];
		cout<<ans<<'\n';
	}
}
posted @ 2025-12-02 15:23  dengchengyu  阅读(104)  评论(0)    收藏  举报