【比赛记录】2025CSP+NOIP 冲刺模拟赛合集Ⅳ

HZOJ NOIP2025模拟3

A B C D Sum Rank
100 40 20 12 172 7/28

A. 变形怪

直接记忆化搜索即可。\(x\) 中包含前十个质数时答案最大,为 \(458123\),可以接受。

Code
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int m;
ll n,a[17];
__gnu_pbds::cc_hash_table<ll,__gnu_pbds::null_type> ans;
il void dfs(ll x){
//	cout<<x<<'\n';
	if(ans.find(x)!=ans.end()){
		return ;
	}
	ans.insert(x);
	if(!x){
		return ;
	}
	for(int i=1;i<=m;i++){
		dfs(x/a[i]);
	}
}
int main(){
	freopen("set.in","r",stdin);
	freopen("set.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i];
	}
	sort(a+1,a+m+1);
	m=unique(a+1,a+m+1)-a-1;
	dfs(n);
	cout<<ans.size();
	return 0;
}
}
int main(){return asbt::main();}
/*
562949953421312 10
2 3 5 7 11 13 17 19 23 29
*/

B. 忍者小队

\(b_x=\sum_{i=1}^{n}[x|S_i]\),可以调和级数求。于是有如果最小值存在则最大值为 \(b_x\),否则最大值也不存在。

记值域为 \(V\)。注意到前七个质数的乘积就超过了 \(V\),所以 \(k=1\) 时答案最多为 \({7\choose6}=7\),显然 \(k\) 更大时答案也不会超过 \(7\)。考虑枚举每个答案是否可行。假设当前枚举到了 \(t\),设 \(f_x\) 表示选出 \(t\) 个数使它们的 \(\gcd=x\) 的方案数,则有:

\[f_x={b_x\choose t}-\sum_{i=2}^{\lfloor\frac{V}{x}\rfloor}f_{ix} \]

