CR 951 (Div. 2)

前言

        姗姗来迟的blog,是因为打完 cf 第二天一早就赶回 HK 准备参加 ACM-HK Local Contest 2024。本蒟蒻的第一场ACM,ust 的几个队都打得不是很好,被后面的 world final 爷薄纱了。在比赛的过程中和队友的配合出现了问题,于最后乱了阵脚,导致原本有希望做出来的两道题竹篮打水,还有一道已经会解的题没有时间打了。由于只有一台电脑,三个人的配合至关重要,好在是这是第一场 ACM ,也能给以后的 ACM 生涯增添点经验。

        言归正传回到 cf 。

A. Guess the Maximum

        答案就是 min-1,简单暴力即可,第三分钟AC。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int T,n,m,a[50005];

int min(int x,int y) { return x < y ? x : y ; }

int max(int x,int y) { return x > y ? x : y ; }

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d",&n);
		int k = 0x3f3f3f3f;
		for (int i = 1;i <= n;++ i) scanf("%d",&a[i]);
		for (int i = 2;i <= n;++ i) k = min(k,max(a[i],a[i - 1]));
		printf("%d\n",k - 1);
	}
	return 0;
}

B. XOR Sequences

        简单计数,答案是2的幂,第22分钟AC。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int T,n,m;

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		int tot = 0,ans = 1;
		while (x && y)
		{
			if(x % 2 == y % 2) ++ tot;
			else break;
			x >>= 1,y >>= 1;
		}
		if(x && (!y))
		{
			while (x && x % 2 == 0) ++ tot,x >>= 1;
		}
		if(y && (!x))
		{
			while (y && y % 2 == 0) ++ tot,y >>= 1;
		}
		for (int i = 1;i <= tot;++ i) ans <<= 1;
		printf("%d\n",ans);
	}
	return 0;
}

C. Earning on Bets

        比赛时用的二分,比赛后发现很难证明可二分性(虽然好像大部分用二分的人都过了,目前还不知道二分的做法是否正确)。第43分钟AC。

        正解应是判断 lcm(参考官方题解)。对于每个可能的结果,投注 \frac{1}{k_i} 就能获得 1 的回报,所以有解的充要条件就是 \sum_{i=1}^{n} k_i < 1 ,由于题目要求的投注为整数,于是给所有的 \frac{1}{k_i} 乘上它们的 lcm ,再判断一下这样构造的序列是否合法即可。

        附上赛时代码。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int T,n,k[55],a[55],ans[55],mn;
long long mx;

int min(int x,int y) { return x < y ? x : y ; }

int check(int x)
{
	mx = (long long)x * (long long)mn - 1ll;
	long long tmp = 0ll;
	for (int i = 1;i <= n;++ i)
		a[i] = (mx / k[i]) + 1,tmp += (long long)a[i];
	if(tmp <= mx) return 1;
	return 0;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		memset(k,0,sizeof k);
		memset(a,0,sizeof a);
		memset(ans,0,sizeof ans);
		scanf("%d",&n);
		mn = 0x3f3f3f3f;
		for (int i = 1;i <= n;++ i) scanf("%d",&k[i]),mn = min(mn,k[i]);
		int l = 1;
		int r = 1000000000;
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if(check(mid))
			{
				for (int i = 1;i <= n;++ i) ans[i] = a[i];
				r = mid - 1;
			}
			else l = mid + 1;
		}
		if(!ans[1]) printf("-1\n");
		else
		{
			for (int i = 1;i <= n;++ i) printf("%d ",ans[i]);
			printf("\n");
		}
	}
	return 0;
}

D. Fixing a Binary String

        我的做法是从后往前找到第一个不合法的位置,翻转后判断即可,注意细节,第1小时22分钟AC。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

#define N 100005

int T,n,k,a[N],b[N];
char s[N];

