AtCoder Beginner Contest 359

AtCoder Beginner Contest 359

比赛地址

A - Count Takahashi

算法:模拟

题目大意

在给定的N个字符串中,我们想要找出有多少个字符串是"Takahashi"。这个问题可以这样表述:
“在N个字符串中,有多少个字符串等于’Takahashi’?”

题目思路

没什么值得注意的点,只是需要注意大小写即可

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5+7;
int dx[]= {1,-1,0,0,1,1,-1,-1};
int dy[]= {0,0,1,-1,1,-1,1,-1};
void solve(){
	int n,ans=0;
	cin>>n;
	string s;
	while(n--) {
		cin>>s;
		if(s=="Takahashi") ans++; 
	}
	cout<<ans;
}
int main(){
	int t{1};
//	cin>>t;
	while(t--) {
		solve();
//		cout<<endl;
	} 
	return 0;
}

B - Couples

算法:模拟

题目大意

在2N个人排成一排的情况下,每个人穿的衣服颜色从1到N,每种颜色恰好有两个人穿。我们需要找出有多少个位置i(i=1,2,…,N),使得在穿第i种颜色衣服的两个人之间,正好有一个人。

题目思路

我们要找两种相同颜色的衣服中间是否存在一个人,我们可以这样理解,对于两个相同颜色的人之间的差是否为2,只需要通过map记录一下第一次颜色出现的人的位置即可,如果是第二次出现这个颜色,判断一下与第一次出现的位置进行相减即可。
(后面在写这个题解的时候,我发现完全可以不用map,直接与前面第二个进行比较即可)

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5+7;
int dx[]= {1,-1,0,0,1,1,-1,-1};
int dy[]= {0,0,1,-1,1,-1,1,-1};
map<int,int> mp;
void solve(){
	int n,ans=0;
	cin>>n;
	for(int i=1,t;i<=n*2;i++) {
		cin>>t;
		if(mp[t]==0) mp[t]=i;
		else if(i-mp[t]==2) ans++;
	}
	cout<<ans;
}
int main(){
	int t{1};
//	cin>>t;
	while(t--) {
		solve();
//		cout<<endl;
	} 
	return 0;
}

C - Tile Distance 2

算法:模拟+贪心

题目大意

在坐标平面上,我们使用2×1的瓷砖按照特定的规则进行铺设。以下是铺设规则的描述:

  1. 瓷砖定义:每个瓷砖由一个正方形区域 A_{i,j} 表示,其中 ij 是整数。区域 A_{i,j} 包含所有满足条件 i ≤ x ≤ i+1j ≤ y ≤ j+1 的点 (x, y)

  2. 图块规则:当 i + j 为偶数时,区域 A_{i,j}A_{i+1,j} 属于同一个图块。

  3. 边界和共享:瓦片包括其边界,并且没有两个不同的瓦片共享一个正面积。

在原点附近,瓦片的布局遵循上述规则,确保每个图块由两个相邻的正方形瓷砖组成,且这些瓷砖的 i + j 之和为偶数。
在原点附近,瓦片的布局如下:
在这里插入图片描述
高桥从坐标平面上的点 ( (S_x + 0.5, S_y + 0.5) ) 开始他的旅程。他可以进行以下类型的移动:

  • 移动规则:他可以选择一个方向(上、下、左或右)和一个正整数 ( n )。然后,他可以向选择的方向移动 ( n ) 个单位。

  • 过路费:每次他进入一个新的瓷砖区域时,他需要支付1单位的过路费。

目标是求出高桥到达点 ( (T_x + 0.5, T_y + 0.5) ) 所需的最小通行费。

这个问题可以通过计算从起点到终点的最短路径来解决,考虑到每次进入新的瓷砖都需要支付过路费。

题目思路

首先,我们可以发现,无法节省次数来进行y轴的移动,也就是说,我们想要移动y轴一定支付次数,那么我们就可以先移动y轴。
然后因为瓷砖是错排的,当我们移动y轴时,我们可以进行左移或者右移一格而不耗费次数,那么遵循贪心的原则,我们肯定会在移动y轴的时候尽可能的靠近我们想要达到的目的地。
所以当出发点到达与目的地相同y轴的时候,x轴也进行了一定的移动,然后新出发点和目的地在同一个y轴上,只考虑左右移动即可。
因为x ,y一定是正数,也就是在第一象限,可以观察出一个规律:当x+y为奇数时,一定在瓷砖的右边,当x+y为偶数时,一定在瓷砖的左边。
处理同水平下左右移动时难免有误差,我们可以强制规定,两个瓷砖在同一侧,那么就不用考虑误差的影响了。

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5+7;
int dx[]= {1,-1,0,0,1,1,-1,-1};
int dy[]= {0,0,1,-1,1,-1,1,-1};
//寄右偶左 
//一定是正数 
//加0.5表示为了在矩阵中心
//跨y一定要一个
//跨x 
void solve(){
	ll sx,sy,tx,ty;
	cin>>sx>>sy;
	cin>>tx>>ty;
	ll y = abs(ty-sy);
	ll x = 0;
	if(tx>sx) {
		//左边 
		if((sx+sy)%2==0) sx++;
		if((tx+ty)%2==0) tx++;
		x = max((tx-sx)-y,0ll);
	} 
	else if(tx<sx){
		//右边 
		if((sx+sy)%2==1) sx--;
		if((tx+ty)%2==1) tx--;
		x = max((sx-tx)-y,0ll);
	} else {
		x = 0;
	}
//	cout<<sx<<" "<<tx;
//	cout<<y<<" "<<x<<endl;
	cout<<y+x/2;
}
int main(){
	int t{1};
//	cin>>t;
	while(t--) {
		solve();
//		cout<<endl;
	} 
	return 0;
}

