Codeforces Round 933 (Div. 3)

A. Rudolf and the Ticket(Problem - A - Codeforces

题目大意:现有两个数组b[],c[]和一个整数k,需要从b,c中各选一个数,使得b[i]+c[j]<=k,问有多少种选法,b[i]和b[j]相同但i,j不同就不算一种选法。

思路:遍历b[],对于每个b[i]去找一个对应的c[j],使得b[i]+c[j]<=k,我们可以二分查找小于等于k-b[i]的最后一个位置;也可以查找大于k-b[i]的第一个位置(upper_bound),甚至这题还可以用双指针来写。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010],b[200010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n,m,k;
		scanf("%lld%lld%lld",&n,&m,&k);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=1;i<=m;i++) scanf("%lld",&b[i]);
		sort(b+1,b+1+m);
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			int d=k-a[i];
			int l=1,r=m;
			while(l<r)//
			{
				int mid=l+r+1>>1;
				if(b[mid]<=d) l=mid;
				else r=mid-1;
			}
			//全小于
			if(b[l]<=d) //这个特判用来防止全部大于的情况
			{
				ans += l;
				//printf("%d %d %d\n",a[i],b[l],l);
			}
		}
		printf("%lld\n",ans);
	}
}
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010],b[200010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n,m,k;
		scanf("%lld%lld%lld",&n,&m,&k);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=1;i<=m;i++) scanf("%lld",&b[i]);
		sort(b+1,b+1+m);
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			int d=k-a[i];
			ans += upper_bound(b+1,b+1+m,d)-b-1;
		}
		printf("%lld\n",ans);
	}
}

这里我们来梳理一下lower_bound和upper_bound的用法以及特殊情况时的取值,免得老是不敢用,手写二分还是有点浪费时间。

所以就是在我们传入的空间中搜,和正常的手写二分一样,要想避免边界情况的影响,我们可以在左右各多传入一个位置。

 这题实际上还可以用双指针来写,我们将b[]和c[]都排序,然后用一个指针从小开始扫描b[],另一个指针从大往小扫描c,对于b[i],我们找到了一个合适的位置c[j],那么i往后,则b[i]变大,那么c[j]就应该变小,所以两个指针之间都是单向的,所以可以用双指针来写。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010],b[200010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n,m,k;
		scanf("%lld%lld%lld",&n,&m,&k);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=1;i<=m;i++) scanf("%lld",&b[i]);
		sort(b+1,b+1+m);
		sort(a+1,a+1+n);
		int ans=0;
		for(int i=1,j=m;i<=n;i++)
		{
			while(j>=1&&a[i]+b[j]>k) j--;
			ans += j;
		}
		printf("%lld\n",ans);
	}
}

B. Rudolf and 121(Problem - B - Codeforces

题目大意:现在有一个序列a[],我们可以执行如下操作,每次选择一个位置i,然后使a[i-1]-=1,a[i]-=2,a[i+1]-=1,问序列最后能不能变成全0的序列。

思路:涉及到这种操作类的题目,有两个大的思考方向,一个是操作实际产生的效益是什么,一个是操作的顺序有没有关系。我用样例试了一下发现操作的顺序不影响判断,那么我们就可以从第二个位置开始往后操作,每次一直操作到a[i-1]为0,最后判断序列是否全0即可。另外注意过程中可能产生负数,要特判一下,否则我们的操作是每次减去a[i-1],这样容易忽略掉这种特殊情况。

#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		int c=a[1],flag=1;
		for(int i=2;i<n;i++)
		{
			int c=a[i-1];
			a[i-1] -= c;
			a[i] -= 2*c;
			a[i+1]-=c;
			if(a[i]<0||a[i+1]<0) 
			{
				flag=0;
				break;
			}
			c=a[i];
		}
		for(int i=1;i<=n;i++) if(a[i]) flag=0;
		if(flag) printf("YES\n");
		else printf("NO\n");
	}
}

