2020-4-2模拟赛题解

T1

这道题直接纯模拟就好了,我们可以发现题意是这样的:

  • 如果这 4 4 4 个数有数字重复,答案为 Need fix.
  • 如果这 4 4 4 个数没有数字重复,答案为 4 4 4 个数中的最小数的编号
#include 
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 a[10],ans=INT_MIN,ai;
int main(){
	for(int i=1;i<=4;i++)read(a[i]);
	for(int i=1;i+1<=4;i++)
		for(int j=i+1;j<=4;j++)
			if(a[i]==a[j]){//有重复
				cout<<"Need fix.";//输出
				return 0;
			}
	for(int i=1;i<=4;i++){
		if(a[i]>ans){
			ans=a[i];//答案
			ai=i;//编号
		}
	}cout<<ai;
	return 0;
}

T2

这是一道找最小值的问题,我们把读入数据代入那个公式再对所有答案去一个 min ⁡ \min min 就行了,需要注意的是 ∞ \infty 设成 INT_MAX 会错,需要设成 LONG_LONG_MAX

#include 
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 n,ai;
double x,ans=LONG_LONG_MAX;//注意!
int main(){
	cin>>n>>x;
	for(int i=1;i<=n;i++){
		double c,t,e;
		cin>>c>>t>>e;
		double xx=c+t*x/e;
		if(xx<ans){
			ans=xx;
			ai=i;
		}
	}cout<<ai;
	return 0;
}

T3

这道题用递归做非常方便,直接模拟就行了。

i i i 1 1 1

进的位数 = = = n ÷ x n \div x n÷x

这一位的数 = = = n   m o d   x n \bmod x nmodx

#include 
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;
}
ll n;
void work(ll n,ll x){
	if(n==0)return;
	work(n/x,x+1);
	cout<<n%x<<" ";
}
int main(){
	read(n);
	if(n==0)cout<<0;//这里需要特判
	work(n,1);
	return 0;
}

T4

这道题又可以用递归做。

我们发现培养之后就是斐波那契数,斐波那契数有下面几个性质:

  • 所有自然数都可以用不重复的斐波那契数组成。

  • 斐波那契数增长是指数级的。

所以可以直接枚举天数。

我们可以写一个 c h e c k check check 函数

bool check(ll x,ll n){
	if(x<=0)return false;
	if(f[x]==n)return true;
	if(f[x]>n)return check(x-1,n);//这次培养的话病体数量就超过了想要培养的总数了,所有不培养
	return check(x-2,n-f[x]);//否则培养
}

其他就比较简单了:

#include 
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;
}
ll n,X=2,f[10010];
bool check(ll x,ll n){
	if(x<=0)return false;
	if(f[x]==n)return true;
	if(f[x]>n)return check(x-1,n);//这次培养的话病体数量就超过了想要培养的总数了,所有不培养
	return check(x-2,n-f[x]);//否则培养
}
void print(ll x,ll n){
	if(f[x]==n){
		cout<<X-x+1<<" ";
		return;
	}
	if(f[x]>n)print(x-1,n);
	else{
		cout<<X-x+1<<" ";
		print(x-2,n-f[x]);
	}
}
int main(){
	read(n);
	if(n==0){
		cout<<endl<<0;//边界
		return 0;
	}
	if(n==1){
		cout<<1<<endl<<1;//边界
		return 0;
	}
	f[0]=1;f[1]=1;//初始化
	while(1){
		f[X]=f[X-1]+f[X-2];
		if(check(X,n)){
			print(X,n);
			cout<<endl<<X;
			return 0;
		}X++;
	}
	return 0;
}

T5

首先我们可以枚举全排列。

#include 
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 n,d,a[20],f[20],ans;
int gcd(int x,int y){
	if(y==0)return x;
	return gcd(y,x%y);
}
int check(){
	ll ans=0;
	for(int i=1;i<=n;i++)ans+=abs(a[f[i]]-a[f[i-1]])*abs(a[f[i]]-a[f[i-1]]);
	return ans;
}
int main(){
	read(n);
	for(int i=1;i<=n;i++)read(a[i]),f[i]=i;
	do{
		ans=max(ans,check());
	}while(next_permutation(f+1,f+n+1));
	cout<<ans;
	return 0;
}

