Codeforces Round 971 (Div. 4)(A,B,C,D,E,F,G1)

比赛链接

讲解

q u e u e f o r c e s queueforces queueforces 了属于是,根本加载不进去。

这场纯纯的数学场,前面比较简单,后面 E F EF EF 比较难, G 23 G23 G23 题太难了。 G 2 G2 G2 听别人讲听明白了,但是代码实在是太难写了,写不出来,主要是最近吃药吃的没法静下心来敲代码,一直感觉心里痒痒的,全身好像有蚂蚁在爬,压制不住的狂躁。


A. Minimize!

题面:

给你两个整数 a a a b b b ( a ≤ b a \leq b ab )。在 c c c ( a ≤ c ≤ b a \leq c \leq b acb ) 的所有可能整数值中,求 ( c − a ) + ( b − c ) (c - a) + (b - c) (ca)+(bc) 的最小值。

思路:

签到, ( c − a ) + ( b − c ) = c − a + b − c = b − a (c - a) + (b - c)=c-a+b-c=b-a (ca)+(bc)=ca+bc=ba

code:

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

int T,a,b;

int main(){
	cin>>T;
	while(T--){
		cin>>a>>b;
		cout<<b-a<<endl;
	}
	return 0;
} 

B. osu!mania

题面:

您正在玩您最喜欢的节奏游戏 osu!mania。您的节拍图布局包括 n n n 行和 4 4 4 列。由于最下面的音符距离较近,因此您将先处理最下面的一行,最后处理最上面的一行。每一行将包含一个音符,用 "#"表示。

对于每个音符 1 , 2 , … , n 1, 2, \dots, n 1,2,,n ,按照处理顺序,输出该音符所在的列。

思路:

每行就四列,而且有且只有一个 “#”,读入每一行,然后找到第一个 “#” 的下标位置,最后倒序输出(因为题目要求从下到上)即可。

code:

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

int T,n;

int main(){
	cin>>T;
	while(T--){
		string s;
		vector<int> a;
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>s;
			a.push_back(s.find('#')+1);
		}
		for(auto it=a.rbegin();it!=a.rend();it++)
			cout<<*it<<" ";
		cout<<endl;
	}
}

C. The Legend of Freya the Frog

题面:

青蛙弗莱娅正在二维坐标平面上旅行。她目前在点 ( 0 , 0 ) (0,0) (0,0) ,想去点 ( x , y ) (x,y) (x,y) 。在一次移动中,她选择了 0 ≤ d ≤ k 0 \leq d \leq k 0dk 这样的整数 d d d ,并沿着她面对的方向向前跳跃了 d d d 个点。

最初,她面向的是正方向 x x x 。每次移动后,她将交替朝向正 x x x 方向和正 y y y 方向(即第二次移动时朝向正 y y y 方向,第三次移动时朝向正 x x x 方向,以此类推)。

她最少要走多少步才能落在点 ( x , y ) (x,y) (x,y) 上?

思路:

x x x 轴上的移动至少需要 ⌈ x k ⌉ \left\lceil\dfrac{x}{k}\right\rceil kx 次,同理 y y y 轴上的移动至少需要 ⌈ y k ⌉ \left\lceil\dfrac{y}{k}\right\rceil ky 次。但是因为是增加 x x x 值和增加 y y y 值是轮着来的,所以当一边移动次数比较多的时候,另一边可能已经走完了,但是这边就是不走,也得过轮数。

因为先走 x x x 方向,所以 x x x 方向走 m m m 次, y y y 方向至少会走 m − 1 m-1 m1 次,因此一共至少要过 2 m − 1 2m-1 2m1 轮。同理, y y y 方向走 m m m 次, x x x 方向至少会走 m m m 次,因此一共至少要过 2 m 2m 2m 轮。

分别算出 x x x 轴上和 y y y 轴上分别至少要走多少次,然后取 m i n { ⌈ x k ⌉ ∗ 2 − 1 , ⌈ y k ⌉ ∗ 2 } min\{\left\lceil\dfrac{x}{k}\right\rceil*2-1,\left\lceil\dfrac{y}{k}\right\rceil*2\} min{kx21,ky2} 即可。

code:

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

int T,x,y,k;