C. Rudolf and the Ugly String(Problem - C - Codeforces

题目大意:给定一个字符串,要求字符串中不能有子串的"map"和"pie",我们可以进行删字符操作,问最少删除多少个字符,可以使得字符串满足要求。

思路:以"map"为例,如果对于一个出现了"map"的位置,我们删除哪个字符比较好。显然可能会出现"mmmmmapppp"这种情况,所以删除中间的字符比较好,对于pie也是同理,删除i,那么第一条策略就出来了,由于这两个子串的特殊性,我们实际行还需要考虑一种特殊情况"mapie",按照前面的两条特殊规则,我们应该删除'a'和'i',但是对于这种情况很显然应该删除'p',然后所有的策略都出来了,我们可以直接循环然后记录删除的次数。

#include<bits/stdc++.h>
using namespace std;
char s[1000010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d%s",&n,s+1);
		int ans=0;
		for(int i=2;i<n;i++)
		{
			if(s[i-1]=='m'&&s[i]=='a'&&s[i+1]=='p'&&s[i+2]=='i'&&s[i+3]=='e') ans++,i+=4;
			else if(s[i-1]=='m'&&s[i]=='a'&&s[i+1]=='p') ans++,i+=2;
			else if(s[i-1]=='p'&&s[i]=='i'&&s[i+1]=='e') ans++,i+=2;
		}
		printf("%d\n",ans);
	}
}

D. Rudolf and the Ball Game(Problem - D - Codeforces

题目大意:现在有n个小朋友站成一圈,进行m次传球游戏,最开始球在编号为k的小朋友手中,传球的操作为"x c",c==0的时候,表示顺时针传递x个位置,c==1的时候,表示逆时针传递x个位置,c=='?'的时候,表示方向不确定传递x个位置。问最后球有可能在哪些小朋友手中。

思路:这题显然就是bfs暴搜,但是由于每层可能会出现多个位置,所以我们一定要一层一层的搜完,而且由于我们并不能直接的判断当前搜到元素是第几层的,所以要么标记,要么在while大循环中套一个小循环,小循环每次把这一层搜完就截止。另外还有一点要注意,显然元素会重复,往后搜的过程中数据规模会很大,但是由于其中有很多重复的元素,所以我们可以在新增元素之前整一个去重操作,这里就要新开一个临时队列了,不然用原有队列会出现bug,比如一个元素在队列中,但是它在上一层。

然后最关键的地方在于传递后的位置的确定:

0:(k+x)%n==0?n:(k+x)%n

1:(k-x+n)%n==0?n:(k-x+n)%n

?:(k+x)%n==0?n:(k+x)%n,(k-x+n)%n==0?n:(k-x+n)%n

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int st[200010];
vector<pair<int,int>>a;
void bfs(int k)
{
	queue<int>q;
	q.push(k);
	int c=0;
	while(q.size())
	{
		if(c>=m) break;
		int ti=q.size();
		int x=a[c].first;
		int op=a[c].second;
		queue<int>tmp;
		while(ti--)
		{
			int t=q.front();
			q.pop();
			if(op==0) //顺时针
			{
				t += x;
				t %= n;
				if(!t) t=n;
				if(!st[t])
				{
					st[t]=1;
					tmp.push(t);
				}
			}
			else if(op==1)
			{
				t -= x;
				t += n;
				t %= n;
				if(!t) t=n;
				if(!st[t])
				{
					st[t]=1;
					tmp.push(t);
				}
			}
			else 
			{
				int t1=t+x;
				t1%=n;
				if(!t1) t1=n;
				int t2=t-x+n;
				t2 %= n;
				if(!t2) t2=n;
				if(!st[t1])
				{
					st[t1]=1;
					tmp.push(t1);
				}
				if(!st[t2])
				{
					st[t2]=1;
					tmp.push(t2);
				}
			}
		}
		while(tmp.size())
		{
			q.push(tmp.front());
			st[tmp.front()]=0;
			tmp.pop();	
		}
		c++;
	}
	set<int>s;
	while(q.size())
	{
		s.insert(q.front());
		q.pop();
	}
	cout<<s.size()<<endl;
	for(auto it:s) cout<<it<<" ";
	cout<<endl;

}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d",&n,&m,&k);
		a.clear();
		for(int i=1;i<=m;i++)
		{
			int x;
			string s;
			cin>>x>>s;
			if(s[0]=='0') a.push_back({x,0});
		    else if(s[0]=='1') a.push_back({x,1});
		    else a.push_back({x,2});
			//cout<<op[0]<<endl;
		}
 
		bfs(k);
	}
}

