AtCoder Beginner Contest 351(ABC 351) A-F题解

前排提示:最五彩斑斓的一集

比赛链接

在这里插入图片描述
A . A. A.

传送门

题意:求前9个数减去后8个数再+1的答案

c o d e : code: code:

#include <cstdio>
int a[15];
int b[10];
int main()
{
	int asum=0;
	int bsum=0;
	for(int i=1;i<=9;i++) scanf("%d",&a[i]),asum+=a[i];
	for(int i=1;i<=8;i++) scanf("%d",&b[i]),bsum+=b[i];
	printf("%d",asum-bsum+1);
}

B . B. B.

传送门

题意:给两个大小一样的矩阵,问两个矩阵哪个位置的字符不一样,数据保证答案存在且唯一

c o d e : code: code:

#include <string>
#include <iostream>
#include <cstdio>
using namespace std;
int n;
char a[105][105],b[105][105]; 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) cin>>a[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) cin>>b[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(a[i][j]!=b[i][j])
			{
				printf("%d %d",i,j);
				return 0;
			}
		}
}

C . C. C.

传送门

题意:在这里插入图片描述

分析:模拟题意即可,注意 2 A ∗ 2 2^A*2 2A2就是指数加1即可。

c o d e : code: code:

#include <cstdio>
const int N = 2e5+5;
int n;
int a[N];
int b[N];
int head;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
		b[++head]=a[i];
		if(head==1) continue;
		while(b[head]==b[head-1])
		{
			b[head]=0;
			head--;
			b[head]++;
		}
	}
	printf("%d",head);
}

D . D. D.

传送门

题意:给一个 H ∗ W H*W HW的二维矩阵,由.和#两种字符构成,如果 ′ . ′ '.' .的上下左右没有#,那么这个位置可以移动,求矩阵任意 ′ . ′ '.' .位置出发能搜索到的最多的 ′ . ′ '.' .的个数
数据范围 H , W < = 1000 H,W <=1000 H,W<=1000

分析:搜索+剪枝,重点是让每一个点只被搜索一次,从而保证复杂度为 O ( H ∗ W ) O(H*W) O(HW),具体实现可以把通常判断是否搜过的vis数组由0和1变为0和搜索编号,让能被同一个点出发搜到的所有点变成连通块,实现剪枝(说的抽象,可以看看代码), b f s bfs bfs或者 d f s dfs dfs都可以

c o d e : code: code:

#include <cstdio>
#include <queue>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1005;
int h,w;
char mp[N][N];
bool b[N][N];
int dx[5]={0,0,1,-1};
int dy[5]={1,-1,0,0};
int ans,cnt;
struct node{
	int x;
	int y;
};
queue <node> q[N*N];
int num;
int vis[N][N];
void bfs(int x,int y)
{
	num++;
	q[num].push((node){x,y});
	vis[x][y]=x*h+y;
	while(!q[num].empty())
	{
		int fx=q[num].front().x;
		int fy=q[num].front().y;
		q[num].pop();
		for(int i=0;i<4;i++)
		{
			int nx=fx+dx[i];
			int ny=fy+dy[i];
			if(nx<1||nx>h||ny<1||ny>w) continue ;
			if(vis[nx][ny]==x*h+y) continue;
			if(mp[nx][ny]=='.')
			{
				vis[nx][ny]=x*h+y;
				cnt++;
			}
			if(b[nx][ny]) q[num].push((node){nx,ny}); 
		}
	} 
int main()
{
	scanf("%d%d",&h,&w);
	for(int i=1;i<=h;i++)
		for(int j=1;j<=w;j++)
		{
			cin>>mp[i][j];
		}
	for(int i=1;i<=h;i++)
		for(int j=1;j<=w;j++)
		{
			if(mp[i][j]=='#') continue ;
			int flag=1;
			for(int k=0;k<4;k++)
			{
				int nx=i+dx[k];
				int ny=j+dy[k];
				if(nx<1||nx>h||ny<1||ny>w) continue;
				if(mp[nx][ny]=='#')
				{
					flag=0;
					break;
				}
			}
			if(flag) b[i][j]=1;
		}
	ans=1;
	for(int i=1;i<=h;i++)
		for(int j=1;j<=w;j++)
		{
			if(!b[i][j]) continue ;
			if(vis[i][j]) continue;//这里是剪枝的关键,只把没搜过的点进行搜索
			cnt=1;
			bfs(i,j);
			ans=max(ans,cnt);
		}
	printf("%d",ans);
}

E . E. E.

传送门

题意:给 N N N个点的坐标, P i Pi Pi每次移动可以从 ( X i , Y i ) (Xi,Yi) (Xi,Yi) ( X i + 1 , Y i + 1 ) , ( X i + 1 , Y i − 1 ) , ( X i − 1 , Y i + 1 ) , ( X i − 1 , Y i − 1 ) (Xi+1,Yi+1),(Xi+1,Yi-1),(Xi-1,Yi+1),(Xi-1,Yi-1) (Xi+1,Yi+1),(Xi+1,Yi1),(Xi1,Yi+1),(Xi1,Yi1)四个方向,定义 d i s t ( A , B ) dist(A,B) dist(A,B)为从 A A A点到 B B B点的最小移动次数,若不能从 A A A B B B,则 d i s t ( A , B ) = 0 dist(A,B) = 0 dist(A,B)=0,求任意两点之间的 d i s t dist dist的和[ Σ i = 1 N − 1 \Sigma_{i=1}^{N-1} Σi=1N1 Σ j = i + 1 N \Sigma_{j=i+1}^{N} Σj=i+1N d i s t ( P i , P j ) dist(Pi,Pj) dist(Pi,Pj)]

分析:
先考虑任给两个点,这两个点能否相互到达。题意说明是斜着四十五度进行移动,即如图所示,红色可以互相到达,白色可以互相到达,不能从红到白或者从白到红。
在这里插入图片描述
那么我们不难发现,横纵坐标和为偶数的可以相互到达,和为奇数的可以相互到达,所以分成两组统计答案即可,不同组之间的坐标的 d i s t dist dist都是0.
斜着移动太费劲了,不如把每个点的坐标都旋转45°然后把距离乘以 2 \sqrt2 2 ,同时把每次移动也这样变化(可以把每次移动看成一段向量,对向量进行旋转和放大),坐标从 ( X , Y ) (X,Y) (X,Y)变化为 ( X + Y , X − Y ) (X+Y,X-Y) (X+Y,XY),移动变成从 ( a , b ) (a,b) (a,b)移动至 ( a + 2 , b ) , ( a − 2 , b ) , ( a , b + 2 ) , ( a , b − 2 ) (a+2,b),(a-2,b),(a,b+2),(a,b-2) (a+2,b),(a2,b),(a,b+2),(a,b2),因为答案只考虑移动次数,所以将坐标和移动同时旋转放大不会影响答案,这样就把斜着移动转为上下左右移动了。考虑 d i s t ( A , B ) = 1 / 2 ∗ ( ∣ X a − X b ∣ + ∣ Y a − Y b ∣ ) dist(A,B)=1/2*(|Xa-Xb|+|Ya-Yb|) dist(A,B)=1/2(XaXb+YaYb),即曼哈顿距离的一半。
下面考虑把这个数学式子化简一下,只考虑求 Σ i = 1 n − 1 \Sigma_{i=1}^{n-1} Σi=1n1 Σ j = i + 1 n \Sigma_{j=i+1}^{n} Σj=i+1n ∣ X i − X j ∣ |Xi-Xj| XiXj,首先把 X X X升序排列,然后直接去掉绝对值,原式变为 Σ i = 1 n − 1 \Sigma_{i=1}^{n-1} Σi=1n1 Σ j = i + 1 n \Sigma_{j=i+1}^{n} Σj=i+1n ( X i − X j ) (Xi-Xj) (XiXj)

X i 对答案的贡献 = 比他小的 X 的个数 ∗ X i − 比他大的 X 的个数 ∗ X i Xi对答案的贡献=比他小的X的个数*Xi-比他大的X的个数*Xi Xi对答案的贡献=比他小的X的个数Xi比他大的X的个数Xi

这样就可以 O ( N ) O(N) O(N)地统计答案,排序复杂度 O ( N l o g N ) O(NlogN) O(NlogN),总复杂度 O ( N l o g N ) O(NlogN) O(NlogN),对 Y Y Y的贡献同理可求,不要忘记最后答案 ∗ 1 / 2 *1/2 1/2

c o d e : code: code:

#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2e5+5;
int n;
int h1,h2;
int a1[N],a2[N],a3[N],a4[N];
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%lld%lld",&x,&y);
		if((x+y)%2)
		{
			a1[++h1]=(x+y);
			a2[h1]=(x-y);
		}
		else
		{
			a3[++h2]=(x+y);
			a4[h2]=(x-y);
		}
	}
	int ans=0;
	sort(a1+1,a1+h1+1);
	sort(a2+1,a2+h1+1);
	sort(a3+1,a3+h2+1);
	sort(a4+1,a4+h2+1);
	for(int i=1;i<=h1;i++)
	{
		int cnt=a1[i];
		ans+=(i-1)*cnt;
		ans-=(h1-i)*cnt;
	}
	for(int i=1;i<=h1;i++)
	{
		int cnt=a2[i];
		ans+=(i-1)*cnt;
		ans-=(h1-i)*cnt;
	}
	for(int i=1;i<=h2;i++)
	{
		int cnt=a3[i];
		ans+=(i-1)*cnt;
		ans-=(h2-i)*cnt;
	}
	for(int i=1;i<=h2;i++)
	{
		int cnt=a4[i];
		ans+=(i-1)*cnt;
		ans-=(h2-i)*cnt;
	}
	printf("%lld",ans/(2LL));
}

F . F. F.
传送门

