Contest3393 - 2024寒假集训 进阶训练赛 (十一)

问题 A: [蓝桥杯2019初赛]求和

思路:直接暴力,循环的时候判断当前访问的数,是否符合要求,符合就把它算进结果即可。

#include<bits/stdc++.h>
using namespace std;
int check(int k)
{
	while(k)
	{
		int t=k%10;
		if(t==2||t==0||t==1||t==9) return 1;
		k/=10;
	}
	return 0;
}
int main()
{
	int sum=0;
	for(int i=1;i<=2019;i++)
	{
		if(check(i)) sum += i;
	}
	printf("%d",sum);
}

 问题 B: [蓝桥杯2015初赛]星系炸弹

思路:n不超过1000,所以也是暴力即可,将循环天数,设定年月日为y,m,d,从d开始累加,满了往前进位即可,注意区分下平年闰年的二月即可。

#include<bits/stdc++.h>
using namespace std;
int check(int y)
{
	if((y%4==0&&y%100!=0)||(y%400==0)) return 1;
	else return 0;
}
int mon[13][2]={{0,0},{31,31},{28,29},{31,31},{30,30},{31,31},{30,30},{31,31},{31,31},{30,30},{31,31},{30,30},{31,31}};
int main()
{
	int y,m,d,t;
	while(~scanf("%d%d%d%d",&y,&m,&d,&t))
	{
		for(int i=1;i<=t;i++)
		{
			d++;
			int p=check(y);
			if(d>mon[m][p]) d=1,m++;
			if(m>12) m=1,y++;
		}
		printf("%04d-%02d-%02d\n",y,m,d);
	}
}

 问题 C: 查找与给定值最接近的元素

思路:题目给的是一个有序序列,那么我们直接去序列中二分查找给定元素的位置即可,准确来说是去找大于等于给定元素的位置,如果找到的位置恰好就是给定元素,那么这个元素肯定是最近的一个,如果不相等,那么找到的位置的元素大于给定元素,找到位置的左边的元素是所有小于给定元素的元素中最大的,答案就在这两个中产生。

#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int m;
	scanf("%d",&m);
	while(m--)
	{
		int x;
		scanf("%d",&x);
		int l=1,r=n;
		while(l<r)
		{
			int mid=(l+r)/2;
			if(a[mid]>=x) r=mid;
			else l=mid+1;
		}
		int y=a[l-1],z=a[l];
		if(z-x<x-y) printf("%d\n",z);
		else printf("%d\n",y);
	}
}

问题 D: 二分解方程

思路:我们可以判一下单调性,这个等式左边是单调递增的,所以这也是个二分题,不过和上面的整数二分略有不同。 因为浮点数有误差的,所以我们判断是否相等的时候要给一个误差eps=1e-8。另外对于二分出来的结果也要判断一下是否合法,这里如果不合法相差就大了,所以这时候判断误差不用把精度设的太高,否则会误判。

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
double y;
int check(double mid)
{
	double ans=8.0*mid*mid*mid*mid+7.0*mid*mid*mid+2.0*mid*mid+3.0*mid+6.0;
	if(ans-y>=eps) return 1;
	else return 0;
}
int cmp(double mid)
{
	double ans=8.0*mid*mid*mid*mid+7.0*mid*mid*mid+2.0*mid*mid+3.0*mid+6.0;
	if(fabs(ans-y)<=1e-1) return 1;
	else return 0;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%lf",&y);
		double l=0,r=100;
		while(r-l>eps)
		{
			double mid=(l+r)*1.0/2;
			if(check(mid)) r=mid;
			else l=mid;
		}
		if(cmp(l)) printf("%.4lf\n",l);
		else printf("No solution!\n");
	}
}

问题 E: 单词的长度

思路:实际上直接暴力,访问到非空格字符去再开一个循环往后找到当前单词的结尾即可。不过有一点需要特别注意,这里字符串有空格,所以用fgets()来输入,但是fgets会把"\n",所有样例中只有第一个样例结尾有\n,剩下的字串结尾都没有\n,所以需要特判一下最后一个字符是否是\n。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	char s[2000];
	fgets(s,2000,stdin);
	vector<int>q;
	int len=strlen(s);
	if(s[len-1]=='\n') len--;
	for(int i=0;i<len;i++)
	{
		if(s[i]!=' ')
		{
			int d=i;
			int c=0;
			while(d<len&&s[d]!=' ')
			{
				c++;
				d++;
			}
			q.push_back(c);
			i=d;
		}
	}
	for(int i=0;i<q.size();i++)
	{
		if(i) printf(",");
		printf("%d",q[i]);
	}
}

 问题 F: 加密的病历单