int main(){
	cin>>T;
	while(T--){
		cin>>x>>y>>k;
		cout<<max((x+k-1)/k*2-1,(y+k-1)/k*2)<<endl;
	}
	return 0;
}

D. Satyam and Counting

题面:

萨提亚姆在二维坐标平面上给定了 n n n 个不同的点。对于所有给定点 ( x i , y i ) (x_i, y_i) (xi,yi) ,保证 0 ≤ y i ≤ 1 0 \leq y_i \leq 1 0yi1 。选择三个不同的点作为顶点,可以形成多少个不同的非退化直角三角形 ∗ ^{\text{∗}}

如果存在一个点 v v v ,使得 v v v a a a 的顶点而不是 b b b 的顶点,则两个三角形 a a a b b b 是不同的。

∗ ^{\text{∗}} 非退化直角三角形是指面积为正,一个内角为 9 0 ∘ 90^{\circ} 90 的三角形。

思路:

这挠滩翻译给我整出个非世代直角三角形给我cpu干烧了,非退化直角三角形其实就是我们常识里的直角三角形。

这个题非常贴心地保证 0 ≤ y i ≤ 1 0 \leq y_i \leq 1 0yi1,因此发现直角三角形只有两种可能的形成方式,一个是一个直角边是竖直的,另一个点随便取,另一个就是三个点的分布形如 ( i , 0 ) , ( i + 1 , 1 ) , ( i + 2 , 0 ) (i,0),(i+1,1),(i+2,0) (i,0),(i+1,1),(i+2,0) ( i , 1 ) , ( i + 1 , 0 ) , ( i + 2 , 1 ) (i,1),(i+1,0),(i+2,1) (i,1),(i+1,0),(i+2,1) 的斜边水平的三角形。图形画出来大概如下:
在这里插入图片描述
注意点可能是重合的,选取不同的点画出来的三角形算作不同的三角形。

第一种三角形假设竖直的边的两个点坐标分别为 ( i , 0 ) , ( i , 1 ) (i,0),(i,1) (i,0),(i,1),在这个坐标上的点的个数分别为 a , b a,b a,b,一共有 n n n 个点,那么可以组成的三角形个数为 a ∗ b ∗ ( n − a − b ) a*b*(n-a-b) ab(nab) 个。

第二种三角形假设三个点坐标为 ( i , 0 ) , ( i + 1 , 1 ) , ( i + 2 , 0 ) (i,0),(i+1,1),(i+2,0) (i,0),(i+1,1),(i+2,0) ( i , 1 ) , ( i + 1 , 0 ) , ( i + 2 , 1 ) (i,1),(i+1,0),(i+2,1) (i,1),(i+1,0),(i+2,1),点的个数分别为 a , b , c a,b,c a,b,c 个,那么三角形个数就是 a ∗ b ∗ c a*b*c abc 个。

枚举 i i i ,一个一个算,累加起来即可。注意点的横坐标可以是 0 0 0,所以 i i i 可以取 0 0 0。横坐标小于等于 n n n,所以不用离散化。

code:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
typedef long long ll;

int T,n;

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		vector<vector<int> > pt(n+1,vector<int>(2));
		for(int i=1,x,y;i<=n;i++){
			cin>>x>>y;
			pt[x][y]++;
		}
		ll ans=0;
		for(int i=0;i<=n;i++){
			int a=pt[i][0],b=pt[i][1];
			ans+=1ll*a*b*(n-a-b);
		}
		for(int i=0;i<=n-2;i++){
			ans+=1ll*pt[i][0]*pt[i+2][0]*pt[i+1][1];
			ans+=1ll*pt[i][1]*pt[i+2][1]*pt[i+1][0];
		}
		cout<<ans<<endl;
	}
	return 0;
}

E. Klee’s SUPER DUPER LARGE Array!!!

题面:

Klee 有一个长度为 n n n 的数组 a a a ,其中包含按顺序排列的整数 [ k , k + 1 , . . . , k + n − 1 ] [k, k+1, ..., k+n-1] [k,k+1,...,k+n1] 。克利希望选择一个索引 i i i ( 1 ≤ i ≤ n 1 \leq i \leq n 1in ),使得 x = ∣ a 1 + a 2 + ⋯ + a i − a i + 1 − ⋯ − a n ∣ x = |a_1 + a_2 + \dots + a_i - a_{i+1} - \dots - a_n| x=a1+a2++aiai+1an 最小。请注意,对于任意整数 z z z 而言, ∣ z ∣ |z| z 表示 z z z 的绝对值。

