上海市计算机学会竞赛平台2023年5月月赛

上海市计算机学会竞赛平台2023年5月月赛

比赛链接

T1 升序排列(二)

升序排列(二)
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定一个长度为 n 的排列,每次操作,你可以选择其中一个数字,并将它放到开头或结尾,请问最少多少次操作,才能使该排列变为升序?
输入格式
输入第一行,一个正整数 n
输入第二行,n 个正整数, p 1 , p 2 , . . . , p n p_1,p_2,...,p_n p1,p2,...,pn
表示一个排列
输出格式
输出共一行,一个正整数,表示最少操作次数
数据范围
对于30%的数据, 1 ≤ n ≤ 10 1\leq n \leq 10 1n10
对于60%的数据, 1 ≤ n ≤ 1 0 3 1\leq n \leq 10^3 1n103
对于100%的数据, 1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1n105
样例数据
输入:
7
3 5 4 6 1 7 2
输出:
4
说明:
第一步把4挪到开头:4 3 5 6 1 7 2
第二步把3挪到开头:3 4 5 6 1 7 2
第三步把2挪到开头:2 3 4 5 6 1 7
第四步把1挪到开头:1 2 3 4 5 6 7

首先,补充一下题意:该长度为n的序列里的数字 1 ≤ p i ≤ n 1 \leq p_i \leq n 1pin且必定不重复。
“正难则反”,既然无法思考有哪些数字应当调换位置,那么就想有哪些数字可以留下来。
因为数字只可以添加到序列尾或序列首,所以留下的数字必须连续。而改变位置的数字需要 n n n 次可以完成( n n n 为数字个数,可以证明)。
这样,只需要枚举连续的数。为减少时间复杂度,以空间换时间,可以开一个数组记录该数的位置,这样就可以O(1)访问。然后枚举连续的数,打擂台求最值。

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN=100010;
int n,b[MAXN],id[MAXN],a[MAXN],maxn;

signed main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		b[a[i]]=i;
	}
	id[1]=1;//记录以位置i结尾的连续个数。
	for(int i=2;i<=n;++i){
		if(b[a[i]-1]<i)id[i]=id[b[a[i]-1]]+1;//如果这个数字能和之前的某个连续数列连上
		else id[i]=1;
		maxn=max(maxn,id[i]);
	}//很类似递推
	cout<<n-maxn<<endl;
	return 0;
}

T2 集体舞

集体舞
内存限制: 256 Mb时间限制: 1000 ms
题目描述
n 名演员围成一个圆圈跳集体舞,每名演员都有一个位置。演员与位置的编号都是从 1 开始到 n 结束。最初,每个演员都站在自己对应的位置编号上。
在舞蹈进行的过程中,陆续会出现若干变换,这些变换分成两种类型:
第一种变换称为旋转,用字母 r 表示。在这种变换下:
原先 1 号位上的演员将移动去 2 号位
原先 2 号位上的演员将移动去 3 号位
… 以此类推
原先 n 号位上的演员将移动去 1 号位
第二种变换称为翻转,用字母 f 表示。在这种变换下:
1 号位上的演员与 n 号位上的演员对换
2 号位上的演员与 n−1 号位上的演员对换
… 以此类推
特别注意,若 n 是奇数,则在翻转变换下,(n+1)/2 号位置上的演员位置不变。
给定一个字符序列,表示集体舞依次经历的变换种类,输出每个位置在舞蹈结束后的演员编号。
输入格式
第一行:单个整数 n
第二行:一个字符串 s 表示变换序列,保证只由 r 与 f 组成。
输出格式
共 n 行:在第 i 行有一个整数,表示舞蹈结束时,第 i 个位置上的演员编号。
数据范围
设 |s| 表示输入字符串的长度
30% 的数据, 1 ≤ n ≤ 3000 , 1 ≤ ∣ s ∣ ≤ 3000 1\leq n \leq 3000,1\leq |s| \leq 3000 1n30001s3000
60% 的数据, 1 ≤ n ≤ 100 , 000 , 1 ≤ ∣ s ∣ ≤ 100 , 000 1\leq n \leq 100,000,1\leq |s| \leq 100,000 1n100,0001s100,000
100% 的数据, 1 ≤ n ≤ 500 , 000 , 1 ≤ ∣ s ∣ ≤ 500 , 000 1\leq n \leq 500,000,1\leq |s| \leq 500,000 1n500,0001s500,000
样例数据
输入:
4
rfr
输出:
4
3
2
1
说明:
(1,2,3,4)–r–>(4,1,2,3)
(4,1,2,3)–f–>(3,2,1,4)
(3,2,1,4)–r–>(4,3,2,1)