思路:因为这三个操作是对整个字串进行操作的,所以它们的顺序实际没有影响。我们依次还原即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	string s;
	cin>>s;
	//大小写还原
	for(int i=0;i<s.size();i++)
	{
		if('A'<=s[i]&&s[i]<='Z')s[i]+=32;
		else s[i]-=32;
	}
	reverse(s.begin(),s.end());//逆转
	for(int i=0;i<s.size();i++)//移动还原
	{
		if('a'<=s[i]&&s[i]<='z') s[i]=(s[i]-'a'+3)%26+'a';
		else s[i]=(s[i]-'A'+3)%26+'A';
	}
	cout<<s;
}

 问题 G: [蓝桥杯2015初赛]三羊献瑞

思路:这题看似很麻烦,但实际上是暴力题,因为每一个字符能代表的数就在0-9之间,那么我们写个多层嵌套的循环就能找到。不过有两点需要注意,不同字符代表的数不能相同,以及“三”和“祥”代表的数不能是0。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	//三羊献瑞祥生辉气
	//a,b,c,d,e,f,g,h
	int flag=0;
	for(int a=1;a<=9;a++)
	{
		for(int b=0;b<=9;b++)
		{	
			if(b==a) continue;
			for(int c=0;c<=9;c++)
			{
				if(c==b||c==a) continue;
				for(int d=0;d<=9;d++)
				{
					if(d==a||d==b||d==c) continue;
					for(int e=1;e<=9;e++)
					{
						if(e==a||e==b||e==c||e==d) continue;
						for(int f=0;f<=9;f++)
						{
							if(f==a||f==b||f==c||f==d||f==e) continue;
							for(int g=0;g<=9;g++)
							{
								if(g==a||g==b||g==c||g==d||g==e||g==f) continue;
								for(int h=0;h<=9;h++)
								{
									if(h==a||h==b||h==c||h==d||h==e||h==f||h==g) continue;
									int x=a*1000+b*100+c*10+d;
									int y=e*1000+d*100+f*10+g;
									int z=a*10000+b*1000+f*100+d*10+h;
									if(x+y==z) 
									{
										flag=1;
										printf("%d%d%d%d",a,b,c,d);
										break;
									}
								}
								if(flag) break;
							}
							if(flag) break;
						}
						if(flag) break;
					}
					if(flag) break;
				}			
				if(flag) break;
			}
			if(flag) break;
		}
		if(flag) break;
	}
}

 问题 H: [蓝桥杯2015初赛]生命之树

思路:这题说要在树上找点集,使得点集中的点可以互达,同时点集中的数的和最大。我最开始看到a,b以为是找树的直径,直接就树形dp了,但是仔细看了才发现这里是要找点集。不过我们可以按照树形dp的思路来分析一下:

我们一这个图为例来看,如果我们想把5和7选进点集,那么2一定要被选。如果我们要找以2为根的点集,那么2肯定要被包含,5如果为正值,那么就可以被算进点集,6,7同理。如果我们要找以1为根的点集,那么肯定也是从下面三棵子树中去找,如果子树的和为正,那么就可以被选入点集,如果子树的和为负,那么就不用选。这里和树形dp找树的直径就有点像了,不过比那个简单一点。这里我们只要是正的子树都可以被算进结果,那么我们只要递归查找就可以找到以每个数为根的点集的最大和,遍历找出最大值即可。

#include<bits/stdc++.h>
using namespace std;
int a[100010],h[100010],e[200010],ne[200010],dp[100010],idx;
int n;
void add(int x,int y)
{
	e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
int dfs(int u,int fa)
{
	int d=0;
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		int t=dfs(j,u);
		if(t>0) d += t;
	}
	dp[u]=max(dp[u],d+a[u]);
	return d+a[u];
}
int main()
{
	memset(h,-1,sizeof h);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++)
	{
		int b,c;
		scanf("%d%d",&b,&c);
		add(b,c);
		add(c,b);
	}
	dfs(1,-1);
	int mx=0;
	for(int i=1;i<=n;i++) mx=max(mx,dp[i]);
	cout<<mx;
}

ps:这块儿写的有点抽象,我贴一个树形dp的博客,大家参考一下(树形dp模型整理-CSDN博客)特别是博客中的第一个题,可以辅助理解这道题的dp过程。

