2025西华acm选拔赛

比赛链接:https://ac.nowcoder.com/acm/contest/103786#description

评价:

感觉后面3题还是蛮有难度的(至少对于我来说)

其它感觉还行,但是有几题被卡了(悲

A

void solve(){
	int n;cin>>n;
	int k=n/2;
	priority_queue<int>q;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int x;cin>>x;
			q.push(x);
		}
	}
	int sum=0;
	for(int i=1;i<=3;i++){
		sum+=q.top();q.pop();
	}
	if(sum>=k){
		cout<<"YES"<<endl;
	}else{
		cout<<"NO"<<endl;
	}
}

B

逆天蓝桥杯题目
主要每次循环都要减1就行

C

有关乘法逆元的题目,我们知道 p/q mod p 是无法直接计算的

然而 p/q mod p = p * inv(q,p)

即模意义下除以一个数就等价于乘这个数的乘法逆元

乘法逆元有如下性质:

  • 1 0无乘法逆元,对于一个整数,其乘法逆元大小为[1,p-1]
  • 2 一个整数有关于这个模数的逆元当且仅当它与模数互质(题目模数大多数质数,所以基本都成立吧
  • 3 定义ax mod p = 1 mod p (其中x是a关于p的乘法逆元)

几种求解方法

  • 1 快速幂,通过费马小引理可得到逆元表达式 inv(a,p) = a^(p-2)
  • 2 exgcd

这些知道题目就非常简单了,注意求快速幂后都要取模,基本上能取模就取模

int quickpow(ll a,int b,int p){
	int res=1;
	while(b){
		if(b&1) res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}


void solve(){
	int n,p,q;cin>>n>>p>>q;
	int k=2*p*q;
	int d=10000;

	k=quickpow(k,n,mod)%mod;
    d=quickpow(d,n,mod)%mod;

	cout<<(k%mod*quickpow(d,mod-2,mod)%mod)%mod<<endl;

}

D

求欧拉回路

欧拉路:只经过所有边一次的道路(一笔画)
欧拉回路:只经过所有边一次且回到起点的道路

如果图是连通

  • 1 无向图:
    若最多存在两个奇点(度数为奇的点),则存在欧拉路
    如果两个奇点,欧拉路一定从一个奇点开始结束于另一个奇点
    如果奇点不存在,则存在欧拉回路,(即度数都为偶数)从任意点出发,一定会回到该点

  • 2 有向图:
    最多有两个点入度不等于出度,且一个出度-入度=1(起点),另一个入度-出度=1(终点)
    所有顶点入度=出度,则存在欧拉回路

(以上摘自紫书)

无向图求欧拉回路板子

void euler(int u){
	for(int i=1;i<=n;i++){
		if(g[u][i]&&!vis[u][i]){
			vis[i][u]=vis[u][i]=1;
			euler(i);
		}
	}
	res.pb(u);
}

于是问题就是先判断再建图然后跑板子

int g[5000][5000];
int indegree[maxn];
int vis[5000][5000];
int p;
vector<int>ans;
int N;
void euler(int u){
    for(int v=1;v<=N;v++){
        if(g[u][v]&&!vis[u][v]){
            vis[u][v]=vis[v][u]=1;
            euler(v);
        }
    }
    ans.pb(u);
}
void solve(){
    int n;
    cin>>n;
     
    int cnt=n+1;
    for(int i=1;i<=(n+1)*(n+1);i++){
        if(i%(n+1)!=0){
            g[i][i+1]=1;
            g[i+1][i]=1;
            indegree[i]++;
            indegree[i+1]++;
        }
         
        if(i+n<=(n+1)*(n+1)&&!g[i][i+n]&&i!=cnt&&(i+n)%(n+1)!=0){
            g[i][i+n]=1;
            g[i+n][i]=1;
            indegree[i]++;
            indegree[i+n]++;
        }
         
        if(i<=n*(n+1)){
            g[i][i+n+1]=1;
            g[i+n+1][i]=1;
            indegree[i]++;
            indegree[i+n+1]++;
        }
         
        if(i%(n+1)==0){
            cnt+=n+1;
            cnt--;
        }
    }
    N=(n+1)*(n+1);
    bool ok=true;
    for(int i=1;i<=(n+1)*(n+1);i++){
//      cout<<indegree[i]<<' ';
        if(indegree[i]%2!=0){
            ok=false;
        }
    }
     
    if(ok){
        cout<<"Yes"<<endl;
        euler(1);
        for(int i=0;i<ans.size();i++){
            cout<<ans[i]<<' ';
        }
    }else{
        cout<<"No"<<endl;
    }
}

E

记dp[i]为以数字i为开头的最长每次递增1的序列长度
dp[i] -> max(dp[i+1]+1,dp[i])

void solve(){
    int n;cin>>n;
    vector<int>sc(n+1);   vector<int>dp(n+5,0);
     
    dp[0]=0;
    for(int i=1;i<=n;i++){
        cin>>sc[i];
    }
    for(int i=n;i>=1;i--){
        dp[sc[i]]=max(dp[sc[i]],dp[sc[i]+1]+1);
    }
 
    int q;cin>>q;
    while(q--){
        int x;cin>>x;
        cout<<dp[x]<<endl;
    }
}

F

题意: 给定一颗树,树上每个节点都是一个字符,具体来说是给定字符串的字符s[i](i为节点编号)

有m次查询,每次查询给定两个节点编号u,v,
询问u,v及其简单路径之间的字符构成的可重新排列的字符串是否能构成回文串

思路:

由于u-v字符串可重排,所以想看它是否回文,只要看是否只有一种字符数量是奇数便可以
但是我们并不可以搜索遍历u-v,因为铁定t

所以有一个技巧,将26个字符映射为26个不同的二进制数

具体来说,a=001,b=010,c=100..以此类推

这样一来,回文条件变为u-v之间字符(映射后)异或后二进制数至多一位是1

树上异或和?洛谷上有一题叫做最大路径异或和,异曲同工

只要计算根节点到每个节点的异或值,那么u-v之间异或值:w[u-v]=w[u]w[v](lca(u,v)映射的值)

为什么要用lca?

因为异或的值不是在路径(边)上,而是在点上,直接w[u]^w[v]会漏掉lca这个点(lca为根节点时)或者多异或了一次lca(普通情况)(这是本题的重点)

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define pb push_back
#define endl "\n"
#define fi first
#define se second
//#pragma GCC optimize(3)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 lll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const ll llmax=LLONG_MAX;
const int maxn=2e5+5;
const int mod=1e9+7;
vector<int>e[maxn];
int fa[maxn],dep[maxn],son[maxn],sz[maxn];
map<char,int>mp;
//  存u的父节点,存u的深度,存u的重儿子,存以u为根的子树的节点数 
int top[maxn];
// 存u所在重链的顶点 
int a[maxn];
void dfs1(int u,int father){
	fa[u]=father,dep[u]=dep[father]+1,sz[u]=1;
	for(int v:e[u]){
		if(v==father)continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v]){
			son[u]=v;
		}
	}
}
void dfs2(int u,int t){
	top[u]=t;
	if(!son[u])return;//遇到叶子节点返回 
	dfs2(son[u],t);//向下搜重儿子
	for(int v:e[u]){
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);//搜轻儿子 
	} 
}
int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
string s;
int vis[maxn];
void dfs(int u,int cur){
	vis[u]=1;
	a[u]=cur;
	for(auto v:e[u]){
		if(!vis[v]){
			dfs(v,cur^mp[s[v-1]]);
		}
	}
}
int get(int u,int v){
	if(__builtin_popcount(a[u]^a[v]^mp[s[lca(u,v)]])<=1)return 1;
	return 0;
}
void solve(){
	int n;cin>>n;
	for(int i=0;i<26;i++){
		mp['a'+i]=1<<i;
	}
	for(int i=1;i<=n-1;i++){
		int x,y;cin>>x>>y;
		e[x].pb(y);
		e[y].pb(x);
	}
	cin>>s;
	int q;cin>>q;
	dfs1(1,0);
	dfs2(1,1);
	dfs(1,0);
	while(q--){
		int u,v;cin>>u>>v;
		if(get(u,v)){
			cout<<"YES"<<endl;
		}else{
			cout<<"NO"<<endl;
		}
	}
}

