sm2025 模拟赛24 (2025.10.20)

T1 超级集合

link
思路
由于删数的实际作用是减小剩下数的排名,所以最后答案排名为 1 1 1
考虑时光倒流。在第 k k k 轮结束后,答案排名为 x = 1 x=1 x=1。考虑第 k − 1 k-1 k1 轮答案的排名 x ′ x' x 。找到最大的 i i i 满足 a i + 1 ≤ x + i a_i+1\le x+i ai+1x+i ,即 a i − i + 1 ≤ x a_i-i+1 \le x aii+1x ,此时 x ′ ← x + i x' \leftarrow x+i xx+i 。这样一轮轮枚举的复杂度为 O ( k ) O(k) O(k) 。发现随着 x x x 的增大, i i i 也会增大。那么从小到大枚举 i i i ,计算出有多少轮的 x x x 的决策点在 i i i ,复杂度 O ( n ) O(n) O(n)

反思
好像之前模拟赛2出过类似思路的题?
所以对于这种删数的是不是可以考虑倒着做然后加数。
虽然但是找规律也有 75pts .

代码

#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
ll a[maxn];
int main(){
	freopen("superset.in","r",stdin);
	freopen("superset.out","w",stdout);
	int id,t; cin>>id>>t;
	while(t--){
		ll n,k; cin>>n>>k;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		if(a[1]>1){
			cout<<"1\n";
			continue;
		}
		ll x=1;
		for(int i=1;i<n;i++){
			ll tmp=(a[i+1]-x-1)/i;
			if(tmp>k){
				x+=k*i;
				break;
			}
			x+=tmp*i; k-=tmp;
		}
		cout<<x+n*k<<"\n";
	}
	return 0;
}

T2 宝可梦

link
思路
首先每只怪物的每种属性只会被改一次。考虑建图,直接暴力建边为 O ( n 2 m ) O(n^2m) O(n2m) 。考虑优化建图。
首先带着点权 c i c_i ci 操作不方便,将一个点拆成入点和出点。然后由于每个属性值只会从小变化到大,所以对于每种属性,将怪物排序后用一条链连起来即可,边权为相邻两值的差。总共会连 O ( 4 n m ) O(4nm) O(4nm) 条边。跑一次最短路即可。

代码

#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,int>
using namespace std;
const int maxn=4e5+5,INF=1e18;

int idx,head[maxn];
struct EDGE{ int v,next; ll w; }e[maxn*2];
void Insert(int u,int v,ll w){
	e[++idx]={v,head[u],w};
	head[u]=idx;
}

int n,m;
ll dis[maxn];
priority_queue<pir,vector<pir>,greater<pir> >q; 
void Dij(){
	for(int i=1;i<=n*m+n;i++){
		dis[i]=INF;
	}
	while(!q.empty()) q.pop();
	dis[n*m+1]=0; q.push({0,n*m+1});
	while(!q.empty()){
		int x=q.top().second; ll y=q.top().first; q.pop();
		if(dis[x]<y) continue;
		for(int i=head[x];i;i=e[i].next){
			int v=e[i].v;
			if(dis[v]>dis[x]+e[i].w){
				dis[v]=dis[x]+e[i].w;
				q.push({dis[v],v});
			}
		}
	}
}

struct NODE{
	ll w; int id; 
	bool operator < (const NODE a)const{
		return a.w>w;
	}
};
vector<NODE>a[maxn];
int main(){
	freopen("pokemon.in","r",stdin);
	freopen("pokemon.out","w",stdout);
	int id,t; scanf("%d%d",&id,&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1,x;i<=n;i++){
			scanf("%d",&x);
			for(int j=1;j<=m;j++)
				Insert((i-1)*m+j,n*m+i,x),Insert(n*m+i,(i-1)*m+j,0);
		}
		for(int i=1;i<=n;i++)
			for(int j=1,x;j<=m;j++){
				scanf("%d",&x);
				a[j].push_back({x,i});
			}
		for(int i=1;i<=m;i++){
			sort(a[i].begin(),a[i].end());
			for(int j=1;j<n;j++){
				Insert((a[i][j].id-1)*m+i,(a[i][j-1].id-1)*m+i,a[i][j].w-a[i][j-1].w),
				Insert((a[i][j-1].id-1)*m+i,(a[i][j].id-1)*m+i,0);
			}
		}
		Dij();
		printf("%lld\n",dis[n*m+n]);
		for(int i=1;i<=m;i++)
			a[i].clear();
		memset(head,0,sizeof(head));
		idx=0;
	}
	return 0;
}