D - Avoid K Palindrome

算法:动态规划

题目大意:

给定一个由字符 ‘A’、‘B’ 和 ‘?’ 组成的字符串 S,长度为 N。同时给定一个正整数 K。我们定义一个好字符串 T 为满足以下条件的由 ‘A’ 和 ‘B’ 组成的字符串:

  • T 中,不存在长度为 K 的连续子串是回文的。

对于给定的字符串 S,我们可以通过将 S 中的 ‘?’ 替换为 ‘A’ 或 ‘B’ 来生成不同的字符串。设 qS 中 ‘?’ 的数量,那么替换后可以得到 2^q 个不同的字符串。

任务是找出这 2^q 个字符串中有多少个是好字符串,并求这个数目对模数 998244353 取模的结果。

题目思路

首先,我们可以将AB字符串理解为01串。
因为K<=10,说明K字串的长度最多为10,也就是2^10种情况,我们可以先预处理一下。
定义一个dp[i][j], 考虑前i个字符后,当前状态为j的字符串的数量。
对于A字符,我们当成在末尾放0进去
对于B字符,我们当成在末尾放1进去
对于?字符,我们当成在末尾0,1分别放进去
当未满k长度或者满足好字串,我们就更新数据

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5+7;
const ll M = 998244353;
int dx[]= {1,-1,0,0,1,1,-1,-1};
int dy[]= {0,0,1,-1,1,-1,1,-1};
//可以把AB字符串当成01串 
int n, k;
bool check(int s)
{
	for (int i = 0; i < k / 2; i++)
	{
		if ((s >> i & 1) != (s >> (k - i - 1) & 1))
		{
			return true;
		}
	}
	return false;
}
void solve()
{

	cin >> n >> k;
	string s;
	cin >> s;
	vector<bool> good(1 << k);
	//预处理 
	for (int i = 0; i < (1 << k); i++)
	{
		good[i] = check(i);
	}
	//考虑前i个字符后,当前状态为j的字符串的数量
	//状态j为一个二进制串 
	vector<vector<int>>dp(n+1,vector<int>(1<<k));
	dp[0][0] = 1;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < (1 << k); j++)
		{
			if (s[i] != 'B')
			{
				bool ok = false;
				if (i + 1 < k) ok = true;
				//处理数据
				//更新状态 
				int nxt = (j << 1) & ((1 << k) - 1);
				if (good[nxt]) ok = true;
				if (ok)
				{
					dp[i + 1][nxt] = (dp[i][j] + dp[i + 1][nxt]) % M;
				}
			}
			if (s[i] != 'A')
			{
				bool ok = false;
				if (i + 1 < k) ok = true;
				//处理数据
				//更新状态 
				int nxt = (j << 1) & ((1 << k) - 1) | 1;
				if (good[nxt]) ok = true;
				if (ok)
				{
					dp[i + 1][nxt] = (dp[i][j] + dp[i + 1][nxt]) % M;
				}
			}
		}
	}
	int res = 0;
	for (int s = 0; s < (1 << k); s++)
	{
		res = (res + dp[n][s]) % M;
	}
	cout << res << "\n";
	return;
}
int main()
{
	solve();
	return 0;
}

E - Water Tank

算法:单调栈

题目大意

给定一个长度为 ( N ) 的正整数序列 ( H = (H_1, H_2, \ldots, H_N) )。

同时给定一个长度为 ( N+1 ) 的非负整数序列 ( A = (A_0, A_1, \ldots, A_N) )。初始时,所有 ( A_i ) 都等于 0。

对 ( A ) 重复进行以下运算:

  1. 将 ( A_0 ) 的值增加 1。
  2. 依次对 ( i = 1, 2, \ldots, N ) 进行以下操作:
    • 如果 ( A_{i-1} > A_i ) 并且 ( A_{i-1} > H_i ),则将 ( A_{i-1} ) 的值减少 1,并将 ( A_i ) 的值增加 1。

我们需要求出每个 ( i = 1, 2, \ldots, N ) 在 ( A_i > 0 ) 第一次成立之前的运算次数。

题目思路

首先,因为水具有蔓延性,所以当左边的挡板低于右边的挡板时,我们需要将左边填满为止。

我们可以理解为最左边有一个无穷高的挡板。
那么当我们一直灌水进去的时候,对我们有影响的其实是左边第一个大于当前挡板的高度,因为这样其面积就是左边那个挡板的蓄水量加上当前挡板到左边挡板的距离乘上当前挡板的高度即可,又因为是第一次成立之前的运算次数,所以需要再加一即可。

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5+7;
const ll M = 1e12;
int dx[]= {1,-1,0,0,1,1,-1,-1};
int dy[]= {0,0,1,-1,1,-1,1,-1};
struct Node {
	ll h;
	ll len;
};
Node a[N];
ll t[N];
stack<Node> st;
void solve(){
	ll ans = 0;
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].h,a[i].len=i;
	st.push(Node{M,0});
	for(int i=1;i<=n;i++) {
		//这里可以不用 因为不用有这种情况
		//只是为了格式好看
		if(st.empty()) st.push(a[i]);
		else {
			while(st.top().h<a[i].h) st.pop();
			t[i]=(a[i].len-st.top().len)*a[i].h+t[st.top().len];
			cout<<t[i]+1<<" ";
			st.push(a[i]);
		}
	}
	return;
}
int main(){
	solve(); 
	return 0;
}

总结

如果有什么不理解或者更好的方法,欢迎进行提问或者分享。

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值