Codeforces Round 919 (Div. 2)补题

Satisfying Constraints(Problem - A - Codeforces

题目大意:有三种限定方式:

1.k>=x;

2.k<=x;

3.k!=x;

根据若干限定,找出合法的k的个数。

思路:通过1,2定出区间,同时记录3中的x,遍历记录的容器,如果x在区间内,那么ans--即可,不要遍历区间,会超时,而且也更麻烦。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		int l=0,r=1e9+10;
		vector<int>p;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			int op,c;
			scanf("%d%d",&op,&c);
			if(op==1) l=max(l,c);
			else if(op==2) r=min(r,c);
			else p.push_back(c);	
		}
		if(r<l) printf("0\n");
		else 
		{
			int ans=r-l+1;
			for(int i=0;i<p.size();i++)
			{
				if(l<=p[i]&&p[i]<=r) ans--;
			}
			printf("%d\n",ans);
		}
	}
}

Summation Game(Problem - B - Codeforces

题目大意:A和B在玩游戏,现有一个数组a[],A先开始,A每次可以删除最多x个数,B可以将最多y个数全部乘-1,两人分别执行一次操作后求数组和,A想最大化结果,B想最小化结果。两人都发挥最佳状态,问最后的结果是多少。

思路:A的操作实际上是将被操作数a[i]的贡献变成0,B则是将被操作数a[i]的贡献变成-a[i],B想要最小化结果,那么肯定是要将操作数全部用掉,但是对A来说,她的操作,一方面减少了被操作数的贡献,但同时也防止被操作数被B操作,造成更大损失。所以A如果操作就要尽可能挑大的数进行操作,进而削弱B的破坏。B也要尽可能对大的数进行操作,造成更多损失。那么我们只用将数组排序,然后讨论A需要操作多少个即可,为了及时获取她们操作区间的和(因为要将这部分从总和中删掉),用前缀和处理一下。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int c[200010],s[200010];
bool cmp(int x,int y)
{
	return x>y;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,a,b,sum=0;
		scanf("%d%d%d",&n,&a,&b);
		for(int i=1;i<=n;i++) scanf("%d",&c[i]),sum += c[i];
		sort(c+1,c+1+n,cmp);
		for(int i=1;i<=n;i++) s[i]=s[i-1]+c[i];
		int mx=-1e9;
		for(int i=0,j=i+b;i<=a;i++,j++)
		{
			if(j>n) j=n;
			int d=2*(s[j]-s[i])+s[i];
			int ans=sum-d;
			mx=max(mx,ans);
		}
		printf("%d\n",mx);
	}
}

Partitioning the Array()

题目大意:现有一个数组a[],我们要将它划分成k份,每份含有的数量是相同的,如果存在一个m,使得用a[i]%m去替换a[i]后,每个划分出来的数组都相同,那么便算作一次有效划分,问共有多少有效划分。

思路:我们很容易发现,划分后对应位置是同余的,即a[i]与a[i+k]是同余的,当然a[i]与a[i+2k]也是同余的,但是我们可以用a[i+1]与a[i+2k]来判断,这样就有递推关系了。则意味着它们的差值的最大公因数非1.核心部分如下:

cpp 复制代码
for(int k=1;k<=n;k++)
		{
			if(n%k==0)
			{
				int g=0;
				for(int i=1;i+k<=n;i++)
				{
					g=__gcd(g,abs(a[i+k]-a[i]));
				}
				if(g!=1) ans++;
			}
		}

__gcd()是内置函数,__gcd(0,a)=a

实际上的时间复杂度是小于2*sqrt(n)*n,所以虽然是嵌套循环,但不会超时。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,ans=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int k=1;k<=n;k++)
		{
			if(n%k==0)
			{
				int g=0;
				for(int i=1;i+k<=n;i++)
				{
					g=__gcd(g,abs(a[i+k]-a[i]));
				}
				if(g!=1) ans++;
			}
		}
		printf("%d\n",ans);
	}
}

