codeforces #700 div2 CD1D2

过年真的啥也没学,so现在来补

也是等一个官方题解和网上blog啥的

C

题意: 给一个n的排列,但是你不知道这个排列具体是什么你只知道长度为n,你可以进行最多100次询问,每次会回答你每个下标的值是多少,需要找一个下标x,使得$a[x - 1] < a[x] < a[x + 1] $ , a [ 0 ] a[0] a[0] a [ n + 1 ] a[n + 1] a[n+1]为无穷

交互题,但是没有想得那样先自乱阵脚,交互这个模式倒是没有影响到什么,大概是题面引导的不错

看到区间最值,100次询问我没思路啊,挺那啥的我拿模拟退火冲了好多发全wa7,谢谢

参考blog

性质很妙,当前区间 [ l , r ] [l,r] [l,r],则永远维护 a [ l ] < a [ l − 1 ] a[l] < a[l - 1] a[l]<a[l1] a [ r ] < a [ r + 1 ] a[r] < a[r+ 1] a[r]<a[r+1] ,缩小区间一定有解,

考虑题解的100次询问也确实很二分

code // 很多细节参考了blog(几乎抄了,自己写wa22,哈哈连二分都不会写

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int n;
int a[N];

void get(int x)
{
    
    
	if( x>n||x<1||a[x] ) return;
    cout << "? " << x << endl;
    fflush(stdout);
    cin >> a[x]; 	
}

int main() {
    
    
    cin >> n;
    a[0] = a[n + 1] = 1e9;
    get(1); get(n);
 	int l = 1, r = n;
    while(l < r) {
    
    
        int mid = l+r>>1;
        get(mid); get(mid+1);
        if(  a[mid] > a[mid+1] ) l = mid+1;//
        else	r = mid;
    }
    cout << "! " << r << endl;
    fflush(stdout);
}

D1

题意: 给一个数列,让你将它不变顺序的分成两个部分,将两个部分中连续相等的数合并,问两个部分中最多的数的和是多少

也是赛时想了很久,当时是有数据能把自己卡掉,但是一时半会想不出正解这样子

看了官方题解和朋友的写法,

官方: (证明好长我没看,但是确实好用心(),贪心

定义两个集合s、t,定义集合中最后加入的元素分别为x、y,刚开始都为空,从前往后遍历整个数列,设当前取到的值是z,分情况:

  1. z = x z=x z=x z = y z = y z=y ,放在任意集合(等价)
  2. z = x z= x z=x z = y z= y z=y ,放在另一个集合,增加贡献
  3. z ≠ x z \neq x z=x z ≠ y z \neq y z=y , 看一下数列中下一个x、y的位置,如果next(x) < next(y) ,放在s中,否则放在t 中

朋友的 (和我wa掉的写法挺接近(但你wa了啊

首先连续的数就分成两类:只有1个或者大于1个,

两个集合,也是看集合中最后加入的元素(我懒得写了,接下来和官方差不多了)

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int a[N], pos[N], nxt[N]; 

int main() {
    
     
	int n;
	cin >> n;
	for(int i = 1; i <= n; ++ i) {
    
    
		cin >> a[i]; 
	}
	for(int i = 0; i <= n; ++ i) {
    
     // 预处理next(x) 
		pos[i] = n + 1;
	}
	for(int i = n; i >= 0; -- i) {
    
    
		nxt[i] = pos[a[i]];
		pos[a[i]] = i;
	}
	
	int x = 0, y = 0;
	int ans = 0;
	
	for(int i = 1; i <= n; ++ i) {
    
    
		if(a[x] == a[i]) {
    
    
			ans += (a[i] != a[y]);
			y = i;
		}
		else if(a[y] == a[i]) {
    
    
			ans += (a[i] != a[x]);
			x = i;
		}
		else if(nxt[x] < nxt[y]) {
    
    
			ans += (a[i] != a[x]);
			x = i;
		}
		else {
    
    
			ans += (a[i] != a[y]);
			y = i;
		}
	} 
	
	cout << ans;
} 

D2

题意: 把D1中的最大变成最小

也是同样可以用贪心的做法去做,

贪心:

定义两个集合s、t,定义集合中最后加入的元素分别为x、y,刚开始都为空,从前往后遍历整个数列,设当前取到的值是z,分情况:

  1. z = x z=x z=x z = y z = y z=y ,放在任意集合(等价),不加贡献
  2. z = x z= x z=x z = y z= y z=y ,放在另一个集合,不加贡献
  3. z ≠ x z \neq x z=x z ≠ y z \neq y z=y , 看一下数列中下一个x、y的位置,如果next(x) > next(y) ,放在s中,否则放在t 中

只要稍微改一下D1的代码就行,

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int a[N], pos[N], nxt[N]; 

int main() {
    
     
	int n;
	cin >> n;
	for(int i = 1; i <= n; ++ i) {
    
    
		cin >> a[i]; 
	}
	for(int i = 0; i <= n; ++ i) {
    
     // 预处理next(x) 
		pos[i] = n + 1;
	}
	for(int i = n; i >= 0; -- i) {
    
    
		nxt[i] = pos[a[i]];
		pos[a[i]] = i;
	}
	
	int x = 0, y = 0;
	int ans = 0;
	
	for(int i = 1; i <= n; ++ i) {
    
    
		if(a[x] == a[i]) {
    
    
			//ans += (a[i] != a[x]);
			x = i;
		}
		else if(a[y] == a[i]) {
    
    
			//ans += (a[i] != a[y]);
			y = i;
		}
		else if(nxt[x] > nxt[y]) {
    
    
			ans += (a[i] != a[x]);
			x = i;
		}
		else {
    
    
			ans += (a[i] != a[y]);
			y = i;
		}
	} 
	
	cout << ans;
} 

set:// 这题的做法真的很多啊 // 这里直接copy w同学的blog

  1. 对原数列作相邻的同项合并

  2. 从头到尾遍历,用set来记录可能是两个队伍末尾的数字。

    1. 如果当前数字不能在set中,则加入set;

    2. 如果当前数字在set中,清空set,将当前位置与当前位置的前一个位置的两个数组加入set

      显然,set维护的其实是一连串不同的数字,通过不同的划分方案,这些数字都有可能成为两条队伍的末尾。

    遍历过程中一旦出现一个数字,它在set中已经有了对应元素,那么这两个相同的数字一定能通过一个划分方案位置相邻,完成我们缩短队伍长度的目的。

    在相邻之后,队伍末尾就确定下来了,一定是当前数字和上一个数字(可以理解为,把两个相同数字中间夹着的数字全部移动到另一个队伍里去了,那么最末尾的数字肯定是这个数的上一个数了)。

DP 挖个坑 1

猜你喜欢

转载自blog.csdn.net/qq_39602052/article/details/113818924