于是若 \(f_x=0\)\(t\) 不可行,否则可行。时间复杂度 \(O(7V\ln V)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5,mod=1e9+7,V=3e5,inf=1e9;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,m,a[maxn],fac[maxn],inv[maxn],tong[maxn],f[maxn],g[maxn],ans[maxn];
il int qpow(int x,int y=mod-2){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		x=x*1ll*x%mod,y>>=1;
	}
	return res;
}
il void init(int n=V){
	fac[0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
	}
	inv[n]=qpow(fac[n]);
	for(int i=n;i;i--){
		inv[i-1]=inv[i]*1ll*i%mod;
	}
}
il int C(int x,int y){
	return x<y||y<0?0:fac[x]*1ll*inv[y]%mod*inv[x-y]%mod;
}
int main(){
	freopen("sor.in","r",stdin);
	freopen("sor.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		tong[a[i]]++;
	}
	for(int i=1;i<=V;i++){
		for(int j=i;j<=V;j+=i){
			g[i]+=tong[j];
		}
	}
	init();
	memset(ans,0x3f,sizeof(ans));
	for(int t=1;t<=7;t++){
		for(int i=V;i;i--){
			f[i]=C(g[i],t);
			for(int j=i<<1;j<=V;j+=i){
				sub(f[i],f[j]);
			}
			if(f[i]){
				ans[i]=min(ans[i],t);
			}
		}
	}
	for(int i=1;i<=m;i++){
		if(ans[i]>=inf){
			cout<<-1<<' '<<-1<<'\n';
		}else{
			cout<<ans[i]<<' '<<g[i]<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 尘埃下的神话

D. 怪盗德基

11.08 HZOJ NOIP2025模拟4

A B C D Sum Rank
100 40 65 - 205 4/24

听了 zwh 的建议决定记录日期,因为教练老是改比赛名字🐱‍💻

A. 括号问号

首先考虑对于一个确定的字符串求 \(f(S)\),设 \(dp_{i,j}\) 表示考虑到 \(i\),多出来 \(j\)( 的方案数,有转移:

\[dp_{i,j}=\begin{cases} \begin{aligned} &dp_{i-1,j-1}&s_i='('\\ &dp_{i-1,j+1}&s_i=')'\\ &dp_{i-1,j-1}+dp_{i-1,j+1}&s_i='?' \end{aligned} \end{cases} \]

考虑对每个子序列求和,设 \(dp_{i,j,k}\) 表示考虑到 \(i\)\(i\) 是当前子序列的第 \(j\) 位,多出来 \(k\)( 的方案数,转移是类似的。注意到第二维只会从 \(j-1\) 转移到 \(j\),可以直接去掉。然后再前缀和优化一下即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5,mod=998244353;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,f[maxn][maxn];
string s;
int main(){
	freopen("bracket.in","r",stdin);
	freopen("bracket.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>s;
	s=" "+s;
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=i;j++){
			if(s[i]=='('){
				f[i][j]=pls(f[i-1][j],j?f[i-1][j-1]:0);
			}else if(s[i]==')'){
				f[i][j]=pls(f[i-1][j],f[i-1][j+1]);
			}else{
				f[i][j]=pls(f[i-1][j],pls(j?f[i-1][j-1]:0,f[i-1][j+1]));
			}
		}
	}
	cout<<f[n][0];
	return 0;
}
}
int main(){return asbt::main();}

B. 狗卡

考虑从总贡献中减去损失的贡献,即如果存在英雄在 \(t\) 时刻完成了升级则将贡献减去 \(t\)

于是我们要做的就是将 \(n\) 个数组 \(a_{i,j}\) 重排到另一个数组 \(b_i\) 中,使得在 \(b\) 中满足 \(a\) 中的顺序,所有前缀和的和最小。考虑两端在 \(a\) 中连续的 \(x\)\(y\),则 \(x\)\(y\) 的前面的充要条件就是 \(x\) 的平均值小于 \(y\)。于是我们要将每个 \(a_i\) 分段,使得每一段的平均值都尽可能的小。考虑 \(a\) 的前缀和数组 \(s\),对于每个 \(i\),我们会有 \(k_i\) 个点 \((j,s_j)\),而两点之间的斜率就是这一段的平均值。考虑最后每一段的平均值都最小,则必然斜率递增,于是我们维护一个下凸包即可。然后就不断将每个 \(i\) 的最前面一段丢进优先队列即可。注意这里不要使用 queue,因为它的底层实现是 deque,内存是分段连续的多个固定大小的数组块,空间占用巨大。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=6e5+5,maxm=1.2e6+5;
int n,b[maxn],s[maxm],p[maxn];
ll m,c[maxm];
vector<int> a[maxn];
struct node{
	int i,l,r,len;
	ll sum;
	node(int i=0,int l=0,int r=-1,ll sum=0):i(i),l(l),r(r),len(r-l+1),sum(sum){}
	il bool operator<(const node &x)const{
		return sum*x.len>x.sum*len;
	}
};
vector<node> d[maxn];
priority_queue<node> q;
int main(){
	freopen("dog.in","r",stdin);
	freopen("dog.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>b[i];
		a[i].resize(b[i]+1);
		int top=0;
		s[++top]=0;
		for(int j=1;j<=b[i];j++){
			cin>>a[i][j];
			c[j]=c[j-1]+a[i][j];
			while(top>1&&(c[j]-c[s[top]])*(s[top]-s[top-1])<=(c[s[top]]-c[s[top-1]])*(j-s[top])){
				top--;
			}
			s[++top]=j;
		}
		for(int j=2;j<=top;j++){
			d[i].pb(node(i,s[j-1]+1,s[j],c[s[j]]-c[s[j-1]]));
		}
		q.push(d[i].front()),p[i]=1;
	}
//	puts("666");
//	return 0;
	ll ans=0,cur=0,sum=0;
	while(q.size()){
		node t=q.top();
		q.pop();
		for(int i=t.l;i<=t.r;i++){
			ans+=cur*a[t.i][i],cur++,sum+=a[t.i][i];
		}
		if(p[t.i]<d[t.i].size()){
			q.push(d[t.i][p[t.i]]),p[t.i]++;
		}
	}
	cout<<ans+(m-sum)*cur;
	return 0;
}
}
signed main(){return asbt::main();}

C. 均衡区间

首先求出 \(i\) 左/右侧第一个比 \(a_i\) 大/小的位置 \(sl_i,gl_i,sr_i,gr_i\)。于是对于一个合法的区间 \([i,j]\),必然有 \(i<\min(sl_j,gl_j)\land j>\max(sr_i,gr_i)\)。而这个条件显然也是充分的,直接二维数点即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=1e6+5,inf=2e9;
int n,T,a[maxn],sl[maxn],gl[maxn],sr[maxn],gr[maxn],s[maxn],ans[maxn];
pii b[maxn];
struct{
	#define lowbit(x) (x&-x)
	int tr[maxn];
	il void clear(){
		memset(tr,0,sizeof(tr));
	}
	il void add(int p,int x){
		for(;p<=n+1;p+=lowbit(p)){
			tr[p]+=x;
		}
	}
	il int query(int p){
		int res=0;
		for(;p;p-=lowbit(p)){
			res+=tr[p];
		}
		return res;
	}
	#undef lowbit
}F;
il void work(){
	int t=0;
	s[0]=0;
	for(int i=1;i<=n;i++){
		while(t&&a[s[t]]>=a[i]){
			t--;
		}
		sl[i]=s[t];
		s[++t]=i;
	}
	t=0;
	for(int i=1;i<=n;i++){
		while(t&&a[s[t]]<=a[i]){
			t--;
		}
		gl[i]=s[t];
		s[++t]=i;
	}
	s[0]=n+1,t=0;
	for(int i=n;i;i--){
		while(t&&a[s[t]]>=a[i]){
			t--;
		}
		sr[i]=s[t];
		s[++t]=i;
	}
	t=0;
	for(int i=n;i;i--){
		while(t&&a[s[t]]<=a[i]){
			t--;
		}
		gr[i]=s[t];
		s[++t]=i;
	}
//	for(int i=1;i<=n;i++){
//		cout<<i<<' '<<sl[i]<<' '<<gl[i]<<' '<<sr[i]<<' '<<gr[i]<<'\n';
//	}
	for(int i=1;i<=n;i++){
		b[i]=mp(min(sl[i],gl[i]),i);
	}
	sort(b+1,b+n+1);
	int p=n;
	F.clear();
	for(int i=n;i;i--){
		while(p&&b[p].fir>i){
			F.add(b[p--].sec,1);
		}
		ans[i]=F.query(n+1)-F.query(max(sr[i],gr[i]));
	}
}
int main(){
	freopen("interval.in","r",stdin);
	freopen("interval.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>T;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	work();
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	cout<<'\n';
	reverse(a+1,a+n+1);
	work();
	for(int i=n;i;i--){
		cout<<ans[i]<<' ';
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 喵了个喵了个喵

11.10 HZOJ NOIP2025模拟5

A B C D Sum Rank
100 60 100 - 260 5/20

A. 家具运输

显然当 \(w\) 增加时,运输次数一定不会变多,于是二分答案,模拟运输的过程即可。时间复杂度 \(O(n^2\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5;
int n,m,a[maxn];
bool b[maxn];
il bool chk(){
	for(int i=1;i<=n;i++){
		if(!b[i]){
			return 1;
		}
	}
	return 0;
}
il bool check(int x){
	memset(b,0,sizeof(b));
	int cnt=0;
	while(chk()){
		int sum=0;
		for(int j=1;j<=n;j++){
			if(!b[j]&&sum+a[j]<=x){
				b[j]=1,sum+=a[j];
			}
		}
		cnt++;
	}
	return cnt<=m;
}
int main(){
	freopen("trans.in","r",stdin);
	freopen("trans.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	int l=0,r=4e6;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		l=max(l,a[i]);
	}
	sort(a+1,a+n+1,greater<>());
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		}else{
			l=mid+1;
		}
	}
	cout<<l;
	return 0;
}
}
int main(){return asbt::main();}

B. 取石子

考虑如果总和为奇数,则第一步取 \(1\) 即可获胜;否则双方不停取偶数,可以递归到 \(k\gets\lfloor\frac{k}{2}\rfloor,a_i\gets\lfloor\frac{a_i}{2}\rfloor\)

于是我们有先手必胜的充要条件:\(\bigoplus a_i\not\equiv0\pmod{2^{\lfloor\log k\rfloor}}\)

考虑第一步之后的异或和,假设其 \(lowbit\)\(2^t\),我们显然希望第一步取的值小于 \(2^t\),否则后手就可以取 \(2^t\),先手必败。于是我们枚举取的位置和 \(t\),找出需要取多少能使第 \(t\) 位一下都变成 \(0\) 即可。有可能会算重,去重即可,时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=5e4+5;
int n,m,a[maxn];
vector<pii> ans;
int main(){
	freopen("nim.in","r",stdin);
	freopen("nim.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	int sum=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum^=a[i];
	}
	for(int i=0;1<<i<=m;i++){
		if(sum>>i&1){
			cout<<1<<'\n';
			goto togo;
		}
	}
	cout<<0;
	return 0;
	togo:;
	for(int i=1;i<=n;i++){
		sum^=a[i];
//		cout<<i<<' '<<(bitset<4>)sum<<'\n';
		for(int t=1;t<=30;t++){
			int d1=a[i]&((1<<t)-1);
			int d2=sum&((1<<t)-1);
			int tmp=d1>d2?d1-d2:d1+(1<<t)-d2;
			if(tmp<=a[i]&&tmp<=m){
				ans.pb(mp(i,tmp));
			}
		}
		sum^=a[i];
	}
	sort(ans.begin(),ans.end());
	ans.erase(unique(ans.begin(),ans.end()),ans.end());
	for(pii x:ans){
		cout<<x.fir<<' '<<x.sec<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 选取字符串

考虑 kmp,连边 \(nxt_i\to i\),于是我们得到了一棵树,每个点的所有祖先就是他的 border。于是对于每个点 \(u\),我们可以计算 \(p,q\) 中有 \(u\) 的答案:\((2\times dep_u-1)\times{sz_u\choose k}\)。时间复杂度 \(O(n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e6+5,mod=998244353;
il int qpow(int x,int y=mod-2){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		x=x*1ll*x%mod,y>>=1;
	}
	return res;
}
int n,m,nxt[maxn],sz[maxn],fac[maxn],inv[maxn],ans;
string s;
vector<int> e[maxn];
il int C(int x,int y){
//	cout<<x<<' '<<y<<'\n';
	return x<y||y<0?0:fac[x]*1ll*inv[x-y]%mod*inv[y]%mod;
}
il void dfs(int u,int dep){
	sz[u]=1;
	for(int v:e[u]){
		dfs(v,dep+1);
		sz[u]+=sz[v];
	}
//	cout<<u<<' '<<dep<<' '<<2*dep-1<<' '<<sz[u]<<' '<<C(sz[u],m)<<' '<<(2*dep-1)*1ll*C(sz[u],m)<<'\n';
	ans=((2*dep-1)*1ll*C(sz[u],m)+ans)%mod;
}
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>m>>s;
	n=s.size(),s=" "+s;
	e[0].pb(1);
	for(int i=2,j=0;i<=n;i++){
		while(j&&s[i]!=s[j+1]){
			j=nxt[j];
		}
		if(s[i]==s[j+1]){
			j++;
		}
		nxt[i]=j;
		e[j].pb(i);
	}
//	for(int i=1;i<=n;i++){
//		cout<<nxt[i]<<' ';
//	}
//	cout<<'\n';
	fac[0]=1;
	for(int i=1;i<=n+1;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
	}
	inv[n+1]=qpow(fac[n+1]);
	for(int i=n+1;i;i--){
		inv[i-1]=inv[i]*1ll*i%mod;
	}
//	for(int i=0;i<=n;i++){
//		for(int j=0;j<=i;j++){
//			cout<<C(i,j)<<' ';
//		}
//		cout<<'\n';
//	}
	dfs(0,1);
//	for(int i=0;i<=n;i++){
//		cout<<sz[i]<<' ';
//	}
//	cout<<'\n';
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

D. 蚂蚁搬家

先假设有解。

考虑操作序列的最后一条边 \((u,v)\),其必然为两棵树的重边,否则就无法操作。进一步考虑,将这条边删去(\(u,v\) 合并)后,操作序列的倒数第二条边 \((u',v')\) 必然也是重边。于是我们考虑倒着做,每次将重边删去。

那么做法实际上就很显然了:每次找到一条重边 \((u,v)\),将两点用并查集并起来,然后将二者连出的边合并到同一点上。注意到合并的次数很多,启发式合并即可。使用哈希表维护每个点的临边和图上的重边,时间复杂度 \(O(n\log n)\)

如果这个过程中没有重边了那就无解。

Code
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define ll long long
#define il inline
#define ull unsigned ll
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int n,fa[maxn];
__gnu_pbds::gp_hash_table<int,__gnu_pbds::null_type> st[maxn];
__gnu_pbds::gp_hash_table<ull,__gnu_pbds::null_type> S;
queue<pii> q;
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il ull hash(int u,int v){
	if(u>v){
		swap(u,v);
	}
	return u*1llu*maxn+v;
}
il void addedge(int u,int v){
	st[u].insert(v),st[v].insert(u);
	ull ha=hash(u,v);
	if(S.find(ha)!=S.end()){
		q.push(mp(u,v));
	}else{
		S.insert(ha);
	}
}
int main(){
	freopen("ants.in","r",stdin);
	freopen("ants.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1,u,v;i<=2*n-2;i++){
		cin>>u>>v;
		addedge(u,v);
	}
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	while(--n){
		if(q.empty()){
			cout<<"NO";
			return 0;
		}
		int u=find(q.front().fir),v=find(q.front().sec);
		q.pop();
		if(st[u].size()>st[v].size()){
			swap(u,v);
		}
		st[v].erase(u),S.erase(hash(u,v));
		for(int x:st[u]){
			if(x==v){
				continue;
			}
			st[x].erase(u);
			S.erase(hash(u,x));
			addedge(v,x);
		}
		fa[u]=v;
	}
	cout<<"YES";
	return 0;
}
}
int main(){return asbt::main();}

11.11 2025noip模拟赛73

A B C D Sum Rank
100 - 20 20 140 4/7

久违的原神启动场啊(bushi

A. 饥饿的狐狸

最小值显然是极差,从水的温度向下走一遍再向上走一遍就好了。

考虑最大值,先不考虑喝水,我们希望每一段都尽量被经过更多次,每次交替选择最大的和最小的饼干即可。加上喝水,那就每次都将是否喝水取个最大值。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[maxn];
ll ans;
int main(){
//	freopen("a25.in","r",stdin);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	int mn=m,mx=m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		mn=min(mn,a[i]);
		mx=max(mx,a[i]);
	}
	cout<<mx-mn<<' ';
	sort(a+1,a+n+1);
	ll res=0;
	for(int i=1,j=n,x=m;i<=j;i++,j--){
		res+=max(abs(x-a[i]),abs(m-a[i]));
		if(i<j){
			res+=max(abs(a[i]-a[j]),abs(m-a[j]));
		}
		x=a[j];
	}
	ans=max(ans,res),res=0;
	for(int i=1,j=n,x=m;i<=j;i++,j--){
		res+=max(abs(x-a[j]),abs(m-a[j]));
		if(i<j){
			res+=max(abs(a[i]-a[j]),abs(m-a[i]));
		}
		x=a[i];
	}
	ans=max(ans,res);
	cout<<ans;
    return 0;
}
}
int main(){return asbt::main();}

B. 保险箱

如果 \(x,y\) 是密码,则由裴蜀定理有 \(\gcd(x,n),\gcd(x,y)\) 必然是密码。设密码集合为 \(S\)\(g=\gcd(S_1,S_2,\dots S_p,n)\),于是 \(S\) 就是 \(g\) 的所有倍数。于是我们考虑枚举所有可能的 \(g\) 检查是否合法。合法的充要条件也是显然的,即为 \(\forall i\in[1,k-1],g\nmid m_i\)。设 \(\gcd(n,m_k)=d\),直接做是 \(O(\sigma(d)k)\) 的,其中 \(\sigma(d)\) 表示 \(d\) 的约数个数,大概是 \(10^4\) 量级。考虑优化,我们考虑提前将 \(m_i\) 的所有因数全都找出来打上标记。这看似没有优化,但进一步,我们标记所有 \(m_i\) 的因数是没有用的,因为我们只需要判断所有 \(d\) 的因数是否可行,于是令 \(m_i\gets\gcd(m_i,d)\)。此时我们可以使用记忆化搜索,并且提前将 \(d\) 的所有质因数都找出来。时间复杂度 \(O(k\log d+\sqrt{d}+\sigma(d)\omega(d))\),其中 \(\omega(d)\) 表示 \(d\) 的不同质因子个数,是 \(O(\log d)\) 的。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2.5e5+5;
int n,m,a[maxn];
vector<int> prm;
set<int> vis;
il void div(int x){
	for(int i=2;i<=x/i;i++){
		if(x%i==0){
			prm.pb(i);
			while(x%i==0){
				x/=i;
			}
		}
	}
	if(x>1){
		prm.pb(x);
	}
}
il void dfs(int x){
	if(vis.count(x)){
		return ;
	}
	vis.insert(x);
	for(int p:prm){
		if(x%p==0){
			dfs(x/p);
		}
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int d=__gcd(a[n],m);
	div(d);
	for(int i=1;i<n;i++){
		dfs(__gcd(a[i],d));
	}
	int i=1;
	for(;i<=d/i;i++){
		if(d%i==0&&!vis.count(i)){
			cout<<m/i;
			return 0;
		}
	}
	for(i--;i;i--){
		if(d%i==0&&!vis.count(d/i)){
			cout<<m/(d/i);
			return 0;
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}

C. 追逐

\(f_{u,i,0/1}\) 表示从根走到 \(u\) 的子树中,\(u\) 子树内部用了 \(i\) 个磁铁,\(u\) 有没有用磁铁的最大答案。注意这里我们实际上统计的是以根为开头的链,这样转移就不用考虑前效性:

\[f_{u,i,0}\gets\max(f_{v,i,0},f_{v,i,1})\\ f_{u,i,1}\gets f_{u,i-1,0}+b_u \]

其中 \(b_u\) 表示 \(u\) 的所有儿子的 \(a\) 之和。

时间复杂度 \(O(n^2V)\),于是再换根即可。需要记录转移过程中的最大值和次大值。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[maxn],b[maxn],f[maxn][105][2],hp[105][2];
vector<int> e[maxn];
struct{
	int mx1,mx2;
	il void insert(int x){
		if(x>=mx1){
			mx2=mx1,mx1=x;
		}else if(x>mx2){
			mx2=x;
		}
	}
}g[maxn][105];
il void dfs1(int u,int fa){
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dfs1(v,u),b[u]+=a[v];
		for(int i=1;i<=m;i++){
			g[u][i].insert(max(f[v][i][0],f[v][i][1]));
			f[u][i][0]=max({f[u][i][0],f[v][i][0],f[v][i][1]});
		}
	}
	for(int i=1;i<=m;i++){
		f[u][i][1]=f[u][i-1][0]+b[u];
	}
}
il void dfs2(int u,int fa){
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		int bb=b[u]-a[v];
		b[v]+=a[u];
		for(int i=1;i<=m;i++){
			if(max(f[v][i][0],f[v][i][1])==g[u][i].mx1){
				hp[i][0]=g[u][i].mx2;
			}else{
				hp[i][0]=g[u][i].mx1;
			}
			hp[i][1]=hp[i-1][0]+bb;
			g[v][i].insert(max(hp[i][0],hp[i][1]));
			f[v][i][0]=max({f[v][i][0],hp[i][0],hp[i][1]});
			f[v][i][1]=f[v][i-1][0]+b[v];
		}
		dfs2(v,u);
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	dfs1(1,0),dfs2(1,0);
	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			ans=max({ans,f[i][j][0],f[i][j][1]});
		}
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

D. 字符串

C 记为 \(1\)T 记为 \(-1\)

首先考虑暴力,显然贪心,先正着扫,如果到某个位置前缀和变为负数,就将它删掉;然后再同样倒着扫一遍。

考虑怎么快速完成这个过程。给这个区间做前缀和和后缀和,分别记为 \(pre\)\(suf\)。先考虑第一步正着扫,我们要将所有的负的前缀和变成非负数,也就是会在第一次出现 \(-1,-2,-3\dots\) 的地方进行加一操作,操作的次数即为 \(-\min\{pre_p\}\),记为 \(-pre_{\min}\)。然后考虑第二步倒着扫。对于每个位置 \(q\),我们考虑第一步对 \(suf_q\) 的影响。在 \(<q\) 的位置上进行加一的次数是显然的 \(-\min_{p<q}\{pre_p\}\),于是在 \(q\) 后面加一的次数即为 \(\min_{p<q}\{pre_p\}-pre_{\min}\)。于是新的后缀和 \(suf'_q=suf_q+(\min_{p<q}\{pre_p\}-pre_{\min})\)。于是两步相加,答案即为 \(-\min_{p<q}\{pre_p+suf_q\}\)

然后可以发现这个式子实际上就是区间最大子段和减去区间和,线段树维护一下即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=5e5+5;
int n,m;
string s;
struct node{
	int sum,lm,rm,mm;
	node(int sum=0,int lm=0,int rm=0,int mm=0):sum(sum),lm(lm),rm(rm),mm(mm){}
	il node operator+(const node &x)const{
		return node(sum+x.sum,max(lm,sum+x.lm),max(rm+x.sum,x.rm),max({mm,x.mm,rm+x.lm}));
	}
}tr[maxn<<2];
il void pushup(int id){
	tr[id]=tr[lid]+tr[rid];
}
il void build(int id,int l,int r){
	if(l==r){
		int x=s[l-1]=='C'?1:-1;
		tr[id]=node(x,max(x,0),max(x,0),max(x,0));
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il node query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return tr[id];
	}
	int mid=(L+R)>>1;
	if(r<=mid){
		return query(lid,L,mid,l,r);
	}
	if(l>mid){
		return query(rid,mid+1,R,l,r);
	}
	return query(lid,L,mid,l,r)+query(rid,mid+1,R,l,r);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>s>>m;
	build(1,1,n);
	while(m--){
		int l,r;
		cin>>l>>r;
		node ans=query(1,1,n,l,r);
		cout<<ans.mm-ans.sum<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

11.12 HZOJ NOIP2025模拟6

A B C D Sum Rank
60 20 - 40 120 17/29

A. 汉谟拉比(crazy)

\(b_i=\sum_{j=1}^{n}[a_j>i]\)。于是我们可以 DP:设 \(f_{i,j}\) 表示当前分了 \(i\) 份,和为 \(j\) 的最小答案,于是有转移:

\[f_{i,j}\gets f_{i-1,j-k}+b_k \]

直接转移是 \(O(nm^2)\) 的。考虑此时每一份都是等价的,于是可以进行分治:

  • \(n\) 为奇数,递归到 \(n-1\),再 \(O(m^2)\) 暴力加入一个。
  • \(n\) 为偶数,递归到 \(\frac{n}{2}\),再自己跟自己卷积一下,时间复杂度也是 \(O(m^2)\) 的。

于是时间复杂度 \(O(m^2\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define uprb upper_bound
using namespace std;
namespace asbt{
const int maxn=5e3+5,inf=1e9;
int n,m,a[maxn],b[maxn],f[maxn],g[maxn];
il void solve(int n){
	if(!n){
		memset(f,0x3f,sizeof(f));
		f[0]=0;
		return ;
	}
	if(n&1){
		solve(n-1);
		memset(g,0x3f,sizeof(g));
		for(int i=0;i<=m;i++){
			for(int j=0;j<=i;j++){
				g[i]=min(g[i],f[i-j]+b[j]);
			}
		}
		memcpy(f,g,sizeof(g));
	}else{
		solve(n>>1);
		memset(g,0x3f,sizeof(g));
		for(int i=0;i<=m;i++){
			for(int j=0;j<=m-i;j++){
				g[i+j]=min(g[i+j],f[i]+f[j]);
			}
		}
		memcpy(f,g,sizeof(g));
	}
}
int main(){
	freopen("crazy.in","r",stdin);
	freopen("crazy.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	for(int i=0;i<=m;i++){
		b[i]=n-(uprb(a+1,a+n+1,i)-a-1);
	}
	solve(n);
	int ans=inf;
	for(int i=0;i<=m;i++){
		ans=min(ans,f[i]);
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 虫洞折跃(flip)

\(f_{x,y}\) 表示走到 \((x,y)\) 的最小操作次数。于是我们有最基本的转移:

\[f_{x',y'}\gets\begin{cases} \begin{aligned} &f_{x,y}&&a_{x,y}=a_{x',y'}\\ &f_{x,y}&&a_{x,y}=1\land a_{x',y'}=0\\ &f_{x,y}+1&&a_{x,y}=0\land a_{x',y'}=1 \end{aligned} \end{cases} \]

这里我们不用考虑 \(rc>1\) 的限制,因为在一般情况下,路径上一个单独的 \(1\) 可以和路径外的一些点一起取反。

然后我们需要进行一些特判。

  • 首先是 \(n=1\) 的情况,如果只有一个 \(1\),则需要将它和旁边的一段 \(0\) 一起取反,然后再将那一段 \(0\) 再取一次反;否则有多少段连续的 \(1\) 就操作多少次。不过在 \(m\le 3\) 的情况下这不一定能成立,特判掉即可。\(m=1\) 同理。
  • 然后考虑如果左下角是 \(1\),且它的两侧都是 \(0\),有可能又需要类似地进行两次取反。不过如果它的右边还有 \(1\),则只需要操作一次即可。右上角同理。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e3+5;
const int dx[]={0,1},dy[]={1,0};
int T,n,m,a[maxn][maxn],f[maxn][maxn];
int main(){
	freopen("flip.in","r",stdin);
	freopen("flip.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		if(n==1||m==1){
			if(n>m){
				swap(n,m);
			}
			for(int i=1;i<=m;i++){
				cin>>a[1][i];
			}
			if(m==1){
				if(a[1][1]){
					cout<<"Impossible\n";
				}else{
					cout<<0<<'\n';
				}
			}else if(m==2){
				if(a[1][1]!=a[1][2]){
					cout<<"Impossible\n";
				}else{
					cout<<(a[1][1]?1:0)<<'\n';
				}
			}else if(m==3){
				switch(a[1][1]<<2|a[1][2]<<1|a[1][3]){
					case 0b000:{
						cout<<0<<'\n';
						break;
					}case 0b001:{
						cout<<2<<'\n';
						break;
					}case 0b010:{
						cout<<3<<'\n';
						break;
					}case 0b011:{
						cout<<1<<'\n';
						break;
					}case 0b100:{
						cout<<2<<'\n';
						break;
					}case 0b101:{
						cout<<2<<'\n';
						break;
					}case 0b110:{
						cout<<1<<'\n';
						break;
					}default:{
						cout<<1<<'\n';
						break;
					}
				}
			}else{
				int cnt1=0,cnt2=0;
				for(int i=1;i<=m;i++){
					if(a[1][i]){
						int j=i;
						while(j<m&&a[1][j+1]){
							j++;
						}
						cnt1+=j-i+1,cnt2++,i=j;
					}
				}
				cout<<(cnt1==1?2:cnt2)<<'\n';
			}
		}else{
			for(int i=1;i<=n;i++){
				for(int j=1;j<=m;j++){
					cin>>a[i][j];
				}
			}
			memset(f,0x3f,sizeof(f));
			f[1][1]=a[1][1];
			for(int x=1;x<=n;x++){
				for(int y=1;y<=m;y++){
					for(int i:{0,1}){
						int xx=x+dx[i],yy=y+dy[i];
						if(xx>n||yy>m){
							continue;
						}
						int w=0;
						if(a[x][y]==a[xx][yy]||a[x][y]){
							w=0;
						}else if(xx==n&&yy==1){
							for(int j=2;j<=m;j++){
								if(a[n][j]){
									w=1;
									goto togo1;
								}
							}
							w=2;
							togo1:;
						}else if(xx==1&&yy==m){
							for(int j=2;j<=n;j++){
								if(a[j][m]){
									w=1;
									goto togo2;
								}
							}
							w=2;
							togo2:;
						}else{
							w=1;
						}
						f[xx][yy]=min(f[xx][yy],f[x][y]+w);
					}
				}
			}
			cout<<f[n][m]<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 深巢温泉(dnspring)

总数总是不很好求,不妨求期望 \(E\),最后再乘 \([(2m+1)\frac{n(n+1)}{2}]^q\) 即可。

考虑对每个位置分别求出贡献。首先对于一个操作,位置 \(x\) 被选中的概率为:

\[p_x=\frac{x(n-x+1)}{\frac{n(n+1)}{2}} \]

\(f_{x,y}\) 表示 \(x\) 位置在第 \(y\) 次操作后的值,考虑修改操作,取 \(\min\)\(\max\) 操作放在一起考虑,等价于有 \(\frac{1}{2}\) 的概率变成 \(0\sim m-1\) 中的任意值,\(\frac{1}{2}\) 的概率不变。于是有转移:

\[f_{x,0}=0\\[3mm] f_{x,y}=\frac{p_xm}{2m+1}\cdot\frac{m-1}{2}+(1-\frac{p_xm}{2m+1})f_{x,y-1} \]

\(r_x=1-\frac{p_xm}{2m+1}\)。于是有:

\[f_{x,y}=(1-r_x)\frac{m-1}{2}+r_xf_{x,y-1} \]

\(f_{x,y}+\lambda=r_x(f_{x,y-1}+\lambda)\),于是 \(\lambda=\frac{1-m}{2}\)。于是:

\[f_{x,y}-\frac{m-1}{2}=r_x(f_{x,y-1}-\frac{m-1}{2})\\[3mm] \Rightarrow f_{x,y}-\frac{m-1}{2}=-r_x^y\frac{m-1}{2}\\[3mm] \Rightarrow f_{x,y}=\frac{1}{2}(m-1)(1-r_x^y) \]

于是:

\[E=\sum_{x=1}^{n}\frac{p_x}{2m+1}\sum_{y=0}^{q-1}a_{x,y}\\[3mm] = \sum_{x=1}^{n}\frac{p_x}{2m-1}\cdot\frac{m-1}{2}(q-\frac{r_x^q-1}{r_x-1}) \]

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5,mod=1e9+7;
il int qpow(int x,int y=mod-2){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		x=x*1ll*x%mod,y>>=1;
	}
	return res;
}
int n,m,q,p[maxn],r[maxn];
int main(){
	freopen("dnspring.in","r",stdin);
	freopen("dnspring.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>q;
	int ans=0;
	for(int i=1;i<=n;i++){
		p[i]=i*2ll*(n-i+1)%mod*qpow(n*1ll*(n+1)%mod)%mod;
		r[i]=(1+mod-p[i]*1ll*m%mod*qpow(2*m+1)%mod)%mod;
		ans=(p[i]*1ll*(m-1)%mod*qpow(4*m+2)%mod*(q+mod-(qpow(r[i],q)-1+mod)*1ll*qpow(r[i]-1+mod)%mod)+ans)%mod;
	}
	cout<<ans*1ll*qpow((2*m+1)*1ll*n%mod*(n+1)%mod*qpow(2)%mod,q)%mod;
	return 0;
}
}
int main(){return asbt::main();}

D. 逃离冰场(skate)

称由于蹬了一脚而出现的冰为生成冰。有这样的结论:我们不会两次撞到生成冰上。因为每个空格走到另一个空格都可以通过先蹬出去,再蹬回来两步完成,因此如果我们像走到这个生成冰旁边的某个位置,不如当时就两步走过去。

于是我们考虑图论建模。每个「可以通过的点」向四连通的「可以通过的点」连一条边权为 \(2\) 的边,再向四个方向的第一块冰连一条边权为 \(1\) 的边,然后跑最短路即可。由于边权只有 \(1\)\(2\),可以将 \(2\) 拆成两个 \(1\) 和一个虚点,于是跑 bfs 即可。

但是这样在 AT 上过不了,因为空间卡的比较死。但是我懒得写了

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ID(x,y) (n*((y)-1)+(x))
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e3+5,maxm=5e6+5,inf=1e9;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int n,m,r1,c1,r2,c2,dis[maxm];
bool vis[maxm];
string s[maxn];
vector<int> e[maxm];
queue<int> q;
int main(){
	freopen("skate.in","r",stdin);
	freopen("skate.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		s[i]=" "+s[i];
	}
	cin>>r1>>c1>>r2>>c2;
	int cnt=n*m;
	for(int x=1;x<=n;x++){
		for(int y=1;y<=m;y++){
			if(s[x][y]=='.'){
				for(int i:{0,1,2,3}){
					int xx=x+dx[i],yy=y+dy[i];
					if(xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.'){
						e[ID(x,y)].pb(++cnt);
						e[cnt].pb(ID(xx,yy));
					}
				}
			}else{
				for(int i:{0,1,2,3}){
					for(int xx=x+dx[i],yy=y+dy[i];xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.';xx+=dx[i],yy+=dy[i]){
						e[ID(xx,yy)].pb(ID(x+dx[i],y+dy[i]));
					}
				}
			}
		}
	}
	memset(dis,0x3f,sizeof(dis));
	dis[ID(r1,c1)]=0,q.push(ID(r1,c1)),vis[ID(r1,c1)]=1;
	while(q.size()){
		int u=q.front();
		q.pop();
		for(int v:e[u]){
			if(!vis[v]){
				dis[v]=dis[u]+1,q.push(v),vis[v]=1;
			}
		}
	}
	cout<<(dis[ID(r2,c2)]>=inf?-1:dis[ID(r2,c2)]);
	return 0;
}
}
int main(){return asbt::main();}

upd:空间卡过了。建四连通的边时只向右面和下面连边即可减少一半的虚点。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ID(x,y) (n*((y)-1)+(x))
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e3+5,maxm=3e6+5,inf=1e9;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int n,m,r1,c1,r2,c2,dis[maxm],q[maxm];
bool vis[maxm];
string s[maxn];
vector<int> e[maxm];
int main(){
	freopen("skate.in","r",stdin);
	freopen("skate.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		s[i]=" "+s[i];
	}
	cin>>r1>>c1>>r2>>c2;
	int cnt=n*m;
	for(int x=1;x<=n;x++){
		for(int y=1;y<=m;y++){
			if(s[x][y]=='.'){
				for(int i:{0,3}){
					int xx=x+dx[i],yy=y+dy[i];
					if(xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.'){
						e[ID(x,y)].pb(++cnt);
						e[cnt].pb(ID(xx,yy));
						e[cnt].pb(ID(x,y));
						e[ID(xx,yy)].pb(cnt);
					}
				}
			}else{
				for(int i:{0,1,2,3}){
					for(int xx=x+dx[i],yy=y+dy[i];xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.';xx+=dx[i],yy+=dy[i]){
						e[ID(xx,yy)].pb(ID(x+dx[i],y+dy[i]));
					}
				}
			}
		}
	}
	memset(dis,0x3f,sizeof(dis));
	int hd=1,tl=0;
	dis[ID(r1,c1)]=0,q[++tl]=ID(r1,c1),vis[ID(r1,c1)]=1;
	while(hd<=tl){
		int u=q[hd++];
		for(int v:e[u]){
			if(!vis[v]){
				dis[v]=dis[u]+1,q[++tl]=v,vis[v]=1;
			}
		}
	}
	cout<<(dis[ID(r2,c2)]>=inf?-1:dis[ID(r2,c2)])<<'\n';
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2025-11-06 17:26  zhangxy__hp  阅读(37)  评论(0)    收藏  举报