E. Rudolf and k Bridges(Problem - E - Codeforces)

题目大意:现在有一条很宽的河,我们可以把它分成n行m列,按照这个划分,给出每个位置的河水深度,现在需要架k座相邻的桥,每座桥需要建在一行中,从这一行中选出若干位置搭支架,首尾两个位置必须被选,中间再选出一些位置,要求两直接间的位置数量小于等于d,被选中的位置产生的花费为a[i]+1,问最后的总花费最小是多少。

思路:显然行与行之间是独立的,所以我们需要先找出每一行的最小值。首尾位置必须选,中间跨度是d,那么就说明不能有连续d+1个位置不被选,否则就不满足跨度是d的要求了。这里就是一个经典的单调队列优化的dp问题。

定义dp[i]表示第i个元素被选,产生的花费。怎么更新呢,显然它往前d+1个位置中至少有一个被选,至于具体哪个被选,我们可以用单调队列维护一个区间最小值来实现。

但是有一点要注意,首尾必须选,这个还是有点麻烦的,但是也还好,我们最开始的队列为空,那么第一个元素被放进去后,后面的元素都会用它来更新,保证末尾元素被选,那么我们直接取dp[m]即可,因为集合定义的时候就是dp[i]中i一定被选。

然后至于连续的这个条件,我们可以求前缀和,然后枚举实现。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int dp[200010],ans[200010],q[200010],hh,tt;
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n,m,k,d;
		scanf("%lld%lld%lld%lld",&n,&m,&k,&d);
		int c=d+1;
		for(int i=1;i<=n;i++)
		{
			hh=0,tt=-1;
			q[0]=0;
			for(int j=1;j<=m;j++)
			{ 
				scanf("%lld",&dp[j]);
				while(hh<=tt&&j-q[hh]>c) hh++;
				dp[j]+=dp[q[hh]]+1;
				while(hh<=tt&&dp[q[tt]]>dp[j]) tt--;
				q[++tt]=j;	
			}
			ans[i]=dp[m];
		}
		for(int i=1;i<=n;i++) ans[i] += ans[i-1];
		int mi=1e18;
		for(int i=k;i<=n;i++) mi = min(mi,ans[i]-ans[i-k]);
		printf("%lld\n",mi);
	}
}