输出 x x x 的最小可能值。

思路:

说白了就是取前 i i i 个数为正,后 n − i n-i ni 个数为负,然后求和。两段分别为一个等差数列,求和后分别为 ( 2 k + i − 1 ) ∗ i 2 \dfrac{(2k+i-1)*i}{2} 2(2k+i1)i ( 2 k + n + i − 1 ) ∗ ( n − i ) 2 \dfrac{(2k+n+i-1)*(n-i)}{2} 2(2k+n+i1)(ni),因此前一段减后一段的结果就是 ( 2 k + i − 1 ) ∗ i 2 − ( 2 k + n + i − 1 ) ∗ ( n − i ) 2 = ( − 2 k n + 4 k i − n 2 + n + 2 i 2 − 2 i ) 2 \dfrac{(2k+i-1)*i}{2}-\dfrac{(2k+n+i-1)*(n-i)}{2}=\dfrac{(-2kn+4ki-n^2+n+2i^2-2i)}2 2(2k+i1)i2(2k+n+i1)(ni)=2(2kn+4kin2+n+2i22i)。它是一个关于 i i i 的一个函数,看式子或者根据实际意义很容易看出它是一个单调递增的函数。

我们要找取绝对值后最小的值,其实就是找最接近 0 0 0 的值,不难想到就是最后一个负值和第一个正值,我们分别取绝对值再取两者较小值即可。因为它是单调函数所以可以二分来找答案。

我们可以二分找到第一个大于等于 0 0 0 的值,它的上一个位置就是最后一个负值。根据实际意义可以想到这个函数一定会存在至少一个负值和至少一个正值,所以不用担心找不到导致越界的问题。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

ll T,n,k;
ll calc(ll i){
	return (-2*k*n+4*k*i-n*n+n+2*i*i-2*i)/2;
}
bool check(ll i){
	return calc(i)>=0;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		ll l=1,r=n;
		while(l<r){
			ll mid=(l+r)>>1;
			if(check(mid))r=mid;
			else l=mid+1;
		}
		cout<<min(abs(calc(l-1)),abs(calc(l)))<<endl;
	}
	return 0;
}

F. Firefly’s Queries

题面:

萤火虫是一个给定长度为 n n n 的数组 a a a。让 c i c_i ci 表示 a a a 数组的第 i i i 次循环移动 ∗ ^{\text{∗}} 。她创建了一个新数组 b b b ,使得 b = c 1 + c 2 + ⋯ + c n b = c_1 + c_2 + \dots + c_n b=c1+c2++cn 其中 + + + 表示连接操作 † ^{\text{†}}

然后,她会向你提出 q q q 个查询。对于每一个查询,输出从第 l l l 个元素开始,到第 r r r 个元素结束的 b b b 子数组中所有元素的总和,包括两端的元素。

∗ ^{\text{∗}} 数组 a a a 的第 x x x ( 1 ≤ x ≤ n 1 \leq x \leq n 1xn ) 次循环移位是 a x , a x + 1 … a n , a 1 , a 2 … a x − 1 a_x, a_{x+1} \ldots a_n, a_1, a_2 \ldots a_{x - 1} ax,ax+1an,a1,a2ax1 。请注意, 第 1 1 1 次移位就是最初的 a a a

† ^{\text{†}} 长度为 n n n 的两个数组 p p p q q q (换句话说, p + q p + q p+q )的连接操作是 p 1 , p 2 , . . . , p n , q 1 , q 2 , . . . , q n p_1, p_2, ..., p_n, q_1, q_2, ..., q_n p1,p2,...,pn,q1,q2,...,qn

思路:

不难想到假设第 l l l 个元素落在第 L L L 次循环移动里,第 r r r 个元素落在第 R R R 次循环移动里的话,那么 L + 1 ∼ R − 1 L+1\sim R-1 L+1R1 中间的循环移动就是完整的循环移动,一次完整的循环移动的元素没有变化,它的所有元素的和就是原本所有元素的和。