signed main()
{
	ios::sync_with_stdio(false),cin.tie(0);
	int T=1;
	
	while(T--){
	solve();
	}
	
	return 0;
}


G

输出1~n即可

H

一些数的gcd<=其中的最小值,一些数的按位与<=其中的最小值

所以等式左边 <=2b(min)
等式右边 2
b(max)

若想使其相等,需要b(min)=b(max)
所以用set统计有多少个数就行

void solve(){
	int n;cin>>n;
	set<int>st;
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		st.insert(x);
	}
	cout<<st.size()<<endl;
}

I

总方案数:m^n
能够不使其他人攻击的方案数:m
答案为上边两个做差
注意:取模后相减要+mod再取模
防止最后答案是个负数
比如:m=5,n=2,mod=7
5 ^ 2 % 7= 3
5%7=5
3-5=-2
此时应该+7
(-2+7)%7=5

int quickpow(ll a,int b,int p){
	int res=1;
	while(b){
		if(b&1) res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}
void solve(){
	int m,n;cin>>m>>n;
	cout<<(quickpow(m,n,mod)%mod-m%mod+mod)%mod<<endl;

}

J

树链剖分+线段树跑板子

注意的是题目n,m最大1e6,所以直接静态数组会导致MLE

所以把线段树的静态tr数组改成vector
等到n确定后,再resize为4*n+1e5

感觉有点极限,但是能过~

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define pb push_back
#define endl "\n"
#define fi first
#define se second
//#pragma GCC optimize(3)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 lll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const ll llmax=LLONG_MAX;
const int maxn=1e6;
const int mod=1e9+7;


vector<int>w;
vector<int>e[maxn];
int fa[maxn],dep[maxn],sz[maxn],son[maxn];
//每个节点的父亲,深度,以u为根的树大小,重儿子 
int top[maxn],id[maxn],nw[maxn],cnt;
//存u所在重链的顶点,u剖分后的新编号,新编号在树中所对应节点的权值
void dfs1(int u,int father){
	fa[u]=father;dep[u]=dep[father]+1;sz[u]=1;
	
	for(int v:e[u]){
		if(v==father)continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v])son[u]=v;
	}
}   