题意:求 Σ i = 1 N \Sigma_{i=1}^{N} Σi=1N Σ j = i + 1 N \Sigma_{j=i+1}^{N} Σj=i+1N m a x ( A j − A i , 0 ) max(Aj-Ai,0) max(AjAi,0),数据规模 N < = 2 e 5 N<=2e5 N<=2e5

分析:拿人话说一下,就是统计每个 A i Ai Ai后边所有比他大的数,并把两者的差值计入答案。

	  看到这种分大小关系统计答案的题目,一般考虑排序。

考虑每个 A i Ai Ai对答案的影响
A i 的贡献 = A i 前面比他小的 A 的个数 ∗ A i − A i 后面比他大的 A 的个数 ∗ A i Ai的贡献=Ai前面比他小的A的个数*Ai-Ai后面比他大的A的个数*Ai Ai的贡献=Ai前面比他小的A的个数AiAi后面比他大的A的个数Ai
我们把每个 A i Ai Ai的贡献求和就是答案。

我们开一个新数组 B [ N ] B[N] B[N],初始值为 0 0 0。先把最小的 A i Ai Ai找到,然后把 B i Bi Bi改为1,接着再找第二小的 A i Ai Ai,然后把 B i Bi Bi改为1。。。一共进行 N N N次操作。第 K K K次操作时, B B B数组已经有了 K − 1 K-1 K1 1 1 1,这些 1 1 1代表他们对应的 A A A比当前的 A j Aj Aj要小,剩下的 0 0 0代表的 A A A都比当前 A j Aj Aj要大。进而,我们只需要统计 A i Ai Ai前面的 1 1 1的总数和 A i Ai Ai后边的 0 0 0的总数即可。

具体到代码实现,对每次操作,我们先统计0和1的数量,再把当前位置的 B B B改为1。用单点修改的线段树可以很好实现上述过程,统计和修改的单次时间复杂度都是 O ( l o g N ) O(logN) O(logN),总的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

再优化一下,第 i i i次操作求后边的 0 0 0的个数时,可以直接用公式:

s u m ( 0 ) = N − i − ( n o w p l − 1 − s u m ( 1 ) ) sum(0) = N-i-(nowpl-1-sum(1)) sum(0)=Ni(nowpl1sum(1))

其中, n o w p l nowpl nowpl表示 A i Ai Ai的下标, s u m ( 1 ) sum(1) sum(1)表示该位置前面 1 1 1的总数

c o d e : code: code:

#include <cstdio>
#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
const int maxn = 4e5+5;
int n,m,p;
int a[maxn];
int c[maxn];
struct node{
	int l,r,tag,sum;
};
struct snode{
	int num;
	int pl;
};
snode b[maxn];
node tree[maxn<<2];
int ls(int rt)
{
	return rt<<1;
}
int rs(int rt)
{
	return rt<<1|1;
}
void pushdown(int rt)
{
	if(tree[rt].tag)
	{
		int tg=tree[rt].tag;
		tree[rt].tag=0;
		tree[ls(rt)].tag +=tg;
		tree[rs(rt)].tag +=tg;
		tree[ls(rt)].sum +=(tg*(tree[ls(rt)].r-tree[ls(rt)].l+1));
		tree[rs(rt)].sum +=(tg*(tree[rs(rt)].r-tree[rs(rt)].l+1));
		return ;
	}
}

void build(int rt,int l,int r)
{
	tree[rt].l=l;tree[rt].r=r;
	if(l==r)
	{
		tree[rt].sum=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(ls(rt),l,mid);
	build(rs(rt),mid+1,r);
	tree[rt].sum=tree[ls(rt)].sum+tree[rs(rt)].sum;
}
int s_search(int rt,int l,int r)
{
	if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].sum;
	int s=0;
	pushdown(rt);
	if(tree[ls(rt)].r>=l) s+=s_search(ls(rt),l,r);
	if(tree[rs(rt)].l<=r) s+=s_search(rs(rt),l,r);
	tree[rt].sum=tree[ls(rt)].sum+tree[rs(rt)].sum;
	return s;
}
void add(int rt,int l,int r,int k)
{
	if(tree[rt].l>=l&&tree[rt].r<=r)
	{
		tree[rt].tag+=k;
		tree[rt].sum+=(tree[rt].r-tree[rt].l+1)*k;
		return ;
	}
	pushdown(rt);
	if(tree[ls(rt)].r>=l) add(ls(rt),l,r,k);
	if(tree[rs(rt)].l<=r) add(rs(rt),l,r,k);
	tree[rt].sum=tree[ls(rt)].sum+tree[rs(rt)].sum;
	return ;
}
bool cmp(snode a,snode b)
{
	return a.num<b.num;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&c[i]);
		b[i].num=c[i];
		b[i].pl=i;
	}
	sort(b+1,b+1+n,cmp);
	build(1,1,n);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		int nowpl=b[i].pl;
		int nowx=s_search(1,1,nowpl);
		int nowy=n-i-(nowpl-1-nowx);
		ans+=nowx*b[i].num;
		ans-=nowy*b[i].num;
	}
	printf("%lld",ans);
}
  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值