Array Repetition(Problem - D - Codeforces

题目大意:现有一个空数组a[]和两种操作:

1.将x插入到数组末尾

2.将整个数组复制x次接到数组末尾。

按照规定顺序进行操作,最后有q个查询,查询指定位置的数是多少。

1<n,q<1e5,b={1,2},b=1:1<=x<=n;b=2,1<=x<=1e9,查询下标k:1<=k<=min(c,1e18),c是完成所有操作后的数组长度。

思路:这道题肯定不能直接模拟,数据范围太大了,我们注意到对于复制部分需要到被复制部分去找,所以首先哪些部分是由复制得到的需要记录下来。最关键的就是怎么记录这些数据,而且还要区分哪些是插入的,哪些是复制的。有一个思路就是,通过记录每次操作来实现。我们首先定义一个dp[]数组来记录当前操作结束后数组最后一个元素的下标,另外还得定义一个数组来记录最后一个元素的值。和一个可以存放哪些操作是复制操作的容器。比如我们通过容器知道i操作是复制操作,就可以通过dp[i]知道复制区间的右界,同时dp[i-1]就是复制区间的左界(并不是恰好取到,而是定出范围),这样对于一个k,我们通过遍历容器,就可以知道它是否在复制区间内,如果在复制区间内,我们可以通过取模运算将它转到插入部分。通过对dp数组的查找,我们就可以获得它对应的插入部分的位置是在第几次操作时进行插入的,而每次插入的值我们是记录了的,就可以由此得到位置k的数值。

以上只是一个总体的思路,还有许多细节部分需要一一处理清楚:

1.dp的赋值,dp是用来记录第i次操作结束后,数组中最后一个元素的下标的,所以当执行插入操作时,dp[i]=dp[i-1]+1,当执行复制操作时dp[i]=dp[i-1]*(x+1),另外,因为我们的查询范围在1e18以内,所以大于1e18的位置可以不记录了,但是为了统一,我们直接将位置大于1e18的全部记录成一个大于1e18的数,当然可以直接统一成2e18也可以。

2.v[]的计算,v存的是第i次操作结束后,数组中最后一个位置的值,执行插入操作是,很显然v[i]=x,执行复制操作的话v[i]=v[i-1].

3.对容器p的遍历,因为我们容器中存的是复制对应的操作位置i,那么dp[i]就可以得到这个复制操作执行完后,最右端的位置,dp[i-1]+1就是这个复制区间的左端点,但是因为dp[i]和dp[i-1]对应位置的值在v[]中都有记录,如果k在这两个位置,那么我们就不进行处理,可以直接去找值,如果不是,那么k就要转化,转化操作为k%dp[i-1],因为dp[i-1]是前面被复制区间的长度,所以这样就可以得到k位置在被复制区间中的位置。然后再不断的找。因为可能有多次复制操作,所以我们从后往前访问,因为k可能需要不断往前转化,当然,这里是有特殊情况的,就是当k%dp[i-1]==0的时候,直接让k=dp[i-1],然后退出,因为k肯定不能为0。

4.k确定后去查找值,k确定后,我们就可以在dp数组中找到这个值,而这个值对应的下标就是操作数,我们可以通过下标获取每次操作的值,也就是该位置的值。这里在dp中查找最好用lower_bound,因为我们的k通过转化,肯定是对应着dp中某一个确定的值的,所以肯定可以找到,find()的时间复杂度是O(n),而lower_bound()的时间复杂度是O(logn),更快一些。

至此,本题解决。

核心思想就是,通过操作次数的下标将dp[]和v[]联系起来,然后利用容器和dp来判断查找位置是否是复制位置,如果是复制位置,那么就要转化到非复制位置去,然后利用dp[]和v[]的联系找出该位置的值。这里有个查找值是否在特殊区间中的方法就是,记录一个端点的位置然后用被记录端点表示出另一个端点,进而得到区间的范围,我们只需要遍历记录一个端点位置的容器即可。当然分别记录左右端点的位置,然后在两个容器中二分查找也可以,但是本题涉及到不断转化,前一种方法好。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
long long dp[100010];
int v[100010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,q;
		scanf("%d%d",&n,&q);
		vector<int>p;
		int flag=1;
		for(int i=1;i<=n;i++)
		{
			int op,x;
			scanf("%d%d",&op,&x);
			if(op==1) 
			{
				v[i]=x;
				dp[i]=dp[i-1]+1;
			}
			else
			{
				v[i]=v[i-1];
				if(dp[i-1]>=2e18/(x+1)) dp[i]=2e18;
				else dp[i]=dp[i-1]*(x+1);
				if(flag) p.push_back(i);
			}
			if(dp[i]==2e18) flag=0;
		}
		while(q--)
		{
			long long x;
			scanf("%lld",&x);
			for(int i=p.size()-1;i>=0;i--)
			{
				int j=p[i];
				if(dp[j-1]<x&&x<dp[j])
				{
					if(x%dp[j-1]==0)
					{
						x=dp[j-1];
						break;
					}
					x%=dp[j-1];
				}
			}
			int ve=v[(lower_bound(dp+1,dp+1+n,x)-dp)];
			printf("%d ",ve);
		}
		printf("\n");
	}
}

ps:另外要注意2e18是double类型的数,与long long类型的数进行==比较的时候,编译器会自动进行类型提升,但是进行>=时,是会报错的。

相关推荐
许野平16 分钟前
Rust: enum 和 i32 的区别和互换
python·算法·rust·enum·i32
chenziang123 分钟前
leetcode hot100 合并区间
算法
chenziang123 分钟前
leetcode hot100 对称二叉树
算法·leetcode·职场和发展
szuzhan.gy1 小时前
DS查找—二叉树平衡因子
数据结构·c++·算法
一只码代码的章鱼1 小时前
排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)
数据结构·算法·排序算法
青い月の魔女2 小时前
数据结构初阶---二叉树
c语言·数据结构·笔记·学习·算法
我要出家当道士2 小时前
Nginx单向链表 ngx_list_t
数据结构·nginx·链表·c
林的快手2 小时前
209.长度最小的子数组
java·数据结构·数据库·python·算法·leetcode
千天夜3 小时前
多源多点路径规划:基于启发式动态生成树算法的实现
算法·机器学习·动态规划
从以前3 小时前
准备考试:解决大学入学考试问题
数据结构·python·算法