int check(int x)
{
	for (int i = x + 1;i <= n;++ i) b[i - x] = a[i];
	for (int i = 1;i <= x;++ i) b[n - i + 1] = a[i];
	int now = b[1];
	int tmp = 0;
	int f = 1;
	for (int i = 1;i <= n;++ i)
	{
		if(b[i] == now)
		{
			++ tmp;
			if(tmp > k)
			{
				f = 0;
				break;
			}
		}
		else
		{
			if(tmp == k)
			{
				tmp = 1;
				now ^= 1;
			}
			else
			{
				f = 0;
				break;
			}
		}
	}
	return f;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&k);
		scanf("%s",s + 1);
		for (int i = 1;i <= n;++ i) a[i] = s[i] - '0';
		int flag = 0;
		for (int i = n - 1; i ;-- i)
			if(a[i] != a[i + 1])
			{
				flag = i;
				break;
			}
		if(!flag)
		{
			if(k != n) printf("-1\n");
			else printf("%d\n",n);
			continue;
		}
		int tmp = 0;
		int now = a[flag];
		int bz = 0;
		for (int i = flag; i ;-- i)
		{
			if(a[i] == now)
			{
				++ tmp;
				if(tmp > k)
				{
					bz = i;
					break;
				}
			}
			else
			{
				if(tmp == k)
				{
					tmp = 1;
					now ^= 1;
				}
				else
				{
					bz = i;
					break;
				}
			}
		}
		if(!bz && tmp < k) bz = tmp;
		if(!bz)
		{
			if(n - flag != k) printf("-1\n");
			else printf("%d\n",n);
			continue;
		}
		if(check(bz)) printf("%d\n",bz);
		else if(check(bz + tmp)) printf("%d\n",bz + tmp);
		else printf("-1\n");
	}
	return 0;
}

E. Manhattan Triangle

        赛时毫无头绪,可能是没做过这类题目的话,做出来的可能性不大。

        坐标系中的曼哈顿三角形有个关键性质:一定有两个点满足 |x_1-x_2|=|y_1-y_2| ,这个本人画图描线证明了,但是尚且不知如何用代数方法证明(好吧不重要......)。

        对于曼哈顿距离的问题,往往转化成切比雪夫距离的问题更容易解决:

        将原坐标系中的每个点 (x,y) 映射成另一个坐标系中的 (x + y,x - y),这样另一个坐标系中的切比雪夫距离就等于原坐标系中的曼哈顿距离。

        可以证明,曼哈顿三角形经过映射后一定满足如下两个条件之一:

        1. 相等的切比雪夫距离是两个 |x_i-x_j| 和一个 |y_i-y_j| 。

        1. 相等的切比雪夫距离是一个 |x_i-x_j| 和两个 |y_i-y_j| 。

        言外之意,不可能三个相等的切比雪夫距离都是 |x_i-x_j| 或者 |y_i-y_j| 。

        由前面的 “关键性质” 可以推知,映射后的曼哈顿三角形必定满足:有两个点的横坐标相同或者纵坐标相同

        综上,解题的思路也就不难了:(在转化后坐标的基础上)枚举横坐标相等,纵坐标相差为d的两个点,那么合法的第三个点一定在 x+d 或者 x-d 上,判断一下是否满足上述相等的切比雪夫距离的条件即可。

        实现的时候要用到 map 和 set ,也是本蒟蒻第一次用一些陌生的语法,例如 “for (auto &[x,st] : mp)”,这条语句只能在 c++17 下运行(c++ 11 和 c++ 14 不可)。

#include<cstdio>
#include<cstring>
#include<set>
#include<map>
using namespace std;

#define N 200005

int T,n,d,flag;

struct Node
{
	int x,y;
}a[N];

void solve()
{
	map<int , set<pair<int,int> > > mp;
	scanf("%d%d",&n,&d);
	for (int i = 1,u,v;i <= n;++ i)
		scanf("%d%d",&u,&v),a[i].x = u + v,a[i].y = u - v,mp[a[i].x].insert({a[i].y,i});
	for (auto &[x,st] : mp)
	{
		if(mp.count(x + d))
		{
			for (auto &[y,id] : st)
			{
				auto tmp = st.lower_bound({y + d,0});
				if(tmp != st.end() && tmp->first == y + d)
				{
					auto sec = mp[x + d].lower_bound({y,0});
					if(sec != mp[x + d].end() && sec->first - y <= d)
					{
						flag = 1;
						printf("%d %d %d\n",id,tmp->second,sec->second);
						return;
					}
				}
			}
		}
		if(mp.count(x - d))
		{
			for (auto &[y,id] : st)
			{
				auto tmp = st.lower_bound({y + d,0});
				if(tmp != st.end() && tmp->first == y + d)
				{
					auto sec = mp[x - d].lower_bound({y,0});
					if(sec != mp[x - d].end() && sec->first - y <= d)
					{
						flag = 1;
						printf("%d %d %d\n",id,tmp->second,sec->second);
						return;
					}
				}
			}
		}
	}
	mp.clear();
	for (int i = 1;i <= n;++ i) swap(a[i].x,a[i].y),mp[a[i].x].insert({a[i].y,i});
	for (auto &[x,st] : mp)
	{
		if(mp.count(x + d))
		{
			for (auto &[y,id] : st)
			{
				auto tmp = st.lower_bound({y + d,0});
				if(tmp != st.end() && tmp->first == y + d)
				{
					auto sec = mp[x + d].lower_bound({y,0});
					if(sec != mp[x + d].end() && sec->first - y <= d)
					{
						flag = 1;
						printf("%d %d %d\n",id,tmp->second,sec->second);
						return;
					}
				}
			}
		}
		if(mp.count(x - d))
		{
			for (auto &[y,id] : st)
			{
				auto tmp = st.lower_bound({y + d,0});
				if(tmp != st.end() && tmp->first == y + d)
				{
					auto sec = mp[x - d].lower_bound({y,0});
					if(sec != mp[x - d].end() && sec->first - y <= d)
					{
						flag = 1;
						printf("%d %d %d\n",id,tmp->second,sec->second);
						return;
					}
				}
			}
		}
	}
	return;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		flag = 0;
		solve();
		if(!flag) printf("0 0 0\n");
	}
	return 0;
}