T3 城市地铁

link
思路
首先我们希望对于每个询问 ( x , y ) (x,y) (x,y) ,让 x , y x,y x,y 都跳到他们的 lca ,此时的换乘数量之和 − - 跳到 lca 时最后是否乘到同一个地铁就是答案。发现一步步跳很慢,考虑倍增。记 f i , j f_{i,j} fi,j 表示从 i i i 号点换乘 2 j 2^j 2j 次能跳到的最高的点。
实际上判断 x , y x,y x,y 跳到 lca 时最后是否乘到同一个地铁可以将询问离线下来,即在 x , y x,y x,y 都跳到 lca 的下一层时要判断是否有一条地铁能从 x ′ x' x 的子树出发到达 y ′ y' y 的子树,这可以将子树通过 d f n dfn dfn 转化为一段连续的区间,类似二位数点判断即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;

int idx,head[maxn];
struct EDGE{ int v,next; }e[maxn*2];
void Insert(int u,int v){
	e[++idx]={v,head[u]};
	head[u]=idx;
}

int dep[maxn],fatr[maxn][23],dfn[maxn],ed[maxn],tim;
void Dfs(int x,int fa){
	dep[x]=dep[fa]+1; fatr[x][0]=fa; dfn[x]=++tim;
	for(int i=0;i<20;i++)
		fatr[x][i+1]=fatr[fatr[x][i]][i];
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fa) Dfs(v,x);
	}
	ed[x]=tim;
}

int Get_lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[fatr[x][i]]>=dep[y]) x=fatr[x][i];
	if(x==y) return x;
	for(int i=20;i>=0;i--)
		if(fatr[x][i]!=fatr[y][i]) x=fatr[x][i],y=fatr[y][i];
	return fatr[x][0];
}

int f[maxn][23];
vector<int>pos[maxn];
int Dfs2(int x,int fa){
	int mi=x;
	for(auto i:pos[x])
		if(dep[i]<dep[mi]) mi=i;
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fa){
			int tmp=Dfs2(v,x);
			if(dep[tmp]<dep[mi]) mi=tmp;
		}
	}
	return f[x][0]=mi;
}

void Dfs3(int x,int fa){
	for(int i=0;i<20;i++)
		f[x][i+1]=f[f[x][i]][i];
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fa) Dfs3(v,x);
	}
}

int Jump_(int &x,int d){
	int s=0;
	for(int i=20;i>=0;i--)
		if(dep[f[x][i]]>d) x=f[x][i],s+=(1<<i);
	return s;
}

int n,tree[maxn];
void Add(int x){
	while(x<=n) tree[x]++,x+=x&(-x);
}
int Ask(int x){
	int s=0;
	while(x) s+=tree[x],x-=x&(-x);
	return s;
}