这题一定要想象一个环(而不是一个数组或者说线),f代表轴对称翻转,r是旋转。两个f可以抵消,但r呢?
当序列是顺序时(即f%2==0时),r是顺时针旋转,否则是逆时针旋转。所以一顺一逆可以抵消。另外注意,n个同向旋转可以抵消。

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n,f,r;
char s[500000];

signed main(){
	scanf("%lld\n%s",&n,&s);
	int pos=strlen(s);
	for(int i=0;i<pos;++i){
		if(s[i]=='f')++f;
		else if(f%2==0)--r;
		else ++r;
	}
	r%=n;
	if(f%2==0)
		for(int i=0;i<n;++i)
			printf("%lld\n",(i+n+r)%n+1);
	else for(int i=0;i<n;++i)
			printf("%lld\n",(n-i-1+n+r)%n+1);
	return 0;
}

T3 最小倍数

最小倍数
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定一个数字 x ,小爱想知道在所有仅由 0,1 构成的十进制数字中,最小的 x 的倍数是多少?
输入格式
输入第一行,一个正整数 q 表示询问次数
接下来 q 行,每行一个正整数 x i x_i xi,表示第 i 次询问的数字
输出格式
输出共 q 行, 其中第 i 行表示第 i 个询问的答案
数据范围
对于30%的数据, 1 ≤ x ≤ 20 1\leq x \leq 20 1x20
对于60%的数据, 1 ≤ x ≤ 1 0 3 1\leq x \leq 10^3 1x103
对于100%的数据, 1 ≤ x ≤ 1 0 4 , 1 ≤ q ≤ 1 0 3 1\leq x \leq 10^4, 1 \leq q \leq 10^3 1x104,1q103
样例数据
输入:
3
5
27
527
输出:
10
1101111111
1101110111

这题满分算法的思路是宽搜+余数定理(因为超long long了,同时避免高精度)。从1开始,挨个在原数后面加0或1,用余数定理算余数,同时用余数作剪枝(因为题目要求最小倍数,所以就不用搜这个数,同时深搜就不可行),一直搜到余数为0为止,但是为了输出,用于宽搜的队列还要有父节点的编号与本身的选择(0或1)。然后才可以逆序输出。

#include <bits/stdc++.h>
#define int long long
#define inf 10010
using namespace std;

int vh[inf],q,x;

struct nod{
	int id,pos,num;
}a[inf];

void print(int id){
	if(id!=1)print(a[id].id);
	cout<<a[id].pos;
}

void bfs(int x){
	int head=1,tail=2;
	a[1].num=1%x,a[1].pos=1;
	while(head<tail){
		if(a[head].num==0){
			print(head);
			return;
		}
		if(!vh[a[head].num*10%x]){
			vh[a[head].num*10%x]=1;
			a[tail].num=a[head].num*10%x;
			a[tail].id=head;
			a[tail++].pos=0;
		}
		if(!vh[(a[head].num*10+1)%x]){
			vh[(a[head].num*10+1)%x]=1;
			a[tail].num=(a[head].num*10+1)%x;
			a[tail].id=head;
			a[tail++].pos=1;
		}
		++head;
	}
}

signed main(){
	scanf("%lld",&q);
	while(q--){
		scanf("%lld",&x);
		memset(vh,0,sizeof(vh));
		bfs(x);
		cout<<endl;
	}
	return 0;
}