这样可以获得 50 50 50 分的好成绩

我们还可以状压 d p dp dp

#include 
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;
}
ll n,d,a[15],dp[1<<20][20],ans;
int main(){
	read(n);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)dp[(1<<(i-1))][i]=a[i]*a[i];
	for(int i=1;i<(1<<n);i++){
		for(int j=1;j<=n;j++){
			if(i&(1<<(j-1))){
				for(int k=1;k<=n;k++){
					if(i&(1<<(k-1))&&k!=j){
						dp[i][j]=max(dp[i][j],dp[i^(1<<(j-1))][k]+(a[j]-a[k])*(a[j]-a[k]));
						ans=max(ans,dp[i][j]);
					}
				}
			}
		}
	}cout<<ans;
	return 0;
}

上面 2 2 2 个代码是我考试的时候对拍用的

下面说正解了。

我们发现最优解一定是放一个最大数,再一个最小数,再一个剩下的最大数,再一下剩下的最小数……

证明的话,我觉得挺显然的qwq

Q:那你还写对拍?!

#include 
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;
}
ll n,a[110],ans;
vector<ll>v;
int main(){
	read(n);
	for(int i=1;i<=n;i++)read(a[i]);
	a[++n]=0;
	sort(a+1,a+n+1);
	ll l=1,r=n;
	while(l<=r){ 
		if(l==r)v.push_back(a[l]);
		else{
			v.push_back(a[l]);
			v.push_back(a[r]);
		}l++;
		r--;
	}
	for(int i=1;i<v.size();i++)ans=ans+abs(v[i]-v[i-1])*abs(v[i]-v[i-1]);
	cout<<ans;
	return 0;
}

T6

这其实是一个背包 d p dp dp,挺裸的,主要是输出方案,这倒不算啥,关键是输出方案要符合字典序

这里借用 a p o c r y p h a l \textcolor{black}{a} \textcolor{red}{pocryphal} apocryphal 大佬的方法,把序列翻转,这样就可以了。

#include 
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;
}
ll n,x,t[5010],a[5010],f[5010][5010],xx[5010][5010],flag;
void print(ll x,ll y){
	if(x==0)return;
	if(xx[x][y]){
		if(flag==0)cout<<n-x+1,flag=1;
		else cout<<" "<<n-x+1;
	}print(x-1,y-xx[x][y]*t[x]);
}
int main(){
	read(n);read(x);
	for(int i=1;i<=n;i++)read(t[i]);
	for(int i=1;i<=n;i++)read(a[i]);
	reverse(t+1,t+n+1);
	reverse(a+1,a+n+1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=x;j++){
			f[i][j]=f[i-1][j];
			xx[i][j]=0;
			if(j>=t[i]){
				if(f[i-1][j-t[i]]+a[i]>=f[i][j]){
					xx[i][j]=1;
					f[i][j]=f[i-1][j-t[i]]+a[i];
				}
			}
		}
	}
	if(f[n][x]==0){
		puts("Develop failed.");
		return 0;
	}print(n,x);
	return 0;
}

T7

首先给出我不剪枝的代码:

#include 
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 n,d,a[20],f[20],ans;
int gcd(int x,int y){
	if(y==0)return x;
	return gcd(y,x%y);
}
bool check(){
	for(int i=1;i<n;i++)
		if(gcd(a[f[i]],a[f[i+1]])!=1||abs(a[f[i]]-a[f[i+1]])>d)return false;
	return true;
}
int main(){
	read(n);read(d);
	for(int i=1;i<=n;i++)read(a[i]),f[i]=i;
	do{
		if(check())	ans++;
	}while(next_permutation(f+1,f+n+1));
	cout<<ans;
	return 0;
}

只有 40 40 40 q w q qwq qwq。。。