void dfs2(int u,int t){
	top[u]=t,id[u]=++cnt,nw[cnt]=w[u];
	if(!son[u])return;
	dfs2(son[u],t);
	
	for(int v:e[u]){
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);//轻儿子 
	}
}

struct node{
	int l,r;
	int sum,add;
};
vector<node>tr;
void pushup(int u);
void pushdown(int p);
void build(int p,int l,int r);


//查询:求树从x到y节点最短路径上所有节点的值之和  
ll query(int u,int l,int r);
ll query_path(int u,int v);



//修改:将树从x到y节点最短路径所有节点加上k

void update(int u,int l,int r,int k);

void update_path(int u,int v,int k);




//修改/查询:树上以u为根的子树的点权和 
void update_tree(int u,int k);
ll query_tree(int u);



void solve(){
	int n,m,r;cin>>n>>m>>r;
	w.pb(0);
	tr.resize(4*n);
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		w.pb(x);
	}

	for(int i=1;i<=n-1;i++){
		int u,v;cin>>u>>v;
		e[u].pb(v);
		e[v].pb(u);
	}
	dfs1(r,0);
	dfs2(r,r);
	
	for(int i=1;i<=n;i++){
        e[i].clear();
        e[i].shrink_to_fit(); // 实际释放vector内存
    }
    
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int opt;cin>>opt;
		int x,y,z;
		if(opt==1){
			cin>>x>>z;
			update_path(x,x,z);
		}else if(opt==3){
			cin>>x>>y;
			cout<<query_path(x,y)<<endl;
		}else if(opt==2){
			cin>>x>>z;
			update_tree(x,z);
		}
	}
}	

signed main()
{
	ios::sync_with_stdio(false),cin.tie(0);
	int T=1;
	
	while(T--){
	solve();
	}
	
	return 0;
}
void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int p){
	if(tr[p].add){
		int debt=tr[p].add;
		tr[p<<1].sum+=debt*(tr[p<<1].r-tr[p<<1].l+1);
		tr[p<<1|1].sum+=debt*(tr[p<<1|1].r-tr[p<<1|1].l+1);
		tr[p<<1].add+=debt;
		tr[p<<1|1].add+=debt;
		tr[p].add=0;
	}
}
void build(int p,int l,int r){
	tr[p]=node{l,r,nw[l],0};
	if(l==r)return;
	int m=l+r>>1;
	build(p<<1,l,m);build(p<<1|1,m+1,r);
	pushup(p);
}