T4 路径问题(三)

路径问题(三)
内存限制: 256 Mb时间限制: 1000 ms
题目描述
有一个国家共 n 个城市,编号 1 ~ n ,城市之间由 n−1 条双向道路连接,任意城市之间均连通。
现有 m 名游客前往该国家游玩,第 i 名游客的旅游路线起点为 s i s_i si,终点为 t i t_i ti,且沿途不会经过重复道路。已知每名游客在经过某一城市时都会打卡(包括起点城市和终点城市),请你帮忙求出每个城市被打卡的次数。
输入格式
输入第一行,两个正整数 n,m
接下来 n−1 行,每行两个正整数 u i , v i u_i,v_i ui,vi,表示第 i 条道路连接城市 u i , v i u_i,v_i ui,vi
最后 m 行,每行两个正整数 s i , t i s_i,t_i si,ti,表示第 i 名游客旅游路线的起点和终点
输出格式
输出共一行, n个整数,其中第 i 个整数表示 i 号城市被打卡的数量
数据范围
对于30%的数据, 1 ≤ n , m ≤ 100 1\leq n,m \leq 100 1n,m100
对于60%的数据, 1 ≤ n , m ≤ 1 0 3 1\leq n,m \leq 10^3 1n,m103
对于100%的数据, 1 ≤ n , m ≤ 1 0 5 1\leq n,m \leq 10^5 1n,m105, 1 ≤ u i , v i , s i , t i ≤ n 1 \leq u_i,v_i,s_i,t_i \leq n 1ui,vi,si,tin
样例数据
输入:
4 3
1 2
2 3
4 2
1 4
3 4
2 4
输出:
1 3 1 3

这题先存图,然后dfs搜索目的地路径,然后“打卡”,思路简单。但是只可以过30分。

#include <bits/stdc++.h>
#define inf 1010
using namespace std;

int vh[inf],a[inf][inf],n,m,ans[inf],path[inf],y,x;
bool f;

void readp(){
	cin>>n>>m;
	for(int i=1;i<n;++i){
		scanf("%lld%lld",&x,&y);
		a[x][y]=a[y][x]=1;
	}
}

void dfs(int k,int step){
	if(f)return;
	if(k==y){
		for(int i=1;i<=step;++i)
			++ans[path[i]];
		f=1;
		return;
	}
	for(int i=1;i<=n;++i)
		if(a[i][k]&&!vh[i]){
			vh[i]=1;
			path[step+1]=i;
			dfs(i,step+1);
		}
}

signed main(){
	readp();
	while(m--){
		scanf("%lld%lld",&x,&y);
		memset(vh,0,sizeof(vh));
		f=0;
		vh[x]=1,path[1]=x;
		dfs(x,1);
	}
	for(int i=1;i<=n;++i)
		printf("%lld ",ans[i]);
	return 0;
}

为了避免无效的判断,改用vector提高效率,只存邻接节点。

#include <bits/stdc++.h>
#define inf 100010
using namespace std;

int vh[inf],n,m,ans[inf],path[inf],y,x;
vector <int> a[inf];
bool f;

void readp(){
	cin>>n>>m;
	for(int i=1;i<n;++i){
		scanf("%d%d",&x,&y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
}

void dfs(int k,int step){
	if(f)return;
	if(k==y){
		for(int i=1;i<=step;++i)
			++ans[path[i]];
		f=1;
		return;
	}
	int pos=a[k].size();
	for(int i=0;i<pos;++i)
		if(vh[a[k][i]]==0){
			vh[a[k][i]]=1;
			path[step+1]=a[k][i];
			dfs(a[k][i],step+1);
		}
}

signed main(){
	readp();
	while(m--){
		scanf("%d%d",&x,&y);
		memset(vh,0,sizeof(vh));
		f=0;
		vh[x]=1,path[1]=x;
		dfs(x,1);
	}
	for(int i=1;i<=n;++i)
		printf("%d ",ans[i]);
	return 0;
}

可得60分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaoGuohao2022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值