2026/4/25 测试

2026/4/25 测试

T1「NOIP模拟」开挂

简单题,容易发现最后的 \(a\) 数组是可以确定的。然后我们举一个简单的例子,假设初始序列 \(a\)2 2 3,最后的 \(a\) 数组就为 2 3 4。然后我们发现就会有两种方案操作,第一种为一个 \(3\) 加一和一个 \(2\) 加一,另一种为一个 \(2\) 加二,于是我们发现无论 \(b\) 是什么,我们总可以让第二种的代价小于等于第一种的代价。所以我们得出结论,让一个数挪得越多,并把最小的 \(b\) 给他越优,就是一个贪心。然后就没了,详细看代码。然后因为要对 \(2^{64}\) 取模,开 unsigned long long 就好了。

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N=1e6+5;
int n;
int a[N],b[N],c[N],fx[N];
map<int,int> mp;
bool cmp(int l,int r){
	return l>r;
}
int find(int x){
	return fx[x]==x?x:fx[x]=find(fx[x]);
}
void hb(int x,int y){
	if(a[x]+c[x]>=a[y]+c[y]){
		fx[y]=x;
	}
	else{
		fx[x]=y;
	}
	return ;
}
signed main(){
	freopen("openhook.in","r",stdin);
	freopen("openhook.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		fx[i]=i;
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++){
		if(mp[a[i]]==0){
			mp[a[i]]=i;
			c[i]=0;
			if(mp[a[i]-1]!=0){
				int kl=find(mp[a[i]-1]);
				hb(kl,find(i));
			}
			if(mp[a[i]+1]!=0){
				int kl=find(mp[a[i]+1]);
				hb(kl,find(i));
			}
		}
		else{
			int kl=find(mp[a[i]]);
			mp[a[kl]+c[kl]+1]=i;
			c[i]=a[kl]+c[kl]+1-a[i];
			hb(kl,find(i));
			if(mp[a[kl]+c[kl]+2]!=0){
				kl=find(mp[a[kl]+c[kl]+2]);
				hb(kl,find(i));
			}
		}
	}
	sort(c+1,c+1+n,cmp);
	sort(b+1,b+1+n);
	int ans=0;
	for(int i=1;i<=n;i++){
		ans=ans+c[i]*b[i];
	}
	cout<<ans<<'\n';
	return 0;
} 

T2「NOIP模拟」叁仟柒佰万

好题。首先有一个结论,不管你怎么分,每一段的 \(mex\) 等于整个序列的 \(mex\)

证明如下:如果有一种划分,令其每一段的 \(mex\)\(x\)

\(x<mex\) 时,因为 \(0\)\(mex-1\) 的数全部都在序列中出现过了,所以不管你怎么划分,肯定有一段会被分到一个 \(x\),此时这一段的 \(mex\) 就无法取到 \(x\),矛盾。

\(x>mex\) 时,显然不可能,矛盾。

所以我们知道这个结论后,就很好做了。因为我们要求的是区间数量,对于区间数量有一个很经典的方法,定一求一。我们枚举区间的右端点 \(r\),定义 \(l\) 为最大的满足 \(l\)\(r\) 这个区间的 \(mex\) 等于整个序列的 \(mex\) 的位置,容易发现 \(1\)\(l\) 都满足这个条件,所以有 \(dp_i\) 表示前 \(i\) 个位置的方案数,转移为 \(dp_r=1+\sum_{i=1}^{l-1}dp[i]\) 然后弄个前缀和就好了。详细看代码。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int N=3e7+7e6+5;
int n,a[N],vis[N];
int sum[N];
int ksm(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=(long long)res*x%mod;
		x=(long long)x*x%mod;
		y>>=1;
	}
	return res;
}
void sl(){
	int n;
	cin>>n;
	if(n==37000000){
		long long x,y;
		cin>>x>>y; 
		a[1]=0;
		for(int i=2;i<=n;i++){
			a[i]=(x*a[i-1]+y+i)&(262143);
		}
	}
	else{
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
	}
	int mex=0;
	for(int i=1;i<=n;i++){
		vis[a[i]]++;
		while(vis[mex]){
			mex++;
		}
	}
	for(int i=1;i<=n;i++){
		vis[a[i]]--;
	}
	if(mex==0){
		cout<<ksm(2,n-1)<<'\n';
		return ;
	}
	int l=1,cnt=0;
	sum[0]=1;
	for(int i=1;i<=n;i++){
		if(a[i]<mex&&vis[a[i]]==0) cnt++;
		sum[i]=0;
		vis[a[i]]++;
		if(cnt==mex){
			while(1){
				if(vis[a[l]]==1 && a[l]<mex)break;
				else{
					vis[a[l++]]--;
				}
			}
			sum[i]+=sum[l-1];
			sum[i]%=mod;
		}
		sum[i]=(sum[i]+sum[i-1])%mod;
	}
	cout<<(sum[n]-sum[n-1]+mod)%mod<<'\n';
	while(l<=n){
		vis[a[l++]]--;
	}
}
signed main(){
	freopen("clods.in","r",stdin);
	freopen("clods.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;
	while(t--){
		sl();
	}
	return 0;
}

T3「NOIP模拟」超级加倍

神秘好题。嗯,我们的老师在讲这道题的时候有一句话:“对于这种树上最值问题就要考虑 \(Kruskal\) 重构树啊。”嗯,所以这道题就是 \(Kruskal\) 重构树,我们建两棵重构树,其中一颗树 \(a\) 的边权为边两端点编号的最小值,相反另一颗树 \(b\) 就是最大值,如果在 \(a\) 中,\(x\)\(y\)\(lca\) 的权值为 \(x\),在 \(b\) 中,\(x\)\(y\)\(lca\) 的权值为 \(y\)。就是一条合法路径,也就是说一条路径 \(x\)\(y\)\(x\le y\)),如果 \(adfn_x>adfn_y\)\(bdfn_x<bdfn_y\)。则路径合法,然后就没有了。

没有代码。

T4「NOIP模拟」欢乐豆

容易发现 \(n\) 很大,而 \(m\) 很小。所以考虑从 \(m\) 入手。我们把被改边权的边两端的端点标记为关键点,然后我们先考虑关键点到关键点的距离,这个直接dij,但有一个情况就是一个关键点可能会通过一个非关键点到一个关键点,代价为当前关键点 \(x\) 的值 \(a_x\) 加非关键点 \(y\) 的值 \(a_y\)\(a_y\) 我们取最小的就可以了,因为到各个点的距离一样,而且只会通过一次非关键点,证明显然。然后就考虑关键点到非关键点的距离,这个和关键点到关键点的距离相似,就不讲了。最后就是非关键点到任意点的距离,容易发现都是原本的权值乘 \((n-1)\)。然后就做完了,复杂度 \(O(m^2logm)\)

posted @ 2026-04-27 09:56  yxbb  阅读(14)  评论(0)    收藏  举报