//查询:求树从x到y节点最短路径上所有节点的值之和  
ll query(int u,int l,int r){//线段树 
	if(l<=tr[u].l&&tr[u].r<=r)return tr[u].sum;
	pushdown(u);
	int m=tr[u].l+tr[u].r>>1;
	ll res=0;
	if(l<=m)res+=query(u<<1,l,r);
	if(r>m)res+=query(u<<1|1,l,r);
	return res;
}
ll query_path(int u,int v){//原树 
	ll res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res+=query(1,id[top[u]],id[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	res+=query(1,id[v],id[u]);
	return res;
} 



//修改:将树从x到y节点最短路径所有节点加上k

void update(int u,int l,int r,int k){//线段树 
	if(l<=tr[u].l&&r>=tr[u].r){
		tr[u].add+=k;
		tr[u].sum+=k*(tr[u].r-tr[u].l+1);
		return;
	}
	pushdown(u);
	int m=tr[u].l+tr[u].r>>1;
	if(l<=m)update(u<<1,l,r,k);
	if(r>m)update(u<<1|1,l,r,k);
	pushup(u);
} 

void update_path(int u,int v,int k){//原树 
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		update(1,id[top[u]],id[u],k);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	update(1,id[v],id[u],k);
}




//修改/查询:树上以u为根的子树的点权和 
void update_tree(int u,int k){//线段树 
	update(1,id[u],id[u]+sz[u]-1,k);
}
ll query_tree(int u){//原树 
	return query(1,id[u],id[u]+sz[u]-1);
}


K

贪心,发现只需闪避一回合叠一层层数,然后在boss攻击回合使用特殊攻击能够将收益最大化
然而也有可能boss只攻击一回合,所以我们需要在 所有回合都普通攻击的情况 和 使用特殊攻击(先闪避再特攻)的情况 取max
这是本题的重点

void solve(){
	int n,a,b;cin>>n>>a>>b;
	string s;cin>>s;
	int ans=0;
	int gunshi=0,cnt=0;
	for(int i=0;i<n;i++){
		if(s[i]=='K'){
			if(gunshi)cnt++;
			ans+=a;
		}else{
			if(gunshi==0){
				gunshi++;
			}else{
				ans+=2*b;
			}
		}
	}
	if(gunshi&&s[n-1]=='K'){
		ans+=b-a;
	}
	cout<<max(ans,a*n)<<endl;
}

L

利用倍增的思想,时间复杂度O(nlogk)
(这题我也不太懂)
思路是构建一个跳表f[i][j],指的是第i个位置的数跳到i+(1<<j)-1的位置的下标
一开始f[i][0]即是从p[i]跳到p[i]+1的位置的最小距离
f[i][1]=f[f[i][0]][0]
相当于 ( 1->2 )->3 => 1->3
f[i][2]=f[f[i][1]][1]
相当于 1->3->5 => 1->5?
大概是这样把)
然后跳的步数t=k-1
每次能跳就跳,相当于拆分成2的幂次相加

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define pb push_back
#define endl "\n"
#define fi first
#define se second
//#pragma GCC optimize(3)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 lll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const ll llmax=LLONG_MAX;
const int maxn=2e5+5;
const int mod=1e9+7;
int p[maxn];
int j[maxn];
int f[maxn][25];
int n,k;
int ans=inf;
int N;
int find(int x){
	int t=k-1;
	if(t<0)return -1;
	for(int i=N;i>=0;i--){
	  if(t >= (1<<i)){
            t -= (1<<i);
            if(f[x][i] == 0) return -1; // 中途遇到无效位置
            x = f[x][i];
        }
	}
    return x;
}

void solve(){
	cin>>n>>k;
	N=log2(n)+1;
	for(int i=1;i<=n;i++){
		cin>>p[i];
	}
	if(n==1){
		cout<<0<<endl;return;
	}
	for(int i=n;i>=1;i--){
		j[p[i]]=i;
		f[i][0]=j[p[i]+1];
	}
	
	for(int j=1;j<=N;j++){
		for(int i=1;i<=n;i++){
			if(i+(1<<j)-1>n)break;
			f[i][j]=f[f[i][j-1]][j-1];
		}
	}
	
	for(int i=1;i<=n;i++){
		int j=find(i);
		if(j>0){
			ans=min(ans,j-i);
		}
	}
	if(ans!=inf)cout<<ans<<endl;else cout<<-1<<endl;
}

signed main()
{
	ios::sync_with_stdio(false),cin.tie(0);
	int T=1;
	
	while(T--){
	solve();
	}
	
	return 0;
}


M

模拟即可

int g[1005][1005];
void solve(){
	int n,m;cin>>n>>m;
	int res=0;
	rep(i,1,m){
		int x,y,z;cin>>x>>y>>z;
		g[x][y]=z;
	}
	int ans=0;
	bool ok=true;
	pii now={0,0};
	rep(i,1,n){
		int x=now.fi,y=now.se;
		char opt;cin>>opt;
		if(opt=='D'){
			ok=false;
			g[x][y]+=ans/2;
			ans=0;
		}else if(opt=='R'){
			ok=true;
			now={0,0};
		}else if(opt=='M'){
			int a,b;cin>>a>>b;
			now={a,b};
			if(ok){
				ans+=g[a][b];
				g[a][b]=0;
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(g[i][j])res+=g[i][j];
		}
	}
	cout<<ans<<' '<<res<<endl;
}
posted @ 2025-03-14 22:30  Marinaco  阅读(13)  评论(0)    收藏  举报
//雪花飘落效果