所以我们可以对中间完整的循环移动快速地用所有元素的和乘上完整的循环移动数组的个数直接算出,而对于前面和后面分别计算。前面的部分在 l l l 所在的循环移动数组中相当于求个后缀的和,后面的部分在 r r r 所在的循环移动数组中相当于求个前缀的和。

我们分别找出 l , r l,r l,r 相当于在第几次循环移动数组中的什么位置上,然后分别找出循环移动后的前后缀元素在原本数组中的位置,然后对它们进行求和即可。

l , r l,r l,r 在第几次循环移动数组中的什么位置上好算。以 l l l 距离,它在第 ⌈ l n ⌉ \left\lceil\dfrac{l}{n}\right\rceil nl 个循环移动数组,减去前 ⌈ l n ⌉ − 1 \left\lceil\dfrac{l}{n}\right\rceil-1 nl1 个循环移动数组的长度就是在这个循环移动数组中的位置,也就是 l − ( ⌈ l n ⌉ − 1 ) ∗ n l-(\left\lceil\dfrac{l}{n}\right\rceil-1)*n l(nl1)n。同理 r r r 也是这么算。

为了迅速求和,我们先处理出 a a a 数组的前缀和数组 s s s

算第 i d id id 个循环移动数组的 i i i 位置以前的前缀和:不妨先给 i d id id 1 1 1,让初始的循环移动(也就是原数组)对应 0 0 0,这样第 i d id id 次循环移动的数组的后 i d id id 个元素就是循环移动过来的最后 i d id id 个元素。这个循环移动数组列出来就是 a i d + 1 , a i d + 2 , … , a n , a 1 , a 2 , … , a i d a_{id+1},a_{id+2},\dots,a_n,a_1,a_2,\dots,a_{id} aid+1,aid+2,,an,a1,a2,,aid

假设 i i i 没有超出前 n − i d n-id nid 个元素,即 i ≤ n − i d i\le n-id inid。那么前缀和就是 a i d + 1 + a i d + 2 + ⋯ + a i d + i = s i d + i − s i d a_{id+1}+a_{id+2}+\dots+a_{id+i}=s_{id+i}-s_{id} aid+1+aid+2++aid+i=sid+isid。如果 i > n − i d i\gt n-id i>nid,那么前缀和就是 a i d + 1 + a i d + 2 + ⋯ + a n + a 1 + a i − ( n − i d ) = s n − s i d + s i + i d − n a_{id+1}+a_{id+2}+\dots+a_{n}+a_1+a_{i-(n-id)}=s_n-s_{id}+s_{i+id-n} aid+1+aid+2++an+a1+ai(nid)=snsid+si+idn

同理算后缀和,如果 i > n − i d i\gt n-id i>nid,那么后缀和就是 a i − ( n − i d ) + ⋯ + a i d = s i d − s i + i d − n − 1 a_{i-(n-id)}+\dots+a_{id}=s_{id}-s_{i+id-n-1} ai(nid)++aid=sidsi+idn1。如果 i ≤ n − i d i\le n-id inid,那么后缀和就是 a i d + i + ⋯ + a n + a 1 + ⋯ + a i d = s n − s i d + i − 1 + s i d a_{id+i}+\dots+a_{n}+a_1+\dots+a_{id}=s_{n}-s_{id+i-1}+s_{id} aid+i++an+a1++aid=snsid+i1+sid

然后列式算即可。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;

int T,n,q;
ll a[maxn],s[maxn];

ll pre(ll id,ll i){
	id--;
	ll ans=0;
	if(i<=n-id){
		ans=s[id+i]-s[id];
	}
	else {
		ans=s[n]-s[id];
		ans+=s[i-n+id];
	}
	return ans;
}
ll suf(ll id,ll i){
	id--;
	ll ans=0;
	if(i>n-id){
		ans=s[id]-s[i-n+id-1];
	}
	else {
		ans=s[id];
		ans+=s[n]-s[i+id-1];
	}
	return ans;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>q;
		for(int i=1;i<=n;i++)cin>>a[i],s[i]=s[i-1]+a[i];
		for(ll _=1,l,r;_<=q;_++){
			cin>>l>>r;
			ll L=(l+n-1)/n+1,R=(r-1)/n;
			ll ans=s[n]*(R-L+1);
			ans+=suf((l+n-1)/n,l-(l-1)/n*n);
			ans+=pre((r+n-1)/n,r-(r-1)/n*n);
			cout<<ans<<endl;
		}
	}
	return 0;
} 