再给出减枝后的代码:

#include 
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 n,d,ans,h[25],f[25],a[25];
vector<int>v[25];
int gcd(int x,int y){
	if(x%y==0)return y;
	return gcd(y,x%y);
}
void dfs(int x){
	if(x==n+1){
		ans++;
		return;
	}
	for(int i:v[f[x-1]]){
		if(!h[i]&&abs(a[f[x-1]]-a[i])<=d){
			h[i]=1;
			f[x]=i;
			dfs(x+1);
			h[i]=0;
		}
	}
}
int main(){
	read(n);read(d);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<n;i++)
		for(int j=i+1;j<=n;j++)
			if(gcd(a[i],a[j])==1)v[i].push_back(j),v[j].push_back(i);
	for(int i=1;i<=n;i++){
		h[i]=1;
		f[1]=i;
		dfs(2);
		h[i]=0;
	}cout<<ans;
	return 0;
}

这时,你拿到了 90 90 90 分的好成绩。

之后想到 100 100 100 分的话,我是听了 a p o c r y p h a l \textcolor{black}{a} \textcolor{red}{pocryphal} apocryphal 大佬的建议,写了复杂度有保证的装压 d p dp dp,时间复杂度 O ( 2 n n 2 ) O(2^n n^2) O(2nn2),比搜索的 O ( n ! ) O(n!) O(n!) 确实强了不少

十分好写,但需要一定的装压 d p dp dp 的基础

#include 
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 n,d,a[15],dp[1<<20][20],ans;
int gcd(int x,int y){
	if(x%y==0)return y;
	return gcd(y,x%y);
}
int main(){
	read(n);read(d);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)dp[(1<<(i-1))][i]=1;
	for(int i=1;i<(1<<n);i++){
		for(int j=1;j<=n;j++){
			if(i&(1<<(j-1))){
				for(int k=1;k<=n;k++){
					if(i&(1<<(k-1))&&k!=j){
						if(abs(a[j]-a[k])<=d&&gcd(a[j],a[k])==1){
							dp[i][j]+=dp[i^(1<<(j-1))][k];
						}
					}
				}
			}if(i==(1<<n)-1)ans+=dp[i][j];
		}
	}cout<<ans;
	return 0;
}

Q:我卡常搜索过了!

A:那是数据水了。

PS:这道题的数据水了,不信你给你的程序测下面的这组数据

12 2
1 1 1 1 1 1 1 1 1 1 1 1

你会发现标程过不了,因为 12 ! = 479001600 ‬ 12!=479001600‬ 12!=479001600 远超 1 1 1 亿。

T8

这道题的 I d e a Idea Idea 不错,可是问题有点多啊

  1. 样例,毒瘤的出题人手抖了一下,把第 4 4 4 行的那个 1 1 1 打到了第 3 3 3 行,变成了

    3 2
    2 4
    21
    1
    2 4
    1 1 10 10 
    

    使本来就比较难懂的题目更加难懂,这里把我坑了好久。。。

  2. 数据范围,毒瘤的出题人手抖了一下,吧 1 0 5 10^5 105打成了 105 105 105。。。

  3. 数据,毒瘤的出题人没有考虑到读入数据中第 i i i 层医疗机构能管辖到的最大编号应该是单调递增的

  4. 标程,毒瘤的出题人没有设置边界条件 s 1 = 1 s_1=1 s1=1

说思路:

这题应该是一眼二分的,考虑如何 c h e c k check check

考虑贪心

  • 如果当前医护人员还可以再去往下一个基层医疗机构就让他去
  • 如果当前医护人员去不了下一个基层医疗机构就新派一个医护人员去

现在要做的就是快速算出两点的距离,很明显这道题的医疗机构的连接是树形的,也就是说所有医疗机构组成看一棵树,求树上任意 2 2 2 点路径,很容易想到 L C A LCA LCA,如果你不会 L C A LCA LCA 的话,可以先学一下