F. Kostyanych's Theorem

        第一次做交互题,考场时做出来还是有点难度的。

        (参考官方题解)

        n个点的无向完全图删除至多 n-2 条边后的边数至少为:       \frac{n(n-1)}{2} - (n-2) = \frac{n^2 - 3n + 4}{2}

        假设所有点的degree都是 n-3,那么总边数为:      \frac{n(n-3)}{2}

        因此可以说明至少有一个点的degree至少为 n - 2 。接下来我们的询问是(n - 2),通过第二个参数是否为 0 可以判断当前图中是否有degree为 n - 2 的点。

        我们可以用双端队列维护答案,使用如下的递归算法:

        1.对于当前图中点数 <= 2 的情况,(按官方题解所说就是)显然。

        2. 若有degree为 n - 2 的点v:删除了 v 及其相连的边之后,图中剩下的边数为 \frac{n^2 - 3n + 4}{2}

- (n-2) = \frac{n^2 - 5n + 8}{2} = \frac{(n - 1) * (n - 2)}{2} - (n - 3),恰好构成了节点为 n-1 个时的子问题图,于是我们可以用递归算法解决子图的哈密尔顿路径。对于节点 v,由于它在当前图中与除了 u 的任何点都相连,所以 v 可以直接加入子图得到的双端队列,要么是队首,要么是队尾。

        3. 若有degree为 n - 1 的点 v:删除了 v 及其相连的边之后,图中剩下的边数为 \frac{n^2 - 3n + 4}{2}

- (n - 1) = \frac{n^2 - 5n + 6}{2},发现这比预计的图多删了一条边,为了满足一致的前提,我们需要找出入度最小的那个点 w 并且将它一并删除(容易证明一定存在至少一个点满足degree <= n - 3,这样删除了 v 和 w 之后的子图也就一定满足前提要求了)。那么递归完之后,维护的队列就应该是:

w -- v -- s -- ...... 其中 s 是子图维护的队首。

        交互题要注意flush的问题,本人IL了很多次,最后发现原来是其他地方写漏了一行,引以为鉴。

        时间复杂度:O(n)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<deque>
using namespace std;

int T,n;

deque<int> q;

void work(int x)
{
	int u,v,w;
	if(x == 1)
	{
		printf("? 0\n");
		fflush(stdout);
		scanf("%d%d",&v,&u);
		q.push_back(v);
		return;
	}
	if(x == 2)
	{
		printf("? 0\n");
		fflush(stdout);
		scanf("%d%d",&v,&u);
		q.push_back(v);
		printf("? 0\n");
		fflush(stdout);
		scanf("%d%d",&v,&u);
		q.push_back(v);
		return;
	}
	printf("? %d\n",x - 2);
	fflush(stdout);
	scanf("%d%d",&v,&u);
	if(u)
	{
		work(x - 1);
		if(u == q.front()) q.push_back(v);
		else q.push_front(v);
	}
	else
	{
		printf("? 0\n");
		fflush(stdout);
		scanf("%d%d",&u,&w);
		work(x - 2);
		q.push_front(v);
		q.push_front(u);
	}
	return;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d",&n);
		while (!q.empty()) q.pop_back();
		work(n);
		printf("! ");
		while (!q.empty()) printf("%d ",q.back()),q.pop_back();
		printf("\n");
		fflush(stdout);
	}
	return 0;
}

总结

        总的来说这场比赛打的还是挺顺的,前四题全部一发过,后面两题还是见识太少了,要多熟悉STL的用法,还是有很多以前会的知识点没有复习完。

  • 45
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值