Codeforces Round 914 (Div. 2)补题

 Forked!(Problem - A - Codeforces)

题目大意:在棋盘中,我们指定骑士的运动方式——向一个方向运动a,然后向另一个方向运动b,两个方向必须是垂直关系。骑士此时能从一个点,进行两次这样的运动(两次运动都以同一个点为起点),且两次运动视为同时进行的,现在给出国王和王后的坐标,问骑士能否同时杀死国王和王后,另外棋盘无限大。

思路:我们这么想,骑士可以到的位置,那么那个位置同样可以以同样的方式到达骑士的位置,那么我们将国王和王后可以到的位置分别用set统计出来,再将两个set合并,最后的结果就是两个单set的size()-合并set的size().

#include
using namespace std;
int dx[]={1,1,-1,-1};
int dy[]={1,-1,1,-1};
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		int x1,y1,x2,y2;
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		set>pa,pb,s;
		for(int i=0;i<4;i++)
		{
			pa.insert({x1+dx[i]*a,y1+dy[i]*b});
			pb.insert({x2+dx[i]*a,y2+dy[i]*b});
		}
		for(int i=0;i<4;i++)
		{
			pa.insert({x1+dx[i]*b,y1+dy[i]*a});
			pb.insert({x2+dx[i]*b,y2+dy[i]*a});
		}
		for(auto i:pa) s.insert(i);
		for(auto i:pb) s.insert(i);
		int ans=pa.size()+pb.size()-s.size();
		printf("%d\n",ans);
	}
}

Collecting Game(Problem - B - Codeforces)

题目大意:现有一个数组a[],我们从中选一个数作为初始分数,并将这个数拿出来,然后我们去这个数组中进行操作,如果某个数小于等于当前分数,那么我们将当前分数加上这个数得到新的分数,并将这个数移出数组,我们最后需要输出,以每个数为初始位置的时候,能得到的最终分数。

思路:我们考虑,一个数被选后,小于等于它的数肯定也能被选,大于它的数就要看看它和小于它的数的和是否大于它,所以实际上就是排序算前缀和,我们定义s[i]为位置i处的前缀和。那么能选多少个数该怎么确定呢。我们知道对于数组中最大的数,它能选的数就是n-1,那么对于第二大的数,如果s[i]>=a[i+1](假设第i个是第二大的数,i+1是最大的数),那么a[i+1]能选的数,它也一定可以选了,如果s[i]

#include
using namespace std;
#define int long long
int a[100010],b[100010],s[100010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i];
		sort(a+1,a+1+n);
		s[1]=a[1];
		mapmp;
		for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i];
		mp[a[n]]=n-1;
		for(int i=n-1;i>=1;i--)
		{
			if(s[i]>=a[i+1]) mp[a[i]]=mp[a[i+1]];
			else mp[a[i]]=i-1;
		}
		for(int i=1;i<=n;i++) printf("%lld ",mp[b[i]]);
		printf("\n");
	}
}

Array Game(Problem - C - Codeforces)

 题目大意:现有一个大小为n的数组a[],我们每次操作可以任意挑两个数,将它们差的绝对值加进数组,问k次操作后,a[]中的最小值是什么。

思路:我们可以发现,结果很明显与k有关系,如果k=1,那么实际上,我们只能执行一次操作,结果的一定是原数组中的最小值或者某两个数的差;如果k=2,我们只能执行两次,首先执行一次可以得到一个新数,然后一次操作,我们可以选择一个原数和这个新数进行计算,不然意义不大,所以最小值有三个来源,原数组中的数,新产生的这个数,新产生的这个数和原数组中某个数的差值;k=3,这是我最生气的一点,真没想到这种情况没有分析清楚,这个实际上是最简单的一种情况,我们对于两个数a,b,第一次操作得到c=b-a,第二次操作d=b-c=b-b+a=a,第三次操作d-a=0,所以这种情况是恒为0的。真离谱,这么简单的情况竟然没分析清楚。所以还是要考虑到这道题的k到1e9了,所以就要想一想,是否k大于等于某个值后,情况都一样,因为这里很明显不能暴力求解,那么就可以从这个角度找找规律。然后就没什么说的了,就是涉及到操作数的,而且操作数很大的就要考虑下这里的操作实际有效的是哪些(或者是哪些组合)(上次的这个题就是,实际有效的操作仅仅5个)或者考虑一下当操作数为多少之后,后面的情况都和它一样。

#include
using namespace std;
#define int long long
int a[2010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n,k;
		scanf("%lld%lld",&n,&k);
		int mi=2e18;
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]),mi=min(mi,a[i]);
		if(k>=3) printf("0\n");
		else
		{
			sort(a+1,a+1+n);
			
			for(int i=1;i<=n;i++)
			{
				for(int j=i+1;j<=n;j++)
				{
					int tmp=a[j]-a[i];
					mi=min(mi,tmp);
					if(k==2)
					{
						int p=lower_bound(a+1,a+1+n,tmp)-a;
						mi=min(mi,tmp-a[p-1]);
						mi=min(mi,a[p]-tmp);
					}
				}
			}
			printf("%lld\n",mi);
		}
		
	}
}

Set To Max (Easy Version)(Problem - D1 - Codeforces)

题目大意:我们给定两个长度均为n的数组a[]和b[],我们可以进行的操作是,从a[]中选一段区间[l,r],将这段区间内的a[i]全部改成这段区间的最大值,问最后能否使a[]变成b[].

思路:我们来考虑,对于某处的a[i],我们需要将它修改成b[i],那么一定要在它的前面或者后面找一个a[j],使得a[j]==b[i],所以我们就分两种情况来讨论,一种是在前面找,一种是在后面找(等价于将数组倒序,再从前往后找一遍),那么我们现在就来看从前面找如何实现。

