过年真的啥也没学,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,谢谢
性质很妙,当前区间 [ l , r ] [l,r] [l,r],则永远维护 a [ l ] < a [ l − 1 ] a[l] < a[l - 1] a[l]<a[l−1] 和 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,分情况:
- 当 z = x z=x z=x 且 z = y z = y z=y ,放在任意集合(等价)
- 当 z = x z= x z=x 或 z = y z= y z=y ,放在另一个集合,增加贡献
- 当 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,分情况:
- 当 z = x z=x z=x 且 z = y z = y z=y ,放在任意集合(等价),不加贡献
- 当 z = x z= x z=x 或 z = y z= y z=y ,放在另一个集合,不加贡献
- 当 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 叻
-
对原数列作相邻的同项合并
-
从头到尾遍历,用set来记录可能是两个队伍末尾的数字。
-
如果当前数字不能在set中,则加入set;
-
如果当前数字在set中,清空set,将当前位置与当前位置的前一个位置的两个数组加入set。
显然,set维护的其实是一连串不同的数字,通过不同的划分方案,这些数字都有可能成为两条队伍的末尾。
遍历过程中一旦出现一个数字,它在set中已经有了对应元素,那么这两个相同的数字一定能通过一个划分方案位置相邻,完成我们缩短队伍长度的目的。
在相邻之后,队伍末尾就确定下来了,一定是当前数字和上一个数字(可以理解为,把两个相同数字中间夹着的数字全部移动到另一个队伍里去了,那么最末尾的数字肯定是这个数的上一个数了)。
-
DP 挖个坑 1