F. Rudolf and Imbalance(Problem - F - Codeforces

题目大意:有一个数组a[],满足a1<a2<...<an,另外有两个数组b[]和c[],要求从b[],c[]中各选一个数,求和得到一个新的数,插入a[]中,使得a[]中相邻两个数的差值的最大值最小。

思路:这里虽然是最大值最小,但是不需要使用二分。我们来考虑一下新数应该插在什么位置,显然是原数组中差值最大的位置,那么新数就需要是一个在这之间的数。最后是差值最大的两数的中点。中点是mid,那么相当于我们要找到两个数与mid最近。还有有两种做法,双指针和二分。不管哪种方法都需要对数进行排序,那么我们就先进行排序。

双指针的话我们需要来分析规律,b[]从小的开始遍历,c[]如果也从小的开始遍历,b变大了,c已经遍历过的值照样能用上,显然不好;那么c从大开始遍历的话,判断条件是什么呢?首先数肯定在在区间中,但是我们想要的是它尽可能地接近mid,所以如果快接近l时的值,b变大的时候实际还是会用到,所以不能仅仅满足在区间中。那么如果满足大于mid呢,当b变大后,为了使和接近mid,那么c确实需要变小,而且已经搜过的值在b小的时候显然更接近mid,那么就不必再用了。所以我们可以以大于mid作为判断条件,考虑到小于mid但接近的情况,我们在wile结束的时候再加一个判断就行。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,pair<int,int>> piii;
int a[200010],b[200010],c[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m1,m2;
		scanf("%d%d%d",&n,&m1,&m2);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=m1;i++) scanf("%d",&b[i]);
		for(int i=1;i<=m2;i++) scanf("%d",&c[i]);
		sort(b+1,b+1+m1);
		sort(c+1,c+1+m2);
		priority_queue<piii>q;
		for(int i=2;i<=n;i++)
		{
			q.push({a[i]-a[i-1],{a[i-1],a[i]}});
		}
		auto it=q.top();
		q.pop();
		int d=it.first,x=it.second.first,y=it.second.second;
		int mid=x+(y-x)/2;
		int ans=d,j=m2;
		for(int i=1;i<=m1;i++)//i变大,那么b[i]变大,所以加起来更接近mid的c[j]应该变小
		{
			while(j&&b[i]+c[j]>mid)
			{
				int sum=b[i]+c[j];
				int res=max(sum-x,y-sum);
				ans=min(res,ans);
				j--;
			}
			if(!j) break;
			int sum=b[i]+c[j];//小于但接近的情况
			int res=max(sum-x,y-sum);
			ans=min(res,ans); 
		}
		if(q.size())
		{
			auto tmp=q.top();
			if(ans>=tmp.first)  cout<<ans<<endl;
			else cout<<tmp.first<<endl;
		}
		else cout<<ans<<endl;
	}
}

二分:

二分实际就没什么特殊的,就是对于mid-b去c中二分查找大于等于它最小的数,然后累计一下最小值即可。但是mid-b有为负数的情况,所以直接这么写的话也不是很好,如果b大于mid的话,就用c中最小的数判一下就行,不用去搜。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,pair<int,int>> piii;
int a[200010],b[200010],c[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m1,m2;
		scanf("%d%d%d",&n,&m1,&m2);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=m1;i++) scanf("%d",&b[i]);
		for(int i=1;i<=m2;i++) scanf("%d",&c[i]);
		sort(b+1,b+1+m1);
		sort(c+1,c+1+m2);
		int mx1=0,mx2=0,idx;
		for(int i=2;i<=n;i++)
		{
			int d=a[i]-a[i-1];
			if(d>mx1) mx2=mx1,mx1=d,idx=i;
			else if(d>mx2) mx2=d;
		}
		int mid=a[idx-1]+(a[idx]-a[idx-1])/2;//防止越界
		int x=a[idx-1],y=a[idx];
		int ans=mx1,tmp,res;
		for(int i=1;i<=m1;i++)//i变大,那么b[i]变大,所以加起来更接近mid的c[j]应该变小
		{
			if(b[i]>=mid) 
			{
				tmp=b[i]+c[1];
				res=max(tmp-x,y-tmp);
				ans = min(ans,res);	
			}
			else
			{
				int d=mid-b[i];
				int in=lower_bound(c+1,c+m2,d)-c;
				tmp=b[i]+c[in];
				res=max(tmp-x,y-tmp);
				ans = min(ans,res);
				if(in!=1)
				{
					tmp=b[i]+c[in-1];
					res=max(tmp-x,y-tmp);
					ans = min(ans,res);
				}
			}
		}
		printf("%d\n",max(mx2,ans));
	}
}

对于两个数组中分别取元素求和的问题,需要往二分和双指针上考虑考虑。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值