int ans[maxn],ok[maxn];
struct SUY{ int x,y; }sw[maxn];
struct QUE{ int x,ql,qr,id,op; }ct[maxn*2];
int main(){
	freopen("metro.in","r",stdin);
	freopen("metro.out","w",stdout);
	int id; cin>>id>>n;
	for(int i=1;i<n;i++){
		int x,y; cin>>x>>y;
		Insert(x,y),Insert(y,x);
	}
	Dfs(1,0);
	int m; cin>>m;
	for(int i=1;i<=m;i++){
		int x,y; cin>>x>>y;
		int lca=Get_lca(x,y);
		pos[x].push_back(lca); pos[y].push_back(lca);
		if(dfn[x]>dfn[y]) swap(x,y);
		sw[i]={dfn[x],dfn[y]};
	}
	Dfs2(1,0),Dfs3(1,0);
	int q; cin>>q; int tot=0;
	for(int i=1;i<=q;i++){
		int x,y; cin>>x>>y;
		if(x==y){ ans[i]=0; continue; }
		int lca=Get_lca(x,y); 
		if(dep[x]>dep[y]) swap(x,y);
		if(x==lca){
			ans[i]=Jump_(y,dep[lca]);
			if(dep[f[y][0]]<=dep[lca]) ans[i]++;
			else ans[i]=-1;
		}
		else{
			ans[i]=Jump_(x,dep[lca])+Jump_(y,dep[lca]);
			if(dep[f[x][0]]<=dep[lca]&&dep[f[y][0]]<=dep[lca]){
				ans[i]+=2;
				if(dfn[x]>dfn[y]) swap(x,y);
				ct[++tot]={dfn[x]-1,dfn[y],ed[y],i,-1};
				ct[++tot]={ed[x],dfn[y],ed[y],i,1};
			}
			else ans[i]=-1;
		}
	}
	sort(sw+1,sw+m+1,[](SUY a,SUY b){ return a.x<b.x; });
	sort(ct+1,ct+tot+1,[](QUE a,QUE b){ return a.x<b.x; });
	for(int i=1,j=1;i<=tot;i++){
		while(j<=m&&sw[j].x<=ct[i].x) Add(sw[j++].y);
		ok[ct[i].id]+=ct[i].op*(Ask(ct[i].qr)-Ask(ct[i].ql-1));
	}
	for(int i=1;i<=q;i++){
		if(ans[i]==-1) cout<<"-1\n";
		else{
			if(ok[i]) ans[i]--;
			cout<<ans[i]<<"\n";
		}
	}
	return 0;
}

T4 寿司晚宴

link
思路
首先如果确定了排列 A , B , C A,B,C A,B,C ,那么每个人吃的顺序是固定的。到某一轮三人分别选择 x , y , z x,y,z x,y,z 的寿司时,此时前面的寿司已经被选完,那么如果 C 有贡献当且仅当 x , y x,y x,y 都在 z z z 的后面。所以从后往前推,如果已经确定 c c c 吃了哪些寿司,考虑将不是 c 吃的那部分寿司插入 c 吃的排列中,那么对应的排列数为 ∏ i = 1 n 3 ( 3 × i − 1 ) ( 3 × i − 2 ) \prod\limits_{i=1}^{\frac{n}{3}}(3\times i-1)(3\times i-2) i=13n(3×i1)(3×i2)
那么现在只需要知道 c c c 吃了哪些寿司即可。考虑计数的限制条件。

  • a,b,c 各自吃的寿司不会有重复;
  • a 吃了的寿司不会被 b,c 选择,b,c 同理。
  • a,b,c 每一轮都要吃;

所以说到第 t t t 轮,c 能选择的数为 3 × t − ∣ { a i 1 , a i 2 , … , a i t } ∪ { b i 1 , b i 2 , … , b i t } ∣ 3\times t-|\{a_{i_1},a_{i_2},\dots,a_{i_t}\}\cup \{b_{i_1},b_{i_2},\dots ,b_{i_t}\}| 3×t{ai1,ai2,,ait}{bi1,bi2,,bit} 。设 f t , i , j f_{t,i,j} ft,i,j 表示考虑到第 t t t 轮,a 吃了 i i ib 吃了 j j jc 的选择数。转移为: f t , i , j = ∑ i ′ < i , j ′ < j f t − 1 , i ′ , j ′ × ( 3 × t − ∣ { a i 1 , a i 2 , … , a i t } ∪ { b i 1 , b i 2 , … , b i t } ∣ ) f_{t,i,j}=\sum\limits_{i'\lt i,j'\lt j} f_{t-1,i',j'}\times (3\times t-|\{a_{i_1},a_{i_2},\dots,a_{i_t}\}\cup \{b_{i_1},b_{i_2},\dots ,b_{i_t}\}|) ft,i,j=i<i,j<jft1,i,j×(3×t{ai1,ai2,,ait}{bi1,bi2,,bit})
前缀和优化一下,复杂度为 O ( n 3 ) O(n^3) O(n3)