bool check(int x){
	int k=0,m=1,a=1;
	for(int i=s[D-1]+1;i<=s[D];i++){
		int ans=dist(a,i);//路径长度
		if(k+ans<=x)k+=ans;//我们的贪心思想
		else{
			m++;//新派一个医护人员
			k=d[i];//这个是根节点到i的距离
			if(k>x)return false;//如果的医护人员在不了x的时间内去不了i号点。
		}a=i;//更新
	}return m<=::m;//医护人员数量需要小于等于现有医护人员的数量
}

关于 d i s t dist dist

void dfs(int x,int fa){
	::fa[x][0]=fa;
	dep[x]=dep[fa]+1;
	d[x]+=d[fa];
	for(int i=1;i<=20;i++)::fa[x][i]=::fa[::fa[x][i-1]][i-1];
	if(x>s[D-1])return;
	for(int i=l[x];i<=r[x];i++)dfs(i,x);
}
int lca(int a,int b){
    if(dep[a]>dep[b])swap(a,b);
    for(int i=20;i>=0;i--)
        if(dep[a]<=dep[b]-(1<<i))b=fa[b][i];
    if(a==b)return a;
    for(int i=20;i>=0;i--)
        if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
    return fa[a][0];
}
int dist(int a,int b){
	int k=lca(a,b);
	return d[a]+d[b]-2*d[k];
}

很普通的倍增 L C A LCA LCA

现在就放一下总代码吧:

#include 
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 N=1e5+10;
int sz[N],l[N],r[N],D,m,x,fa[N][25],dep[N],s[N],d[N];
void dfs(int x,int fa){
	::fa[x][0]=fa;
	dep[x]=dep[fa]+1;
	d[x]+=d[fa];
	for(int i=1;i<=20;i++)::fa[x][i]=::fa[::fa[x][i-1]][i-1];
	if(x>s[D-1])return;
	for(int i=l[x];i<=r[x];i++)dfs(i,x);
}
int lca(int a,int b){
    if(dep[a]>dep[b])swap(a,b);
    for(int i=20;i>=0;i--)
        if(dep[a]<=dep[b]-(1<<i))b=fa[b][i];
    if(a==b)return a;
    for(int i=20;i>=0;i--)
        if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
    return fa[a][0];
}//倍增LCA
int dist(int a,int b){
	int k=lca(a,b);
	return d[a]+d[b]-2*d[k];//距离
}
bool check(int x){
	int k=0,m=1,a=1;
	for(int i=s[D-1]+1;i<=s[D];i++){
		int ans=dist(a,i);//路径长度
		if(k+ans<=x)k+=ans;//我们的贪心思想
		else{
			m++;//新派一个医护人员
			k=d[i];//这个是根节点到i的距离
			if(k>x)return false;//如果的医护人员在不了x的时间内去不了i号点。
		}a=i;//更新
	}return m<=::m;//医护人员数量需要小于等于现有医护人员的数量
}
int main(){
	read(D);read(m);
	l[0]=1,r[0]=1;
	s[1]=sz[1]=1;
	for(int i=2;i<=D;i++)read(sz[i]),s[i]=s[i-1]+sz[i];
	for(int i=1;i<D;i++){
		for(int j=1;j<=sz[i];j++){
			read(r[s[i-1]+j]);
			r[s[i-1]+j]+=s[i];
			l[s[i-1]+j]=r[s[i-1]+j-1]+1;
		}
		for(int j=1;j<=sz[i];j++)
			for(int k=l[s[i-1]+j];k<=r[s[i-1]+j];k++)
				read(d[k]);//连接k好点和他父亲节点的边的长度
	}
	dfs(1,0);//LCA的预处理啦
	int l=0,r=INT_MAX;//二分
	while(l+1<r){
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid;
	}cout<<r;//输出
	return 0;
}

真的的佩服 a p o c r y p h a l \textcolor{black}{a} \textcolor{red}{pocryphal} apocryphal 大佬,如果不是最后一题数据的问题的话,就直接切掉了

你可能感兴趣的:(2020-4-2模拟赛题解)