周师2021年寒假半月赛1

补题地址

http://acm.zknu.edu.cn/contest.php?cid=1118

Problem A 猜密码

思路

把答案分为以零结尾的数字和不以零结尾的数字两种,开一个记录答案的数组ans[i][0/1],表示i位数以0或不以0结尾的方案数。
显然ans[1][0]=1,ans[1][1]=9。
如果以0结尾,该i位数字串的下一位可以是任何数字。如果以1结尾,该i位数字串的下一位可以是除了0以外的任何数字。
易知ans[i][0]=ans[i-1][1],ans[i][1]=(ans[i][0]+ans[i][1])*9。
求解过程中要对1e9+7取余以防溢出。

代码

#include 
const int p=1e9+7;
int n,T,a[10000001],ans[1000001][2];
int main()
{
     
	while (scanf("%d",&a[++T])!=EOF)
		if (a[T]>n) n=a[T];
	T--;
	ans[1][0]=1;
	ans[1][1]=9;
	for (int i=2;i<=n;++i) 
	{
     
		ans[i][0]=ans[i-1][1];
		ans[i][1]=(ans[i-1][0]*9ll%p+ans[i-1][1]*9ll%p)%p;
	} //9ll是longlong类型的九,相当于进行了一次强制类型转化。 
	for (int i=1;i<=T;++i) printf("%d\n",(ans[a[i]][0]+ans[a[i]][1])%p);
	return 0;
} 

Problem B 打弹珠

思路

N个弹珠完全相同,且默认发生完全弹性碰撞,碰撞后两弹珠的速度和方向均交换。我们并不对弹珠们的标号与末位置的对应关系加以要求,所以可以视为弹珠之间不会发生碰撞。
计算所有弹珠的末状态,按要求排序后输出。

代码

#include 
#include 
using namespace std;
struct asdf{
     
	int x,y,z;
}a[101];
bool operator < (asdf a,asdf b)
{
     
	if (a.x==b.x)
		if (a.y==b.y) return a.z<b.z;
		else return a.y<b.y;
	return a.x<b.x;
}
int main()
{
     
	int n,t,vx,vy,vz;
	scanf("%d%d",&n,&t);
	for (int i=1;i<=n;++i)
	{
     
		scanf("%d%d%d%d%d%d",&a[i].x,&a[i].y,&a[i].z,&vx,&vy,&vz);
		a[i].x+=vx*t;
		a[i].y+=vy*t;
		a[i].z+=vz*t;
	}
	sort(a+1,a+1+n);
	for (int i=1;i<=n;++i) printf("%d %d %d\n",a[i].x,a[i].y,a[i].z);
}

Problem C 积水坑

思路

每一列对答案的贡献为min(它左边最大数,它右边最大数)-它自己的高度。
比如下图第三列的答案,为min(3,2)-1=2-1=1。
在这里插入图片描述

求出每一列的答案我们就可以累加求出总的答案。
另外开两个数组,正着遍历即可求出每个元素前面最大的元素, l[i]=max(a[i-1],l[i-1]),同理我们逆序遍历即可求出每个元素后面最大的元素r[i]=max(a[i+1],r[i+1]),最后统计答案。
我的代码中的l和r数组包含了自己,显然可以发现对最后的答案并没有影响。

代码

#include 
#include 
#define min(a,b) (a>b?b:a) 
int n,a[1005],l[1005],r[1005];
int qwq()
{
     
	memset(l,0,sizeof(l));
	memset(r,0,sizeof(r));
	memset(a,0,sizeof(a));
	int ans=0;
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
	{
     
		scanf("%d",&a[i]);
		if (a[i]>l[i-1]) l[i]=a[i];
		else l[i]=l[i-1];
	}
	for (int i=n;i>=1;--i)
	{
     
		if (a[i]>r[i+1]) r[i]=a[i];
		else r[i]=r[i+1];
		if (min(l[i],r[i])-a[i]>0) ans+=min(l[i],r[i])-a[i];
	}
	return ans;
}
int main()
{
     
	int T;
	for (scanf("%d",&T);T--;) printf("%d\n",qwq());
	return 0;
}

Problem D 计算题

思路

题目背景中说道:冰茶几无法正确解决任何一个问题。
所以对于每组数据输出0。

代码

#include 
int main()
{
     
	int T;
	for (scanf("%d",&T);T--;) printf("0\n");
}

Problem E 假期计划

思路

将所有活动按结束时间升序排序,遍历所有活动,如果可以参加活动i,则必然参加,ans++。输出ans。

下面对于正确性进行感性认知证明。
排序后,我们遍历整个数组,此时对于可以被选择的两个活动(即发生时间与其他已经选择了的活动没有交集),它们的发生时间必然为以下三种情况:

  1. 活动一开始、活动一结束、活动二开始、活动二结束。
  2. 活动一开始、活动二开始、活动一结束、活动二结束。
  3. 活动二开始、活动一开始、活动一结束、活动二结束。

对于第一种情况,两个活动没有交集,不影响彼此是否被选择。
对于第二种情况,由于两个活动都可以被选择,所以活动二开始之前一定没有活动正在举行,我们可以忽略掉活动一举行到活动二举行这一段时间,变为第三种情况。
对于第三种情况,选择活动二必然更优(对其他活动的选择影响更小)。
综上,我们的选择策略没有问题。