问题 I: Dongdziz与不说阿拉伯语的阿拉伯商人 

思路:这道题显然就是在1e9中找到满足要求的最大的数,那么直接二分即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int A,B,x;
int check(int mid)
{

	int ans=A*mid;
	int c=0;
	while(mid)
	{
		c++;
		mid/=10;
	}
	ans += B*c;
	if(ans<=x) return 1;
	else return 0;
}
signed main()
{
	scanf("%lld%lld%lld",&A,&B,&x);
	int l=0,r=1e9;
	while(l<r)
	{
		int mid=(l+r+1)/2;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l;
}

 问题 J: 回文游戏

思路:这个只有修改一个操作,那么就很简单了,直接双指针遍历,然后对于不同的修改一个即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	string s;
	cin>>s;
	int c=0;
	for(int i=0,j=s.size()-1;i<j;i++,j--)
	{
		if(s[i]!=s[j]) c++;
	}
	printf("%d",c);
}

 问题 K: 2.4.2 Web导航

思路:这道题实际上就是模拟题,定义两个栈,按照要求一步一步操作就好。有一点要注意的就是,操作如果被忽略,那么当前页面是不变的。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	stack<string>b,q;
	string op,s="***###.acm.org/";
	while(cin>>op)
	{
		if(op=="VISIT") 
		{
			b.push(s);
			cin>>s;
			cout<<s<<endl;
			while(q.size()) q.pop();
		}
		else if(op=="BACK")
		{
			if(b.size())
			{
				q.push(s);
				s=b.top();
				b.pop();
				cout<<s<<endl;
			}
			else cout<<"Ignored"<<endl;
		}
		else if(op=="FORWARD")
		{
			if(q.size())
			{
				b.push(s);
				s=q.top();
				q.pop();
				cout<<s<<endl;
			}
			else cout<<"Ignored"<<endl;
		}
		else 
		{
			break;
		}
	}
}

 问题 L: 2.4.3 骑士移动

思路:这里要判断一个点能否到另一个点,在每一个点处它有八种移动的情况,所以想到搜索来解决,又因为这里求的是最小步数,那么直接bfs就可。

#include<bits/stdc++.h>
using namespace std;
int ans=0;
int n,a,b;
int dx[]={1,1,-1,-1,2,-2,2,-2};
int dy[]={2,-2,2,-2,1,1,-1,-1};
int st[400][400];
struct node
{
	int x,y,v;
};
void bfs(int x,int y)
{
	st[x][y]=1;
	queue<node>q;
	q.push({x,y,0});
	while(q.size())
	{
		auto it=q.front();
		int x=it.x,y=it.y,v=it.v;
		q.pop();
		if(x==a&&y==b) 
		{
			ans=v;
			break;
		}
		for(int i=0;i<8;i++)
		{
			int nx=x+dx[i],ny=y+dy[i];
			if(0<=nx&&nx<n&&0<=ny&&ny<n&&!st[nx][ny])
			{
				st[nx][ny]=1;
				q.push({nx,ny,v+1});
			}
		}
	}	
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		int x,y;
		scanf("%d%d%d%d",&x,&y,&a,&b);
		ans=0x3f3f3f3f;
		memset(st,0,sizeof st);
		bfs(x,y);
		cout<<ans<<endl;
	}
}

问题 M: 大食堂

思路:这道题吧,我想了半天都觉得是贪心题,分析出来了一定要队伍最长的和手速最快的阿姨配对,然后就是怎么选择k个同学,这里就卡住了,我一直在找合适的排序,甚至都用上优先队列了,但是显然优先队列去模拟的话会超时。贪心实在贪不明白了,跑去问了黄大佬,大佬说这是个二分题,二分时间判断是否合法就可以了。然后我一写,果然ac了。就是很显然这个时间是有个范围的从0到一个都不删的最大时间,那么就是去这个区间中找最小值,我们可以二分查找,然后判断需要选择的同学的数量是否超过k,进而判断这个时间是否合法,然后就解决了。所以还是得多练练呀!

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010],f[200010];
struct node
{
	int p,s;
}c[200010];
int n,k,sum=0;
int check(int mid)
{
	int d=0;
	for(int i=1;i<=n;i++)
	{
		int p=c[i].p,s=c[i].s;
		int ti=p*s;
		if(ti>mid)
		{
			int del=(ti-mid)/s;
			if(ti-del*s>mid) del++;
			d += del;
		}
		if(d>k) return 0;
	}
	return 1;
}
signed main()
{
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i];
	for(int i=1;i<=n;i++) scanf("%lld",&f[i]);
	if(k>=sum) printf("0\n");
	else
	{
		sort(a+1,a+1+n);
		sort(f+1,f+1+n);
		int mx=0;
		for(int i=1;i<=n;i++)
		{
			c[i]={a[i],f[n-i+1]};
			mx=max(mx,a[i]*f[n-i+1]);
		}
		int l=0,r=mx;
		while(l<r)
		{
			int mid=(l+r)/2;
			if(check(mid)) r=mid;
			else l=mid+1;
		}
		cout<<l;
	}
}