G1. Yunli’s Subarray Queries (easy version)

题面:

这是问题的简单版本。在这个版本中,可以保证 r = l + k − 1 r=l+k-1 r=l+k1 适用于所有查询。

对于任意数组 b b b ,云里可以执行任意多次下面的操作:

  • 选择索引 i i i 。设置 b i = x b_i = x bi=x ,其中 x x x 是她想要的任意整数( x x x 不限于区间 [ 1 , n ] [1,n] [1,n] )。

f ( b ) f(b) f(b) 记为她需要执行的最小操作次数,直到 b b b 中存在一个长度至少为 k k k 的连续子数组 ∗ ^{\text{∗}}

给云莉一个大小为 n n n 的数组 a a a 并向你提出 q q q 个查询。在每个查询中,你必须输出 ∑ j = l + k − 1 r f ( [ a l , a l + 1 , … , a j ] ) \sum _{j=l+k-1}^{r} f([a_l, a_{l+1}, \ldots, a_j]) j=l+k1rf([al,al+1,,aj]) 。请注意,在这个版本中,你只需要输出 f ( [ a l , a l + 1 , … , a l + k − 1 ] ) f([a_l, a_{l+1}, \ldots, a_{l+k-1}]) f([al,al+1,,al+k1])

∗ ^{\text{∗}} 如果存在一个从索引 i i i ( 1 ≤ i ≤ ∣ b ∣ − k + 1 1 \leq i \leq |b|-k+1 1ibk+1 )开始的长度为 k k k 的连续子数组,那么对于所有的 i < j ≤ i + k − 1 i < j \leq i+k-1 i<ji+k1 都是 b j = b j − 1 + 1 b_j = b_{j-1} + 1 bj=bj1+1

思路:

对这种找公差为 1 1 1 的等差数列,有个技巧就是给每个位置上的数减去 i i i,这样转化后的数组如果连续的数相等,在原本的数组里这连续的数就是等差数列。

转化后对一个 l l l 其实就是找 l ∼ l + k − 1 l\sim l+k-1 ll+k1 k k k 个数里相等的数最多有多少个。而且这样的 l l l 最多也就是 n − k + 1 n-k+1 nk+1 个,我们直接用个滑动窗口一样的东西滑一遍,算出所有 l l l 的答案,然后直接查即可。

这个滑动窗口,需要用一个 m a p map map 存储一个数到这个数的个数的映射,为了查找某一时刻下最多的数的个数,我们还需要一个 m u l t i s e t multiset multiset 存储所有数的个数每次更新一个数的个数时,把这个数旧的个数删掉,再加入新的个数即可。

code:

#include <iostream>
#include <cstdio>
#include <map>
#include <set>
#define pii pair<int,int>
using namespace std;
const int maxn=2e5+5;

int T,n,k,q;
int a[maxn],ans[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k>>q;
		for(int i=1;i<=n;i++)cin>>a[i],a[i]-=i;
//		for(int i=1;i<=n;i++)cout<<a[i]<<" ";
//		cout<<endl;
		map<int,int> mp;
		multiset<int> S;
		for(int i=1;i<k;i++){
			if(mp.find(a[i])!=mp.end())S.erase(S.find(mp[a[i]]));
			mp[a[i]]++;
			S.insert(mp[a[i]]);
			
		}
		for(int l=1,r=k;l<=n;l++,r++){
			if(r<=n){
				if(mp.find(a[r])!=mp.end())S.erase(S.find(mp[a[r]]));
				mp[a[r]]++;
				S.insert(mp[a[r]]);
			}
			
			ans[l]=k-*S.rbegin();
			
			S.erase(S.find(mp[a[l]]));
			mp[a[l]]--;
			if(mp[a[l]]==0)mp.erase(a[l]);
			else S.insert(mp[a[l]]);
		}
		
		for(int i=1,l,r;i<=q;i++){
			cin>>l>>r;
			cout<<ans[l]<<endl;
		}
	}
	return 0;
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值