NOIP 2025 订正

前言

95+40+4+5=144 pts,wssb

NOIP 后两个小时加起来拿了 9 分的高分,不如冲 T2。

T1 Candy

题目

简单题,忘记特判钱是否够痛失 5 分。

T2 Sale

题目

考场上大概有些思路,但当时在发烧,脑子比较混乱,故去打 T3,T4 暴力,然后,就没有然后了,比赛结束了。。。

考虑容斥将其转换成总方案减去不是最优的方案。

可以先考虑 \(m=2\) 的部分分,我觉得是具有启发性的。

\(w_1=1\) 肯定是最优的,不符合条件。

\(w_1=2\) 时需要考虑什么时候会出现不优的情况。样例告诉我们:因为是按性价比排序,因此可能会出现一个 \(i\) 满足 \(w_i=1\)\(a_i>\frac{a_1}{2}\),此时会先去买第 \(i\) 件物品而导致买不起第 \(1\) 件物品,从而去买第一个 \(w_j=1\) 的物品或是没有买的起的物品。若 \(a_i+a_j<a_1\),则此时不是最优的,因为可以直接去买第 \(1\) 件物品。

可以枚举满足条件的 \(i,j\),从而计算答案。此时会有 \(i\) 前面所有物品的 \(w\) 均为 \(2\)\(w_{i+1\sim j-1}=2\),此时 \(j+1\sim n\) 的值可以任意取,因此贡献为 \(2^{n-j}\)

或是只有一个 \(i\) 此时除 \(i\)\(w\) 均为 \(2\),贡献为 \(1\)

代码长这样:

ll tp=0;
for(int i=2;i<=n;i++){
	if(a[i]*2<=a[1]) break;
	if(a[i]^a[1]) tp++;
	while(tp>=mod) tp-=mod;
	for(int j=i+1;j<=n;j++){
		if(a[i]+a[j]<a[1]) tp=(tp+K[n-j])%mod;
	}
}
ll ans=(K[n]-tp)%mod+mod;
if(ans>=mod) ans-=mod;

注意要特判 \(a_i=a_1\) 的情况。

正解:

会发现最后一定是最后买的物品出现不优的情况,就像 \(m=2\) 一样。

可以枚举 \(i,j\) 表示最后买 \(i\) 的物品是最优的,但去买了 \(j\) 物品。此时有两种情况:

  1. 只有 \(w_j=1\),此时可以枚举 \(w_k=2(k<i)\) 的个数,贡献为 \(\sum\limits_{p=0}^{i-1} \binom{i-1}{p}\binom{j-i-1}{m-p-i-1}=\binom{j-2}{m-i-1}\)。(范德蒙德卷积)

  2. 存在 \(w_k=2\),可以枚举 \(k\),有 \(w_{j+1\sim k-1}=2\),有贡献为 \(2^{n-k}\times \binom{j-2}{m-i-1}\)

复杂度为 \(\mathcal{O(n^3)}\)

发现可以拿指针维护最大满足条件的 \(k\),则贡献为 \(\binom{j-2}{m-i-1}+\sum\limits_{x=k}^n2^{n-x}\binom{j-2}{m-i-1}=2^{n-k+1}\binom{j-2}{m-i-1}\)