首先,ak...aj...ai...,如果此时ak==aj==bi,那么区间ak和aj都是可以的,但是我们选aj的话可以减少对[k,j]这一段的影响,因为这段的影响本来就是可以避免的。然后我们再来看,如果aj可以去修改ai需要满足什么条件,首先aj需要是区间[j,i]中的最大值,其次我们一旦修改那么[j,i]的范围内的所有a就都变成aj了,后面只能变大,不能再变小了,所以这一段中的b必须大于等于aj,否则就不能成立。我们对于每一个i都要去找一个aj,但是如果真的去判断这两个条件是否成立的话,显然是超时的。所以我们要用别的方法来代替。

对于条件1,aj是[j,i]中的最大值,那么我们如果对于每个aj都能知道它往后最远能修改到哪里,然后判断一下i是否在他能修改的位置内,不就可以实现了吗;对于第二个条件,中间那段b必须大于等于aj,那么我们可以从ai往前找,找到小于aj(bi)的位置,然后标记一下,然后我们判断一下j是否在这个位置之前不就可以了,如果不在这个位置之前,那么不就可以修改。所以现在很明显,我们需要预处理两个数组出来,我们看看这两个数组的预处理如何实现。

对于第一个预处理数组,我们需要找到每一个位置j,a[j]后面第一个大于它的数,因为小于等于它的话实际上都可以被它修改。那么该如何找呢,对于每一个位置暴力肯定不可以,upper_bound()也不行,因为数组并非有序的,那么这里的解决思路是从后往前访问,用栈(或是说滑动窗口的思想)来优化。我们可以发现,我们将后面的数入栈,访问到aj的时候,如果栈中有一些小于等于它的数,那么再往前,如果访问到小于aj的数,那么找到的位置是j,如果大于等于aj,那么找到的位置肯定也不会在这些小于等于aj的数中,那么实际上,我们可以将这些数弹出去,因为后面一定不会再用到它们。这样的话,实际上就实现了优化,我们抛弃了冗余的访问。

对于第二个预处理数组,我们需要找到每一个位置i,b[i]前面第一个小于它的数,因为如果我们的j在小于b[i]的数前面的话,那么修改的时候,会将一些a[k]变大,大过b[k]而且无法改回去。所以我们就要找到j往左最远能到哪里,一旦j超过这个最左的范围,就不能用来修改了。那么我们就来看,如何找到前面第一个小于b[i]的数,暴力肯定是不行的,二分也不行,因为数组无序,那么这里也是用栈(滑动窗口的思想来优化的)。我们从前往后访问,将数放入栈中,对于b[i],如果栈中有一些大于等于它的数,那么再往后,如果访问到的数大于b[i],那么找到的位置就是i,如果访问到的数小于等于b[i],那么找到的位置肯定也不会在这一片大于等于b[i]的数中,所以这部分是冗余的,我们需要将它们弹出,减少无效访问。

至此两个预处理数组都解决了,现在只用去遍历a[i]并不断更新位置数组(用来记录上个值为b[i]的数出现的位置),然后对于每一个a[i]

#include
using namespace std;
int a[2010],b[2010],la[2010],pr[2010],v[2010],pos[2010];
signed main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),v[i]=0;
		for(int i=1;i<=n;i++) scanf("%d",&b[i]);
		for(int z=0;z<2;z++)
		{
			stack>p;
			p.push({2000,n+1});
			for(int i=n;i>=1;i--)
			{
				while(p.top().first<=a[i]) p.pop();//找后面第一个大于a[i]的位置
				la[i]=p.top().second;
				p.push({a[i],i});
			}
			while(p.size()) p.pop();
			p.push({0,0});
			for(int i=1;i<=n;i++)
			{
				while(p.top().first>=b[i]) p.pop();//找前面第一个小于b[i]的位置
				pr[i]=p.top().second;
				p.push({b[i],i});
			}
			memset(pos,0,sizeof pos);
			for(int i=1;i<=n;i++)
			{
				pos[a[i]]=i;
				if(a[i]==b[i]) v[i]=1;
				if(a[i]pr[i]&&la[pos[b[i]]]>i) v[i]=1;
				}
			}
			reverse(a+1,a+1+n);
			reverse(b+1,b+1+n);
			reverse(v+1,v+1+n);
		}
		int flag=1;
		for(int i=1;i<=n;i++)
		{
			if(!v[i])
			{
				flag=0;
				break;
			}
		}
		if(flag) printf("YES\n");
		else printf("NO\n");
	}
}

ps:虽然每次修改的区间,但是如果从区间的角度考虑不通的话,就要考虑从单点的角度来考虑,去考虑这个点如何能被修改,修改方案对于总体情况来说是否合法。然后这里还有一点就是,在无序数组中寻找第一个大于或小于某个位置的值,用栈来实现。

反思:写题的时候还是要清醒一点,真的就离谱,c题竟然没把最简单的那种情况分析清楚,甚至比较麻烦的两个都分析出来了,下次一定要记住,对于这种操作数确定,而且操作数很大的,一定要找有效操作具体有哪些,或者找当操作数大于某个值后是否所有的操作数都一样,或者找有没有可能每操作多少次,实际变化就循环一次,一定要去找规律,这种肯定不是暴力写的。然后对于区间和单点,它们是相互转化的,涉及到区间的,区间分析不通就要考虑下单点,涉及到单点的,单点不好确定,就要考虑单点能落在哪个区间。另外,对于无序数组的相对位置关系的处理,要考虑用栈实现,本质上就是将冗余部分去掉。

你可能感兴趣的:(codeforces补题,算法,数据结构)