2020-6-6模拟赛题解

前言

这篇题解大概是我 8 : 00 8:00 8:00 写的,当时感觉自己 AK \texttt{AK} AK 了。 9 : 00 9:00 9:00 这篇题解已经出炉了,检查了一下代码没发现啥问题,最后一题不会对拍,自闭了,如何生成一棵树啊。。。

正文

T1

题目描述

小x有一个整数,他想将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零。他觉得这个问题太简单,就将其抛给了你。

题目分析

开始的时候就开始开 T 1 T1 T1,直接写完交了

模拟一下就好了

题目代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int main(){
	int n,ans=0;read(n);
	if(n<0){putchar('-');n*=-1;}
	while(n){
		ans=ans*10+n%10;
		n/=10;
	}cout<<ans;
	return 0;
}

T2

题目描述

​ 小x学了编程,但他吃了饭没事干,于是想自己写一个计算器,计算器上有“+”、“-”、“*”、“/”(整除号)的按键,但他写完以后发现他写错了,看着几百行的程序,他心态崩了,不想调,于是让你给他写一个计算器,来处理一堆正整数的加减乘除运算。

题目分析

T 2 T2 T2 是个模拟题,思路大家应该都清楚,先把数字和符号隔离出来,然后先把乘法和除法算好,次数式子就变成了只有加法和减法的式子,这个没有难度吧。

题目代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
vector<pair<int,char> >q;
vector<pair<int,char> >s;
int work1(){
	for(unsigned i=0;i<q.size();i++){
		switch(q[i].second){
			case '+':s.push_back(q[i]);break;
			case '-':s.push_back(q[i]);break;
			case '*':s[s.size()-1].first*=q[i].first;break;//算乘
			case '/':s[s.size()-1].first/=q[i].first;break;//算除
		}
	}
	int ans=0;
	for(unsigned i=0;i<s.size();i++){
		if(s[i].second=='+')ans+=s[i].first;
		else ans-=s[i].first;
	}return ans;
}
int work(){
	string st;
	cin>>st;
	q.push_back(make_pair(0,'+'));//第1个数前面的符合可以看成是+号
	for(unsigned i=0;i<st.size();i++)
		if(isdigit(st[i]))q[q.size()-1].first=q[q.size()-1].first*10+st[i]-48;
		else q.push_back(make_pair(0,st[i]));
	cout<<work1()<<endl;
	return 0;
}
int main(){
	int T;
	read(T);
	while(T--){
		q.clear();s.clear();
		work();
	}
	return 0;
}

T3

题目描述

小x有一张由n行和m列组成的图片。行从上到下从1到n编号,列从左到右从1到m编号。每个单元都涂成黑色或白色。

小x认为这幅画不够有趣。如果一幅画里至少有一个十字架,你会觉得它很有趣。十字由一对数字x和y表示,其中1≤x≤n和1≤y≤m,这样x行中的所有单元格和y列中的所有单元格都涂成黑色。
例如,这些图片中的每个都包含十字架:

以下图像不包含十字架:

你有一把刷子和一罐黑漆,所以你可以使这幅画有趣。每分钟你都可以选择一个白色的单元格,然后把它涂成黑色。问题是你最少需要多少次,才能使图片至少包含一个十字?

题目分析

先枚举行,算行需要多少次涂鸦墙,然后看列。

注意一个小细节,行和列有重合的地方。

