前言
这题要是放NOIP说不定就让我退役了
题目
讲解
为了方便,我们令最终剩下的数为(s)
part1 35pts
首先我们用记忆化搜索轻松写出(O(N^2))的算法
定义(dp[w][i][j])表示此时左端点为(i),右端点为(j),此时是(w(0/1))人取的时候的最优取值
代码就不给出了
part2 45pts
我们考虑(K=0)小B没有女朋友的时候答案应该如何快速求出
考虑分奇偶讨论
1.如果(N)为奇数
(s)一定出自中间三个数,令他们依次为(t_1,t_2,t_3)
考虑如果答案不是出自中间三个数
- 小A想取左边或者右边的数,这样一定对小B不优,小B就会在对面取数,中间的数始终不变,答案一定在中间三个数中产生
- 小B想取左边或者右边的数,这样一定对小A不优,从第三次取数开始,类似的,小A也会在小B的对边取数,这样答案也一定在中间三个数中产生
当最后剩下(t_1,t_2,t_3)三个数的时候,一定是小A取数,如果(t_2)最小,那么小B一定可以使它被取到
如果(t_2)不是最小,小A自己不可能取走(t_1,t_3)中一个最大的数,他一定会取走最小的数,剩下一个次大值和最
大值,而小B一定会取走最大值,那么(s)即为次大值
2.如果(N)为偶数
类似的,(s)一定出自中间的(t_1,t_2)两个数
此时轮到小A取数,取走最小值,剩下最大值
于是我们就可以(O(1))求出(K=0)时的答案了
我称这个结论为中心论
part3 65pts
笔者在考场上就只码出了这个部分分,结果艹过了70pts...
明显part2推论还可以拓展为求剩下区间为([l,r])的时候,小A先取数时的(s)
对应代码即为:
int Get(int l,int r)
{
if(l == r) return a[l];
if((r-l+1) & 1)
{
int t1 = (l+r-1)/2,t2 = (l+r-1)/2+1,t3 = (l+r-1)/2+2;
t[1] = a[t1]; t[2] = a[t2]; t[3] = a[t3];
if(t[2] < t[1] && t[2] < t[3]) return t[2];
else {sort(t+1,t+3+1);return t[2];}
}
else
{
int t1 = (l+r-1)/2,t2 = (l+r-1)/2+1;
return Max(a[t1],a[t2]);
}
}
当然这部分的代码也可以替换part1的记忆化搜索,毕竟这是(O(1))的查询,对于每个(K)求答案时可以做到(O(N)),共(O(N^2))
我们对于每个(K)可以(O(N))枚举提前取数区间求出
共(O(N^2)),如果(Kge 0),即为(O(N))
part4 100pts
现在我们考虑拿走边上的数会造成什么影响
由于我们分奇偶讨论,拿走奇数个的时候会对奇偶性造成影响,而奇偶性不同的时候求(s)的方法不同,不能合起来讨论,所以我们试图分析拿走偶数个的时候会发生什么
考虑拿走两个数
如果一边拿走一个数,中心不会发生改变!所以(s)并不会改变!
如果单边拿走两个数,中心发生偏移,但是偏移不多,我们可以用两次(Get)操作求出这左右两种取数后的(s),与前一种取数方案的(s)取一个最大值,即为当前(K)的答案
于是可以(O(N))递推
但是注意,当(K=N-1)的时候,此时我们的中心论
已经不成立了,因为所有数都可能成为答案,需要特判
代码
int Get(int l,int r)
{
if(l == r) return a[l];
if((r-l+1) & 1)
{
int t1 = (l+r-1)/2,t2 = (l+r-1)/2+1,t3 = (l+r-1)/2+2;
t[1] = a[t1]; t[2] = a[t2]; t[3] = a[t3];
if(t[2] < t[1] && t[2] < t[3]) return t[2];
else {sort(t+1,t+3+1);return t[2];}
}
else
{
int t1 = (l+r-1)/2,t2 = (l+r-1)/2+1;
return Max(a[t1],a[t2]);
}
}
int ans[MAXN];
void solve_all()
{
ans[0] = Get(1,n);
ans[1] = Max(Get(1,n-1),Get(2,n));
//初始化递推边界
for(int i = 2;i < n;++ i) ans[i] = Max(ans[i-2],Max(Get(1,n-i),Get(1+i,n)));
//O(N)递推,从i-2的状态推过来
for(int i = 1;i <= n;++ i) ans[n-1] = Max(ans[n-1],a[i]);
//特判,因为 "中心论" 不适用了
}
void solve()
{
if(k >= 0)
{
int len = n-k,Ans = 0;
for(int i = 1;i+len-1 <= n;++ i)
Ans = Max(Ans,Get(i,i+len-1));
Put(Ans);
//当然这并没有必要,只是常数优化
}
else
{
solve_all();
for(int i = 0;i < n;++ i)
Put(ans[i],' ');
}
}
int main()
{
// freopen("game3.in","r",stdin);
// freopen("mine.out","w",stdout);
n = Read(); k = Read();
for(int i = 1;i <= n;++ i) a[i] = Read();
solve();
return 0;
}
个人代码有些冗长,如果你不想看的话可以康康下面的std
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,k,a[N],ans[N];
int gt(int l,int r)
{
int mid=(l+r)/2,len=r-l+1;
if(len&1)return max(min(a[mid-1],a[mid]),min(a[mid],a[mid+1]));
return max(a[mid],a[mid+1]);
}
int main()
{
// freopen("game.in", "r", stdin);
// freopen("game.out", "w", stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
ans[0]=gt(1,n);ans[1]=max(gt(1,n-1),gt(2,n));
for(int i=2;i<n;i++)ans[i]=max(ans[i-2],max(gt(1,n-i),gt(i+1,n)));
for(int i=1;i<=n;i++)ans[n-1]=max(ans[n-1],a[i]);
if(k>=0)printf("%d
",ans[k]);
else for(int i=0;i<n;i++)printf("%d ",ans[i]);
return 0;
}