要预处理 \(2\) 的幂次。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
//#define Tp template<typename T>
//#define Ts template<typename T,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=5e3+20,M=1e5+20,MX=5000;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
    int n,m;
    ll a[N];
    ll pre[N],prn[N],K[N];
    inline ll qpow(ll x,ll y){
        ll Ans=1;
        for(;y;y>>=1,x=x*x%mod) if(y&1) Ans=Ans*x%mod;
        return Ans;
    }
    inline ll binom(ll x,ll y){
        if(x<y) return 0;
        return pre[x]*prn[y]%mod*prn[x-y]%mod;
    }
    inline void init(){
        K[0]=1;
        for(int i=1;i<=MX;i++) K[i]=K[i-1]*2%mod;
        pre[0]=prn[0]=1;
        for(int i=1;i<=MX;i++) pre[i]=pre[i-1]*i%mod;
        prn[MX]=qpow(pre[MX],mod-2);
        for(int i=MX-1;i;i--) prn[i]=prn[i+1]*(i+1)%mod;
    }
    inline void solve(){
        cin>>n>>m;
        for(int i=1;i<=n;i++) cin>>a[i];
		sort(a+1,a+1+n,greater<>());
		ll ans=0;
		for(int i=1;i<=n;i++){
			if(m-i-1<0) break;
			ll k=n+1;
			for(int j=i+1;j<=n;j++){
				if(a[i]==a[j]) continue;
				if(a[j]*2<=a[i]) break;
				while(k-1>j && a[k-1]+a[j]<a[i]) k--;
				ans=(ans+K[n-k+1]*binom(j-2,m-i-1)%mod)%mod;
			}
		}
		cout<<(K[n]-ans+mod)%mod<<"\n";
    }
	int main(){
        init();int C=0,T=1;
        cin>>C>>T;
        while(T--) solve();
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T3

T4

\(qn^2\log n\) 跑不过 \(qn^3\)。这就是人傻常数大吗?

好题。

先求前缀和,为 \(a_{0\dots n-1}\)

考虑固定 \(r\) 求所有 \(l\) 造成的贡献。

由于区间长度为 \([L,R]\),因此 \(l\in [r-R,r-L]\)。可以维护一个 \(a\) 递减的序列 \(q_1,\dots,q_k\)

对于序列里的两个位置 \(q_i,q_{i+1}\)。对于答案 \((q_i,q_{i+1}]\) 的贡献为 \(a_r-a_{q_i}\),序列的最后一个数对答案 \((q_k,r]\) 的贡献为 \(a_r-a_{q_k}\)(贡献即为对 ans 取 max)。可以分别计算贡献。

  1. 维护序列最后一个数的贡献:可以倒着枚举 \(r\),会有 \(q_k\) 是不增的,用 ST 表维护 \(q_k\),单调队列维护 \(q_k\)\(r\) 的贡献,可以看代码。

  2. 维护序列中间的贡献:会有 \(q_{i+1}\)\(q_i\) 右边第一个 \(a\) 比它小的数,可以单调栈预处理。对于 \(i\),设其右边第一个比它小的数的位置为 \(nxt_i\)(没有则 \(nxt_i=n\))。可以看做对于区间 \((i,nxt_i]\)\(\max\limits_{r,r-L\ge nxt_i \wedge r-R\le i} a_r-a_i=\max\limits_{r=nxt_i+L}^{i+R} a_r-a_i\) 可以拿 ST 表维护。可是每一个点会被很多个区间覆盖,最简单的方法是拿线段树维护,但会 TLE。
    会发现每个区间的关系为包含但不交,因此可以把区间看成编号为 \(i+1\) ,权值为这个区间的贡献的点,\(i+1\)\(j+1\) 有一条边,其中 \(j\) 为最大的满足 \((j,nxt_j]\) 包含 \((i,nxt_i]\)。那么 \(i\) 点的答案为它所有祖先的权值的 \(\max\) 遍历一遍树就可以了。

Tip:可以不用建树,对于 \(i\) 点它父亲的编号一定比它小,可以用类似于 dp 的方式处理。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define fi first
#define se second
//#define Tp template<typename T>
//#define Ts template<typename T,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=5e4+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int n,m,ff[N],pre[N],mn[N][17],nxt[N];
	ll a[N],ans[N],ans1[N],ans2[N];
	ll K[N],mx[N][17];
	set<PII> s;
	inline int get(int x,int y){
		if(x>y) swap(x,y);
		return a[x]<=a[y]?x:y;
	}
	inline int qmn(int l,int r){
		while(l>r) return -1;
		int k=K[r-l+1];
		return get(mn[l][k],mn[r-(1<<k)+1][k]);
	}
	inline ll qmx(int l,int r){
		while(l>r) return -INF;
		int k=K[r-l+1];
		return max(mx[l][k],mx[r-(1<<k)+1][k]);
	}
    int st[N],top;
	PII b[N];
	inline void init(){
		ans1[0]=-INF;
		for(int i=1;i<=n;i++) a[i]+=a[i-1];
		for(int i=2;i<=n+1;i++) K[i]=K[i>>1]+1;
		for(int i=1;i<=n;i++){
			mn[i][0]=i;
			mx[i][0]=a[i];
		}
		for(int j=1;j<=K[n];j++){//ST 表
			for(int i=1;i+(1<<j)-1<=n;i++){
				mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
			}
		}
		for(int j=1;j<=K[n+1];j++){
			for(int i=0;i+(1<<j)-1<=n;i++){
				mn[i][j]=get(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
			}
		}
       st[0]=n;
        for(int i=n-1;~i;i--){//单调栈
            while(top && a[st[top]]>a[i]) top--;
            nxt[i]=st[top];st[++top]=i;
			b[i]={i+1,nxt[i]};
        }
		for(int i=n-1;~i;i--){//找到第一个把它覆盖的点
			int r=nxt[i];
			auto itt=s.upper_bound({r,n+1});
			for(auto it=s.begin();it!=itt;it++) ff[(*it).second]=i+1;
			s.erase(s.begin(),itt);s.insert({r,i+1});
		}
	}
	inline void Init(){
		for(int i=1;i<=n;i++){
			ans[i]=ans1[i]=ans2[i]=-INF;
			pre[i]=0;
		}
	}
    inline void get_ans(){
        for(int i=1;i<=n;i++){//与其祖先取 max
			ans1[i]=max(ans1[i],ans1[ff[i]]);
		}
		for(int i=1;i<=n;i++){
			ans[i]=max(ans1[i],ans2[i]);
		}
    }
	int fr,tl;
	PLL q[N];
	int main(){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		init();
		cin>>m;
		for(int tt=1,L,R;tt<=m;tt++){
			cin>>L>>R;
			Init();
			fr=1,tl=0;
			for(int i=n;i;i--){
				if(i>=L){
					int pos=qmn(max(0,i-R),i-L);
					ll tp=a[i]-a[pos];
					while(fr<=tl && q[tl].se<=tp) tl--;//单调队列维护
					q[++tl]={pos+1,tp};
				}
				while(fr<=tl && q[fr].fi>i) fr++;
				if(fr<=tl) ans2[i]=q[fr].se;
			}
			for(int i=0;i<n;i++){
				ans1[i+1]=qmx(nxt[i]+L,min(n,i+R))-a[i];
			}
			get_ans();
			unsigned ll Ans=0;
			for(int i=1;i<=n;i++) Ans^=ans[i]*i;
			cout<<Ans<<"\n";
		}
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}
posted @ 2025-12-07 21:35  tyh_27  阅读(11)  评论(0)    收藏  举报