题目代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
char ch[1010][1010];
int s[1010];
int work(){
	int n,m,ans=INT_MAX;read(n);read(m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>ch[i][j];
	for(int j=1;j<=m;j++)
		for(int i=1;i<=n;i++)
			if(ch[i][j]=='.')s[j]++;
	for(int i=1;i<=n;i++){
		int sm=0,mx=INT_MAX;
		for(int j=1;j<=m;j++)
			if(ch[i][j]=='.')sm++;
		for(int j=1;j<=m;j++)
			mx=min(mx,s[j]-(ch[i][j]=='.'));//这里!
		ans=min(ans,mx+sm);
	}cout<<ans<<endl;
	return 0;
}
int main(){
	int T;read(T);
	while(T--){
		memset(s,0,sizeof(s));
		work();
	}
	return 0;
}

T4

题目描述

小x自娱自乐,他在黑板上写下了一行共n个数字,分别是1,2k,22k…2(n-1)k。现在,他想先擦去黑板上第1个数字,然后擦去黑板上剩余数字中的第2个数字,再擦去剩余数字中第3个数字……直到小x无法继续擦去。现在你想知道,算法停止后第x个剩余数的值是多少。

题目分析

首先,这个擦数会令我们感到不爽,于是,我们看看会取哪些数字。

假设有 10 10 10 个数: { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \{ 1,2,3,4,5,6,7,8,9,10\} {1,2,3,4,5,6,7,8,9,10}

  • 1 1 1,序列变成 { 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \{ 2,3,4,5,6,7,8,9,10\} {2,3,4,5,6,7,8,9,10}
  • 3 3 3,序列变成 { 2 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \{ 2,4,5,6,7,8,9,10\} {2,4,5,6,7,8,9,10}
  • 5 5 5,序列变成 { 2 , 4 , 6 , 7 , 8 , 9 , 10 } \{ 2,4,6,7,8,9,10\} {2,4,6,7,8,9,10}
  • 7 7 7,序列变成 { 2 , 4 , 6 , 8 , 9 , 10 } \{ 2,4,6,8,9,10\} {2,4,6,8,9,10}
  • 9 9 9,序列变成 { 2 , 4 , 6 , 8 , 10 } \{ 2,4,6,8,10\} {2,4,6,8,10}
  • 无法继续进行,操作结束

此时很明显的规律如下:所有的奇数会被擦掉,所有的偶数会被留下。

再拓展一下,最后剩下的数的个数 = = = 偶数的个数 = = = ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n

这样的话就简单了。

首先判断 − 1 -1 1,直接看 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n 是否 < < < x x x

然后第 x x x 个数,显然等于 2 ( x ∗ 2 − 1 ) k 2^{(x*2-1)k} 2(x21)k

题目代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
void write(ll x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
const int mod=19260817;
ll pw(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1)ans=ans*x%mod;
		y>>=1;
		x=x*x%mod;
	}return ans;
}
int work(){
	ll n,x,k;read(n);read(x);read(k);
	n/=2;
	if(x>n)return puts("-1"),0;
	write(pw(2,(x*2ll-1ll)*k));
	puts("");
	return 0;
}
int main(){
	int T;read(T);
	while(T--){work();}
	return 0;
}

T5

题目描述

小x管理着N 个城市,这些城市之间有N-1条道路将他们连接起来,小x发现任何两个城市都能够直接或者间接通过道路连接。
虽然所有城市是可以互相到达的,但是他担心有恐怖分子破坏这片区域的安定,破坏道路,于是小x又秘密地修建了 M 条小路来连接。
果不其然,最近一个奇怪的组织意欲破坏道路,请你帮小x想想办法,如果城市 A 到城市 B 的直连道路被破坏,从 A 走到 B 的所有路径中,经过小路的距离最少是多少?

题目分析

n n n 个点, n − 1 n-1 n1 条边,整张图连通,这不是树吗?

首先,树有一个性质——砍掉任意的一条边,就不联通。

我们以一张图为例。

  • 橙色的边表示删除的边。
  • 红色的边表示树上的边。
  • 紫色的边表示新加的边。

先说一个结论,如果有解的话,新加的边用且仅用一次

为什么?

首先一条边被删除后,一棵树变成了两棵树,起点和终点必定在不同的树里,所以我们需要一座桥连接两棵树。

那为什么不会使用多条新加的边呢?因为在一棵树内,任意两点均能相互达到。

知道这个就好办了。

我们先 d f s dfs dfs 找连通块,遍历其中一棵子树。

我们再去找这棵子树连向另外一棵子树的新加边,并从中找到边权最小的作为我们的答案。

题目代码

#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
const int MAXN=1e4+10;
int n,m,x[MAXN],y[MAXN],h[MAXN];
vector<int>v[MAXN];
vector<pair<int,int> >e[MAXN];
vector<int>v1;
void dfs(int x,int fa,int z){
	v1.push_back(x);h[x]=1;
	for(auto i:v[x])
		if(i!=fa&&i!=z)dfs(i,x,z);
}
int main(){
	read(n);read(m);
	for(int i=1;i<n;i++){
		read(x[i]),read(y[i]);
		v[x[i]].push_back(y[i]);
		v[y[i]].push_back(x[i]);
	}
	for(int i=1;i<=m;i++){
		int x,y,z;read(x);read(y);read(z);
		e[x].push_back(make_pair(y,z));
		e[y].push_back(make_pair(x,z));
	}
	for(int i=1;i<n;i++){
		v1.clear();memset(h,0,sizeof(h));
		dfs(x[i],0,y[i]);
		int ans=INT_MAX;
		for(auto j:v1)
			for(auto k:e[j])
				if(!h[k.first])ans=min(ans,k.second);
		if(ans==INT_MAX)puts("-1");
		else cout<<ans<<endl;
	}
	return 0;
}

时间复杂度

这个代码刚写完时,我吓了一跳,这不是 O ( n 2 m ) O(n^2m) O(n2m) 吗?

过了一会儿,我想了想,发现这其实是 O ( n m + n 2 ) O(nm+n^2) O(nm+n2) 的,而且跑不满。

为什么?

我们看 35 ∼ 37 35\sim 37 3537 行代码,这里并不是 O ( n m ) O(nm) O(nm) 的,而是 O ( m ) O(m) O(m) 的,因为新加的一条边最多被访问 2 2 2 次( 2 2 2 个端点各访问一次),时间复杂度是忽略常数的,并且这里是真的跑不满

但愿这题不 T L E TLE TLE (要 T L E TLE TLE 也只会因为那个 m e m s e t memset memset 可能 T T T),不 W A WA WA(没道理 W A WA WA),不 R E RE RE(没资格 R E RE RE),不 U K E UKE UKE(没可能 U K E UKE UKE),不 M L E MLE MLE v e c t o r vector vector 帮我定着呢),不 C E CE CE(这个网站开 c++11,我试过)。(所以不可能不 A C AC AC !但愿吧。。。)

后记

由于这篇题解是比赛的时候写的,所以分数暂时还不知道,但愿能做对的都做对,最好能 A K AK AK

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值