代码

#include 
#include 
struct asdf{
     
	int a,b;
}a[1000001];
int n,x,ans;
int cmp(asdf a,asdf b)
{
     
	return a.b<b.b;
}
int main()
{
     
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d %d",&a[i].a,&a[i].b);
	std::sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;++i)
		if (x<a[i].a) ans++,x=a[i].b;
	printf("%d",ans);
}

Problem F 输水管道

思路

数据范围较小,我们可以用二维数组存储整张地图,进行dfs。
因为这个dfs很裸我不知道该怎么bb,可以看代码把数据代入手玩一下。

代码

#include 
#define max(a,b) (a>b?a:b)
int n,m,q,a[1005][1005],v[1001][1001][2],ans[5001],t,maxx;
void dfs(int x,int y,int z)
{
     
	if (a[x][y]==1)
	{
     
		v[x][y][0]=v[x][y][1]=t;
		if (x>1&&!v[x-1][y][0]) dfs(x-1,y,0);
		if (x<n&&!v[x+1][y][0]) dfs(x+1,y,0);
		if (y>1&&!v[x][y-1][1]) dfs(x,y-1,1);
		if (y<m&&!v[x][y+1][1]) dfs(x,y+1,1);
	}
	else
	{
     
		v[x][y][z]=t;
		if (z==0)
		{
     
			if (x>1&&!v[x-1][y][0]) dfs(x-1,y,0);
			if (x<n&&!v[x+1][y][0]) dfs(x+1,y,0);	
		}
		else 
		{
     
			if (y>1&&!v[x][y-1][1]) dfs(x,y-1,1);
			if (y<m&&!v[x][y+1][1]) dfs(x,y+1,1);
		}
	}
}
int main()
{
     
	scanf("%d%d%d",&n,&m,&q);
	for (int i=1;i<=q;++i)
	{
     
		int x,y;
		scanf("%d%d",&x,&y);
		a[x][y]=1;
	}
	for (int i=1;i<=n;++i)
		for (int j=1;j<=m;++j)
			if (a[i][j]==1)
			{
     
				if (v[i][j][0]) ans[v[i][j][0]]++;
				else ans[++t]=1,dfs(i,j,2);
			}
	for (int i=1;i<=t;++i) maxx=max(maxx,ans[i]);
	printf("%d",maxx);
}

Problem G 睡眠时间

思路

睡眠时间小于一天,所以年月日可以忽略掉(
直接做差就好。
这个题wa了应该去面壁。

代码

#include 
int main()
{
     
	int n,ay,am,ad,ah,amin,as,by,bm,bd,bh,bmin,bs;
	for (scanf("%d",&n);n--;)
	{
     
		scanf("%d%d%d%d%d%d%d%d%d%d%d%d",&ay,&am,&ad,&ah,&amin,&as,&by,&bm,&bd,&bh,&bmin,&bs);
		if (bs<as) bs=bs+60-as,bmin--;
		else bs-=as;
		if (bmin<amin) bmin=bmin+60-amin,bh--;
		else bmin-=amin;
		if (bh<ah) bh=bh+24-ah,bd--;
		else bh-=ah;
		printf("%d %d %d\n",bh,bmin,bs);
	}
}

Problem H 跳荷叶

思路

尽可能跳到跳跃范围内最远的荷叶上。
将荷叶按距离岸边从小到大排序,排好序后从第一片荷叶开始遍历所有荷叶,记录青蛙跳了ans步时距离岸边的最大距离m。如果青蛙无法不经过当前荷叶就跳到下一片荷叶,ans++,青蛙跳到当前荷叶上。如果青蛙无法跳到当前荷叶上,输出无解结束程序。
最后判断青蛙能否跳上对岸,若能,输出ans+1。

代码

#include 
#include 
using namespace std;
int n,a[100001],x,m,ans;
int main()
{
     
	scanf("%d%d",&x,&n);
	n++;
	for (int i=1;i<=n;++i) scanf("%d",&a[i]);
	sort(a+1,a+n);
	for (int i=1;i<n;++i)
	{
     
		if (a[i]>m+x) return printf("-1\n"),0;
		if (a[i+1]>m+x) m=a[i],ans++;
	}
	if (a[n]>m+x) printf("-1");
	else printf("%d",ans+1);
	return 0; 
}

Problem I 最美子数组

思路

动态规划。
给定数组x[i]。
状态:开一个数组f[i],记录前i个数的最美后缀和。
转移:f[i]=max(f[i-1]+a,a)
初状态:f[1]=x[1] //个人喜欢数组从1开始用
答案:max{f[i]}

然而正解是贪心。
用一个sum记录当前前缀和,遍历整个数组,如果前缀和sum变成了负数,当前数就不需要加上前面的数了(还不如只选它自己),这时把sum置为0,再继续累加。
这个问题通常被称为最大子段和问题。

代码

#include 
int main()
{
     
	int n;
	long long sum=-2147483648,ans=-2147483648,a;
	for (scanf("%d",&n);n--;)
	{
     
		scanf("%lld",&a);
		if (sum<0) sum=a;
		else sum+=a;
		if (ans<sum) ans=sum;
	}
	printf("%lld",ans);
}

你可能感兴趣的:(从零开始的ACM生活,算法,动态规划,贪心,比赛,题解)