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

前排提示: D D D题大力分类讨论题, E E E题入门状压题, F F F L I S LIS LIS板子题

比赛链接

A . A. A.

传送门

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

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int n,m,h,w;
signed main()
{
	scanf("%lld",&h);
	int i=1;int tot=1;
	while(1)
	{
		if(tot>h)
		{
			printf("%lld\n",i);
			break;
		}
		tot+=(1<<i);
		i++;
	}
	return 0;
}

B . B. B.

传送门

题意:一共 N N N个人,每个人都有一个 S i S_i Si的字符串表示名字和一个 C i C_i Ci表示分数,把 N N N个人按名字的字典序编号为 0 , 1 , 2... N − 1 0,1,2...N-1 0,1,2...N1,求第 T m o d N T mod N TmodN个人的名字,其中 T T T表示 Σ C i \Sigma C_i ΣCi

数据规模: N < = 100 , C i < = 4229 , l e n t h ( S i ) < = 16 N<=100,C_i <= 4229,lenth(S_i)<=16 N<=100,Ci<=4229,lenth(Si)<=16

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int N = 105;
int t;
string s[N];
int a[N];
int sum;
int n,m,h,w;
bool cmp(string a,string b)
{
	return a<b;
}
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		cin>>s[i];
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	int winn=(sum%n);
	sort(s,s+n,cmp);
	cout<<s[winn];
	return 0;
}

C . C. C.

传送门

题意: N N N张牌编号 1 , 2... N 1,2...N 1,2...N,每张牌有强度 A i A_i Ai和成本 C i C_i Ci,要删去所有 A x < A y A_x<A_y Ax<Ay C x > C y C_x>C_y Cx>Cy的卡牌,输出剩下卡片的编号

数据规模: N < = 2 ∗ 1 0 5 N<=2*10^5 N<=2105

做法:排序。我们先按 A i A_i Ai从小到大排序,对于第 i i i张牌来讲,它的强度 A i A_i Ai比后边每一张都小。那么如果后边有一张牌 j j j,他的成本 C j C_j Cj比第 i i i张牌 C i C_i Ci的小,我们就要把第 i i i张牌删去。可以发现,这实际上是维护后缀最小值。详细地说,我们从后往前维护一个 C C C的最小值,如果当前这张 C i C_i Ci比最小值大,我们就删去这张,如果当前这张 C i C_i Ci比最小值小,我们就保留并把最小值更新为 C i C_i Ci

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int N = 2e5;
int t;
struct node
{
	int aa,bb,pl;
};
node c[N];
int anss[N];
bool vis[N];
int head;
bool cmp(node a,node b)
{
	return a.aa < b.aa;
}
int n,m,h,w;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&c[i].aa,&c[i].bb);
		c[i].pl=i;
	}
	sort(c+1,c+1+n,cmp);
	int minn=c[n].bb;
	int ans=n;
	for(int i=n;i>=1;i--)
	{
		if(c[i].bb>minn)
		{
			ans--;
			vis[c[i].pl]=1;
		}
		else
		{
			minn=c[i].bb;
		}
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
	{
		if(!vis[i]) printf("%d ",i);
	}
	return 0;
}

D . D. D.

传送门

题意:

给定某个矩阵左下角坐标 ( a , b ) (a,b) (a,b)和右上角坐标 ( c , d ) (c,d) (c,d),求:
矩阵覆盖的阴影部分面积 ∗ 2 *2 2
每个阴影三角形的边长为1。
在这里插入图片描述
做法:

狗屎题 需要大力讨论。先找循环节,发现最小循环节长这个样子
在这里插入图片描述
长为4,宽为2。
最小循环节阴影部分为8(答案要×2,所以我们讨论的所有阴影面积就直接×2了)

首先,考虑左下角坐标为原点的矩形怎么算阴影面积。形如这样,红色就是要求的矩形面积,蓝色是最多的循环节,绿色和紫色是剩余部分。
S 蓝 = ⌊ x / 4 ⌋ ∗ ⌊ y / 2 ⌋ ∗ 8 S_蓝 = \lfloor x /4\rfloor*\lfloor y/2\rfloor*8 S=x/4y/28
S 绿 S_绿 S绿需要按长为1,2,3三种情况分类,每种情况又要考虑宽是奇数还是偶数
S 紫 S_紫 S只有 y y y是奇数时才存在,此时 S 紫 = ⌊ x / 4 ⌋ ∗ 6 S_紫 = \lfloor x /4\rfloor*6 S=x/46
在这里插入图片描述

然后我们任取一个在第一象限的矩形,发现它可以转化成好几个左下角为原点的矩形拼凑出来,如图所示, S 黑 = S 红 − S 蓝 − S 紫 + S 黄 S_黑 = S_红 - S_蓝 - S_紫 +S_黄 S=SSS+S
在这里插入图片描述
最后,对于任意的矩形,我们可以把它平移到第一象限,看数据规模,只需要把左下坐标和右上坐标都加上 1 0 9 10^9 109就可以,因为 1 0 9 10^9 109 4 和 2 4和2 42的倍数,所以面积不变