反思

计数的通常方式是列出限制条件,再根据限制条件解决 。

代码

#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=405,maxt=140,mod=1e9+7;
int a[maxn],b[maxn];
ll f[maxt][maxn][maxn],g[maxn][maxn];
bool vis[maxn],oka[maxn][maxn],okb[maxn][maxn];
int main(){
	freopen("sushi.in","r",stdin);
	freopen("sushi.out","w",stdout);
	int id,t; cin>>id>>t;
	while(t--){
		int n; cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		for(int i=1;i<=n;i++)
			cin>>b[i];
		for(int i=1;i<=n;i++){
			vis[a[i]]=1;
			g[i][0]=i;
			for(int j=1;j<=n;j++){
				g[i][j]=g[i][j-1];
				if(!vis[b[j]]) g[i][j]++;
			}
		}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=i;j++)
				okb[i][b[j]]=1,oka[i][a[j]]=1;
		for(int i=0;i<=n;i++)
			for(int j=0;j<=n;j++)
				f[0][i][j]=1;
		for(int i=1;i<=n/3;i++)
			for(int j=1;j<=i*3;j++)
				for(int k=1;k<=i*3;k++){
					if(!okb[k][a[j]]&&!oka[j][b[k]]) (f[i][j][k]+=f[i-1][j-1][k-1]*(3*i-g[j][k])%mod)%=mod;
					(f[i][j][k]+=((f[i][j-1][k]+f[i][j][k-1])%mod-f[i][j-1][k-1]+mod)%mod)%=mod;
				}
		ll ans=f[n/3][n][n];
		for(int i=1;i<=n/3;i++)
			(ans*=(3*i-1)%mod*(3*i-2)%mod)%=mod;
		cout<<ans<<"\n";
		for(int i=1;i<=n/3;i++)
			for(int j=1;j<=n;j++){
				vis[j]=0;
				for(int k=1;k<=n;k++)
					f[i][j][k]=0,g[j][k]=0,oka[j][k]=okb[j][k]=0;
			}
	}
	return 0;
}
内容概要:本文详细介绍了如何使用Hugging Face Transformers库进行大模型推理,涵盖环境配置、模型下载、缓存管理、离线使用、文本生成、推理pipeline及模型量化技术。重点讲解了使用LLMs进行自回归生成的核心流程,包括token选择策略、生成参数配置(如max_new_tokens、do_sample)、填充方式(左填充的重要性)以及常见陷阱的规避方法。同时深入探讨了多种量化技术(如GPTQ、AWQ、bitsandbytes的4位/8位量化),并通过实例演示了如何加载本地模型、应用聊天模板、结合Flash Attention优化性能,并实现CPU-GPU混合卸载以应对显存不足的问题。; 适合人群:具备Python编程基础和深度学习基础知识,熟悉Transformer架构,从事NLP或大模型相关工作的研究人员、工程师和技术爱好者;尤其适合需要在资源受限环境下部署大模型的开发者。; 使用场景及目标:①掌握Hugging Face Transformers库的核心API,实现大模型的本地加载与高效推理;②理解和避免大模型生成过程中的常见问题(如输出过短、重复生成、填充错误等);③应用量化技术降低大模型内存占用,实现在消费级GPU或CPU上的部署;④构建支持批量处理和多模态任务的推理流水线。; 阅读建议:此资源理论与实践紧密结合,建议读者边阅读边动手实践,复现文中的代码示例,并尝试在不同模型和硬件环境下进行调优。重点关注生成配置、量化参数和设备映射策略,结合具体应用场景灵活调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值