问题 N: 吃菜

思路:这道题很明显是贪心题,那么就要找一个合适的排序,因为时间有限值,那么就想到按照时间从小到大排序,然后就贪不下去了,因为点菜是不花时间的,我可以在最后一秒点一个时间花费特别大但是美味度也特别高的菜,这个菜显然不一定刚好在我们当前已经点过的菜后面一个,所以遍历没办法解决。这里每个菜只能点一次,时间有限值,每个菜有一个耗费时间和一个美味度,那么很容易和01背包联系起来。这样就有思路了,我们还是按照时间从小到大排序,然后我们预处理出来每一个菜后面价值最大的菜的价值。然后在背包容量为t-1的情况下做一遍01背包

定义dp[i][j]为从前i个菜中选,消耗时间不超过j的价值集合,这个值表示最大价值。

状态转移则为:

不选第i个菜:dp[i][j]=dp[i-1][j] 

选第i个菜:dp[i][j]=dp[i-1][j-a]+b

dp[i][j]则为两者的最大值。

 然后我们开始遍历菜品,从dp[i][t-1]+mx[i]中找最大值。mx[i]表示i后面价值最大的菜品是哪个。然后问题就解决了。

#include<bits/stdc++.h>
using namespace std;
struct node
{
	int a,b;
}c[200010];
bool cmp(node x,node y)
{
	return x.a<y.a;
}
int dp[3010][3010];
int mx[3010];
int main()
{
	int n,t;
	scanf("%d%d",&n,&t);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		c[i]={x,y};
	}
	sort(c+1,c+1+n,cmp);
	mx[n]=0;
	for(int i=n-1;i>=1;i--)
	{
		mx[i]=max(mx[i+1],c[i+1].b);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=t-1;j++)
		{
			dp[i][j]=dp[i-1][j];
			if(c[i].a<=j)dp[i][j]=max(dp[i][j],dp[i-1][j-c[i].a]+c[i].b);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,dp[i][t-1]+mx[i]);
	}
	cout<<ans;
}

问题 O: 矩形

思路:如下图,显然给出红圈之后,可以将整个区域的绿色菱形都画出来,那么就是要看哪些哪些红圈可以像这样画在一个区域中。 

显然它们的横纵坐标之间有关系,但是还是很麻烦,这里借鉴了曾大佬之前的博客中的一个实现方法——并查集。(曾大佬的博客),这么来想:我们如果将所有点的纵坐标视为横坐标的父节点,那么上图中就化简成了三个连通块:

然后这三个连通块实际是一个区域中的,那么我们就要将它们也联系起来:

 

这样,整块区域中所有的点的父节点都统一成了r,那么我们如果去遍历统计所有横坐标的父节点,就可以知道在r区域内有多少个x,也即有多少列,遍历纵坐标的父节点,就可以知道在r区间内有多少个y,也即有多少行。区间内总的点数应该是列数*行数,然后再减去已知的点,就是我们想要的结果。我们对每个区间都这么处理即可。另外为了区分横纵坐标,因为它们都是数,可以借用曾大佬的写法,将y散列到M+1-2*M的区间中去,因为我们在处理并查集的时候就要将它们区分,不然并查集就乱了。然后这道题就可以解决了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10,M=1e5+10;
int p[N];
int find(int u)
{
	if(p[u]!=u) p[u]=find(p[u]);
	return p[u];
}
int c1[N],c2[N];
signed main()
{
	int n;
	scanf("%lld",&n);
	for(int i=1;i<N;i++) p[i]=i;
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%lld%lld",&x,&y);
		p[find(x)]=find(y+M);
		
	}
	for(int i=1;i<=M;i++)  c1[find(i)]++;
	for(int i=M+1;i<=2*M;i++) c2[find(i)]++;
	//printf("1\n");
	int sum=0;
	for(int i=1;i<=2*M;i++) sum += c1[i]*c2[i];
	printf("%lld",sum-n);
}
  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值