数据规模; − 1 0 9 < = a , b , c , d < = 1 0 9 -10^9<=a,b,c,d<=10^9 109<=a,b,c,d<=109

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int n,m,h,w;
const int M = 1e9;
int f(int x,int y)
{
	int ans=0;
	ans+=(x/4)*(y/2)*8;
	if(x%4==1)//宽为1的绿色
	{
		ans+=3*(y/2);
		if(y%2) ans+=2;//多出来右上角一小块
	}
	if(x%4==2)//宽为2
	{
		ans+=3*y;//每一行的面积都是3,所以不用考虑y的奇偶
	}
	if(x%4==3)//宽为3
	{
		ans+=7*(y/2);
		if(y%2) ans+=3;//多出来右上角一块
	}
	if(y%2) ans+=(x/4)*4;//紫色部分面积
	return ans;
}
int a,b,c,d;
signed main()
{
	
	scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
	a+=M;b+=M;c+=M;d+=M;
	printf("%lld\n",f(c,d)-f(a,d)-f(c,b)+f(a,b));
	return 0;
}
//0 0 5 5

E . E. E.

传送门

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

数据规模: N < = 18 N<=18 N<=18

做法:状压+ d p dp dp/记忆化搜索。设 F [ x ] F[x] F[x] x x x的二进制01串为局面, x x x的第 i i i位为1表示当前第 i i i张牌还在桌子上,为 0 0 0表示不在桌子上。若 F [ x ] = = 1 F[x]==1 F[x]==1则当前局面先手必胜, F [ x ] = = 2 F[x]==2 F[x]==2则当前局面后手必胜, F [ x ] = = 0 F[x]==0 F[x]==0则当前局面还未被搜到。怎么算 F [ x ] F[x] F[x]呢?我们枚举当前状态可以拿走哪两张牌达到下一状态 F [ y ] F[y] F[y],如果所有的 F [ y ] F[y] F[y]都是1,那么 F [ x ] = 2 F[x]=2 F[x]=2,否则 F [ x ] = 1 F[x]=1 F[x]=1
直观理解一下这个转移方程,如果当前牌面任意拿走两张可以拿掉的牌形成新牌面,新牌面都是先手必胜,那么旧牌面一定是后手必胜的(旧牌面的后手和新牌面的先手是同一个人)。如果新牌面至少有一种情况是后手必胜,那么旧牌面一定是先手必胜的(旧牌面的先手一定会选择新牌面是后手必胜的情况去拿牌,这样可以让自己在新牌面必胜,旧牌面的先手和新牌面的后手是同一个人)。
如果没法翻牌了,那么当前局面就是后手胜, F = 2 F=2 F=2,这是动态规划的初始化或者搜索终止的局面。
答案就是 F [ 2 N − 1 ] F[2^N-1] F[2N1]

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int N = 20;
int t;
int a[N],b[N];
int f[1<<N];
int n,m,h,w;
int dit(int x)
{
	int ans=0;
	while(x)
	{
		x>>=1;
		ans++;
	}
	return ans;
}
int dfs(int x)
{
	if(f[x]) return f[x];
	int dits=dit(x);
//	printf("x = %d dit = %d\n",x,dits);
	for(int i=1;i<=dits-1;i++)
	{
		for(int j=i+1;j<=dits;j++)
		{
			if(((x>>(i-1))&1)&&((x>>(j-1))&1))
			if(a[i]==a[j]||b[i]==b[j])
			{
				int y=x-(1<<(i-1))-(1<<(j-1));
				dfs(x-(1<<(i-1))-(1<<(j-1)));
				if(f[y]==2) f[x]=1;
			}
		}
	}
	if(f[x]!=1) f[x]=2;//无牌可拿或者所有新牌面都是先手必胜,则后手必胜,俩情况合一起写了
	return f[x];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
	int ans=dfs((1<<n)-1);
	if(ans==1) printf("Takahashi");
	if(ans==2) printf("Aoki");
	return 0;
}

F . F. F.

传送门

题意:给一个序列 A 1 , A 2 , . . . A N A_1,A_2,...A_N A1,A2,...AN,判断每个 A i A_i Ai是否出现在至少一个最长上升子序列 ( L I S ) (LIS) (LIS)中。

数据规模: N < = 2 ∗ 1 0 5 N<=2*10^5 N<=2105

做法:首先要知道最长上升子序列可以有很多条。不难发现,如果

以 A i 结尾的 L I S 长度 + 以 A i 开头的 L I S 长度 − 1 = L e n t h ( L I S ) 以A_i结尾的LIS长度 +以A_i开头的LIS长度 -1 = Lenth(LIS) Ai结尾的LIS长度+Ai开头的LIS长度1=Lenth(LIS)

那么 A i A_i Ai就在起码一个最长上升子序列中,这个 L I S LIS LIS就是等式左侧的两部分拼接起来。

前一个就是 L I S LIS LIS模板,后一个的长度就是把序列前后颠倒,然后求以 A i A_i Ai结尾的最长下降子序列长度。

代码就不放了,我还没写完

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值