第十届“图灵杯”NEUQ-ACM程序设计竞赛个人赛 题解

A .有用的算法

题意:

给你一个数组,要你判断是否是单调不降,或者单调不升的

做法:

直接模拟判断即可

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 



void solve(){
	
	int n;
	cin>>n;
	vector<int>a(n),s1(n),s2(n);
	for(int i=0;i<n;i++){
		cin>>a[i];
		s1[i]=s2[i]=a[i];
	}
	sort(s1.begin(),s1.end());
	sort(s2.begin(),s2.end(),greater<int>());
	if(a==s1||a==s2){
		cout<<"erfen is useful!\n";
	}else{
		cout<<"bukeyi\n";
	}

}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

B 平衡数

题意:

给你一个四位数,问你前两位的数位和与后两位的数位和相不相等

做法:

模拟判断

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 



void solve(){
	string s;
	cin>>s;
	int a=(s[0]-'0')+(s[1]-'0');
	int b=(s[2]-'0')+(s[3]-'0');
	cout<<(a==b?"YES\n":"NO\n");
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    //t=1;
    cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

C 三角形

题意:

给你三个点:A(0.0),B(xb,0),C(xc,yc) 构成的三角形

问点P(xp,yp) 是否在三角形内部(当落在三角形边上的时候不算内部)

做法:

分情况讨论:

  • xp>=xb||yp>=yc ,这种情况P点一定落在B 点右侧或者与其重合,或者落在C点上侧或与其重合,这种情况一定是在外部

  • 否则我们讨论:xp<xb && yp<yc 的情况

    xc=xp 一定是在三角形内部

    然后我们发现我们就可以把三角形分成两块区域:0<x<xcxc<x<xb

    我们可以利用相似三角形成对应边比例判断点 P 是否落在直线AC 或者直线BC 的下方即可

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 



void solve(){
	
	int xb,xc,yc;
	cin>>xb>>xc>>yc;
	int xp,yp;
	cin>>xp>>yp;
	if(xp<xb&&yp<yc){
		if(xp==xc)cout<<"yes\n";
		else if(xp<xc){
			if(yp*xc<xp*yc)cout<<"yes\n";
			else cout<<"no\n";
		}else{
			if(yp*(xb-xc)<yc*(xb-xp))cout<<"yes\n";
			else cout<<"no\n";
		}
	}else{
		cout<<"no\n";
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

D 文稿修订

题意:
给你一堆字符串,要你把其中的 NEUQ 全改成 WOW NEUQ

同时你需要统计字符串中所有非全是大写的 NEUQ 的数量

做法:

字符串模拟,stringstream 的应用

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int>PII;


void solve(){

	vector<string>ans;
	string s;
	int cnt=0;
	while(getline(cin,s),s!="#"){
		stringstream ssin(s);
		string ss,res;
		while(ssin>>ss){
			if(ss=="NEUQ"){
				if(res.size())res+=" ";
				res+="WOW NEUQ";
			}else{
				if(res.size())res+=" ";
				res+=ss;
				if(ss.size()==4){
					bool ok=false;
					for(auto &c:ss){
						if(c>='a'&&c<='z')ok=true;
						if(c>='A'&&c<='Z')c+=32;
					}
					if(ss=="neuq"&&ok)cnt++;
				}
			}
		}
		ans.push_back(res);
	}
	cout<<cnt<<'\n';
	for(auto s:ans){
		cout<<s<<'\n';
	}
}


int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

E 减肥计划

题意:

给你 n (1n10)种食物,有 x,y,z,w 四种属性,每种食物都有吃或者不吃两种选择,现在要求你所吃食物的 x<=a,y<=b,z<=c

问你 w 最大是多少

做法:

发现 n 很小,直接用二进制枚举或者 dfs 枚举所有可能,取最大值即可

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 

struct node{
	int x,y,z,w;
}bag[20];

void solve(){
	int n,a,b,c;
	cin>>n>>a>>b>>c;
	for(int i=0;i<n;i++){
		auto &[x,y,z,w]=bag[i];
		cin>>x>>y>>z>>w;
	}
	int ans=0;
	for(int i=0;i<1<<n;i++){
		int sa=0,sb=0,sc=0,res=0;
		for(int j=0;j<n;j++){
			if(i>>j&1){
				auto [x,y,z,w]=bag[j];
				sa+=x,sb+=y,sc+=z,res+=w;
			}
		}
		if(sa<=a&&sb<=b&&sc<=c){
			ans=max(ans,res);
		}
	}
	cout<<ans<<endl;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

F 吃包子

题意:

你有 n 个包子,ai=0 表示素包子,ai=1 表示肉包子,现在你需要选择连续的一段把他吃掉,要求你所吃的素包子数量 m

问你在这种情况下,最多能吃到多少肉包子

做法:

双指针经典题,定义 L ,为所有满足 [L,R] 中素包子数量 mL 中最小的那个 ,我们发现当 R 往右移动的时候,L 也是单调往右走

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 

const int N=1e6+10;
int a[N];

void solve(){
	
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	//two point
	int ans=0;
	for(int i=1,j=1,sum=0;i<=n;i++){
		sum+=a[i]==0;
		while(sum>m){
			sum-=a[j++]==0;
		}
		ans=max(ans,i-j+1-sum);
	}
	cout<<ans<<'\n';
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

G 数字鉴定

题意:

给你 n 个区间,然后 q 次询问,每次给你一个数 x ,问你在 n 个区间的并集中,是否不存在任何 x 的倍数

做法:

观察区间范围 1lr106, 故我们可以使用差分来进行区间求并集

然后差分做完求一遍前缀和得到数组 b ,若 b[i] 不为 0,表示这个数出现过在区间的并集中

然后我们只要利用 (nlogn) 复杂度求出所有1x106 数的倍数 y,当我们发现 b[y] 出现过,就给 x 打上标记表示在区间并中存在他的倍数

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 
typedef pair<int,int>PII;
const int N=1e6+10;
int b[N]; //差分
bool ok[N];

void solve(){
	
	int n,q;
	cin>>n>>q;
	for(int i=1;i<=n;i++){
        int l,r;
		cin>>l>>r;
        b[l]++,b[r+1]--;
	}

	for(int i=1;i<N;i++){
		b[i]+=b[i-1];
	}
	for(int i=1;i<N;i++){
		for(int j=i;j<N;j+=i){
			if(b[j])ok[i]=true;
		}
	}
	while(q--){
		int x;
		cin>>x;
		cout<<(!ok[x]?"YES\n":"NO\n");
	}

}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

H 线性变换

题意:

给你 n,P,K,B,T ,以及长度为 n 的整数序列:a1,a2,..,an

同时你有一个加密值 X ,初始值为 0

你需要进行 T 次操作,每次操作进行如下变换:

  • X=x+ap
  • P=(KP+B)%n

做法:

首先我们发现 T (0T1012)

我们不可能暴力求解,但我们发现这是一个经典的式子:P=(KP+B)%n

由于 % 的存在,这个式子是存在循环节,由于模数 1n106

所以我们可以暴力循环找到这个循环节:

这里给出查找类似上述式子循环节的方法:

const int N=1e6+10;
int vis[N],tp;
int P;
while(!vis[N]){
	vis[P]=++tp;
	P=(P*K+B)%n;
}

我们用一个类似时间戳的想法来找到这一段循环节,我们可以举一个例子:

1,(2,3,4,0,7,5,9),(2,3,4,0,7,5,9).......

我们发现循环节为:(2,3,4,0,7,5,9)

由于我们是记录时间戳,当我们发现 vis[p]!=0,说明这个数以及出现过,我们只需要找到第一次 vis[p] 出现的位置,就可以找到整段循环节

循环节长度就为:tpvis[p]+1

故回到这个题,我们只需要找到循环节,然后我们假设循环节出现的位置是:l,我们只需要计算 1l1 的和,以及一个完整循环节:[l,r] 的和

我们就可以知道在 T 次后,变换的结果:

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 

const int N=1e6+10;
int a[N];
int vis[N]; //找循环节
int v[N]; //记一下对应时间的p

void solve(){
	int n,P,K,B;
	LL T;
	cin>>n>>P>>K>>B>>T;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	LL val=0,tp=0;
	while(!vis[P]){
		vis[P]=++tp;
		val+=a[P];
		v[tp]=P;
		if(T==tp){
			cout<<val<<'\n';
			return ;
		}
		P=(1LL*P*K+B)%n;
	}

	LL res=0;
	int l=vis[P],r=tp;
	for(int i=1;i<l;i++){
		res+=a[v[i]];
	}
	val-=res;
	T-=l-1;
	LL ans=res+val*(T/(r-l+1));
	T%=(r-l+1);
	for(int i=1;i<=T;i++){
		ans+=a[v[l+i-1]];
	}
	cout<<ans<<'\n';
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

I 试题排版

题意:

给你一个整数 m(1m5000),问你从 1m这些数选,凑出和恰好为 m 的方案有几种,每个数可以重复选

做法:

完全背包裸体,凑体积恰好为 m 问题

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 

const int N=5100;
const int mod=998244353;
int f[N];

void solve(){
	
	int m;
	cin>>m;
	f[0]=1;
	for(int i=1;i<=m;i++){
		for(int j=i;j<=m;j++){
			f[j]+=f[j-i];
			f[j]%=mod;
		}
	}

	cout<<f[m]<<'\n';

}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

J QQ群

题意:

你有 n+1 个点,编号从 0n,它们之间存在一些有向边的关系,现在定义如下的点为怨种:

  • 若点 u 在一个环(自环也算)内,则它是一个怨种
  • 编号为 0 的点一开始就是怨种

现在你可以连一条从 0 号点指向任意点的有向边,然后按照这个边走下去,在链上的所有点都将成为怨种

请问图中怨种数量最多是多少?

做法:

由于是有向图,且涉及环,我们可以考虑用 tarjan 求出强连通分量,然后缩点,这样每一个强连通分量内部的点

sz[i]>1 ,那么这个强连通分量内的所有点都一定是怨种

sz[i]=1,但这个点有连向自己的边,即存在自环,那么也算怨种

现在我们缩完点之后,图就成了拓扑图,我们只需要在图上找一条最长链(使得链上的孤立点最多)即可

注意我们每一个强连通分量是孤立点当且仅当,它的sz[i]=1,且它不存在自环

所以我们只要在图上 dp 一遍即可

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 


const int N=1e4+10;
vector<int>g[N],G[N];
int dfn[N],low[N],stk[N],id[N],sz[N];
bool cire[N];
int top,scc,tis;
bool is_stk[N];
int f[N];
bool me[N]; //自环

void tarjan(int u){
	dfn[u]=low[u]=++tis;
	stk[++top]=u,is_stk[u]=true;
	for(auto v:g[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}else if(is_stk[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(dfn[u]==low[u]){
		int y;
		scc++;
		do{
			y=stk[top--];
			is_stk[y]=false;
			id[y]=scc;
            cire[y]|=me[y];
			sz[scc]++;
		}while(y!=u);
	}
}

void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		g[i].push_back(x);
        if(i==x)me[i]=true;
	}
	for(int i=0;i<=n;i++){
		if(!dfn[i])tarjan(i);
	}

	for(int i=0;i<=n;i++){
		for(auto j:g[i]){
			if(id[j]!=id[i]){
				G[id[i]].push_back(id[j]);
			}
		}
	}
	memset(f,-1,sizeof f);
	for(int i=scc;i>=1;i--){
		if(f[i]==-1)f[i]=sz[i]==1&&!cire[i]==1?1:0;
		for(auto j:G[i]){
			f[j]=max(f[j],f[i]+(sz[j]==1&&!cire[j]&&j!=1?1:0));
		}
	}

	int ans=0,res=0;
	for(int i=1;i<=scc;i++){
		if(i!=1&&(sz[i]>1||sz[i]==1&&cire[i]))ans+=sz[i];
		res=max(res,f[i]);
	}
	//cout<<ans<<' '<<res<<endl;
	cout<<ans+res+1<<'\n';

}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

K 跳跃

题意:

给你一个数轴有 n 个点,你从 1 号点开始,每个点都有一个区间 [li,ri],每次你会等概率的从这个区间里面选一个数 x,若i+x>n 游戏结束,否则

你会跳到 i+x的位置,每跳到一个点都会获得 ai 的金币,现在问你最后获得的金币期望是多少?

做法:

期望dp题,我们考虑从终点状态往前推,由于所有 >n 的位置都是游戏结束点,我们可以把这些点都归到点n+1

这样我们就可以定义:dp[i] 表示从 i 跳到 n+1 号点所获得的金币期望

则:dp[n+1]=0

然后我们只要倒推,很容易得到状态转移:

dp[i]=(dp[i+li]+dp[i+li+1]+.....+dp[i+ri])1rili+1+a[i]

我们发现 dp[i+li]+...+dp[i+ri] 就是一个长度为 rili+1 的后缀和,我们可以利用后缀和优化掉这个复杂度,这样我们的 dp 的复杂度就控制到了O(n)

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 

const int N=2e5+10;
const int mod=998244353;

int dp[N]; //表示从i跳到n+1的总金币期望
int a[N],l[N],r[N];
int suf[N]; //后缀和


int MOD(int x){
	return (x%mod+mod)%mod;
}

LL qsm(LL a,LL b){
	LL res=1;
	while(b){
		if(b&1)res=res*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return res;
}

void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>l[i];
	}
	for(int i=1;i<=n;i++){
		cin>>r[i];
	}

	for(int i=n;i>=1;i--){
		LL inv=qsm(r[i]-l[i]+1,mod-2);
		dp[i]=MOD(suf[min(n+1,i+l[i])]-suf[min(n+1,i+r[i]+1)])*inv%mod;
		dp[i]=(dp[i]+a[i])%mod;
		suf[i]=(suf[i+1]+dp[i])%mod;
	}

	cout<<dp[1]<<'\n';

}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

L 我把你背回来的

题意:

给你两张一模一样的图,都有 n 个点 m 条无向边,但来两个图的边权不一样

现在你需要重新给图定义边权,规则如下:

  • 对于两个图分别来说边 uv 若从un 的最短路都经过这条边,则新图边权为 0
  • 若只有一个图满足,则边权为 1
  • 若两个图都不满足,则边权 2

然后问你在新图中从 1 走到 n 的最短路径是多少?

做法:

首先由于是无向图,我们可以求一遍从 n 出发走到各个点的最短路,这样我们就可以建图了:

对于某条边 u>v,我们只要看d1[u], 与 d1[v]+r,以及d2[u]d2[v]+d 的相等关系就可以建图了

然后建完图,跑一次 dijstla 即可

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 
typedef pair<int,int>PII;
const int N=4e5+10;
const int INF=0x3f3f3f3f;
vector<PII>g1[N],g2[N],g3[N];
int d1[N],d2[N],d3[N];
bool st[N];
struct node{
	int a,b,r,d;
}edge[N];


void dijstla(int s,int d[],vector<PII>g[],int sz){
	for(int i=1;i<=sz;i++){
		st[i]=false,d[i]=INF;
	}
	d[s]=0;
	priority_queue<PII,vector<PII>,greater<PII>>heap;
	heap.push({0,s});
	while(heap.size()){
		auto [_,u]=heap.top();
		heap.pop();
		if(st[u])continue;
		st[u]=true;
		for(auto [v,w]:g[u]){
			if(d[v]>d[u]+w){
				d[v]=d[u]+w;
				heap.push({d[v],v});
			}
		}
	}
}

void solve(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		auto &[a,b,r,d]=edge[i];
		cin>>a>>b>>r>>d;
		g1[a].push_back({b,r});
		g1[b].push_back({a,r});
		g2[a].push_back({b,d});
		g2[b].push_back({a,d});
	}
	dijstla(n,d1,g1,n);
	dijstla(n,d2,g2,n);

	for(int i=1;i<=m;i++){
		auto [a,b,r,d]=edge[i];
		if(d1[b]==d1[a]+r&&d2[b]==d2[a]+d){
			g3[b].push_back({a,0});
		}else if(d1[b]==d1[a]+r){
			g3[b].push_back({a,1});
		}else if(d2[b]==d2[a]+d){
			g3[b].push_back({a,1});
		}else{
			g3[b].push_back({a,2});
		}
		swap(a,b);
		if(d1[b]==d1[a]+r&&d2[b]==d2[a]+d){
			g3[b].push_back({a,0});
		}else if(d1[b]==d1[a]+r){
			g3[b].push_back({a,1});
		}else if(d2[b]==d2[a]+d){
			g3[b].push_back({a,1});
		}else{
			g3[b].push_back({a,2});
		}
	}
	dijstla(1,d3,g3,n);
	cout<<d3[n]<<'\n';
}


int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}

M 粉色头发的可爱女孩

题意:

k (1k20) 种标签,每个人都有自己的标签v1,v2...vm,同时拥有自己独一无二的魅力值

现在有 Q 次询问,每次询问给定一些标签:x1,x2,...xm ,你需要找到同时满足这些标签的人中,魅力值最大的那个人的编号,如果没有输出1即可

做法:

首先抽象题意,需要我们找到同时满足拥有 x1,x2,..xm 这些标签的人中魅力值最大的,由于 k 不是很大

我们可以考虑状态压缩 f[1<<k] 来表示,标签的集合情况,然后我想想如何求特定的这些值

首先我们来举一个例子,比如我们现在需要更新 f[{1,4,5}] ,那么我们可以发现大集合是包含小集合

即对于满足 {1,4,5,x.y.z....} 的这些大集合,它们都满足同时含有 1,4,5 所以它们都可以来更新 f[{1,4,5}]

故我们发现它本质是一个递推问题,类似状态压缩dp

f[{1,4,5}]=max(f[{1,4,5}],f[{1,4,5,x,,,,,,,}],f[{1,4,5,y,z...}]...............)

故我们只要从大到小递推即可:

代码:

#include<bits/stdc++.h>
using namespace std;
using LL = long long; 
typedef pair<int,int>PII;

const int N=21;
int f[1<<N],id[1<<N];

void solve(){
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		int x,m;
		cin>>x>>m;
		int state=0;
		for(int j=0;j<m;j++){
			int p;
			cin>>p;
			p--;
			state|=1<<p;
		}
		if(f[state]<x){
			f[state]=x;
			id[state]=i;
		}
	}

	//倒推
	for(int i=(1<<k)-1;i>=1;i--){
		for(int j=0;j<k;j++){
			if(i>>j&1){
				int s=i^(1<<j);
				if(f[s]<f[i]){
					f[s]=f[i];
					id[s]=id[i];
				}
			}
		}
	}

	int q;
	cin>>q;
	while(q--){
		int x;
		cin>>x;
		int state=0;
		for(int i=1;i<=x;i++){
			int p;
			cin>>p;
			p--;
			state|=1<<p;
		}
		if(!id[state]){
			cout<<"OMG!\n";
		}else{
			cout<<id[state]<<"\n";
		}
	}
}


int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cout<<fixed<<setprecision(10);
    
    int t;
    t=1;
    //cin>>t;
    while(t--){
    	solve();
	}
    
	
	return 0;
}
posted @   jackle  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示