【问题描述】
奶牛贝茜是卡牌游戏的狂热爱好者,但是令人吃惊的,她缺乏对手。不幸的是,任何牧
群里的其他牛都不是好对手。
他们实在是太差了,实际上,他们玩卡牌游戏时会遵循一种完全可以被预测的模式。然
而对于贝茜来说,找到赢的方法仍然是一个挑战。
贝茜和他的朋友埃尔西最近在玩一个简单的卡牌游戏,总共有 2N 张卡牌,上面的数字
为 1−2N, 贝茜分得 N 张,埃尔西分得 N 张。
他们玩 N 局游戏,每局游戏双方都出一张牌。
最初,数字大的得 1 分,输了不得分。但是,贝茜可以在游戏的某个时刻改变游戏规则
(有且仅有一次),对于剩下的游戏,数字小的得 1 分,输了不得分。贝茜可以不做出这个
选择,整局都是“高分卡片赢”,或者一开始就改变规则“低分卡片赢”。
给出贝茜预测的埃尔西将要使用的 N 张卡片,求出贝茜的得分最大值。
【输入】
第一行一个整数 n
接下来 n 行每行一个整数 x, 表示埃尔西拥有的卡片数字很简单就能推测出贝茜拥有的卡片。
【输出】
只有一行一个整数 max,为得分最大值。
【输入输出样例 1】
Input
4
1
8
4
3
Output
3
【样例提示】
这里,贝茜一定拥有卡片 2,5,6,7,最多可以赢 3 分
以下是两种(不止两种 3 分方案)
- 4 -Elsie Bessie Bessie Score
1 < 2 1
8 > 5 0
4 < 6 1
3 < 7 1
no change rules(一直大者得分)
Elsie Bessie Bessie Score
刚开始大者得分
1 < 7 1
change rules(从此之后小者得分)
8 > 6 1
4 > 2 1
3 < 5 0
【数据范围】
对于 10%的数据,n<=100
对于 30%的数据, n<=2000
对于 100%的数据,n<=50000
【题解】
这道题有几种做法:(然而我只掌握了方法一)
方法一:
贪心+维护前缀后缀和
类似于田忌赛马,只不过没有重复的卡片
我们使用两个set,第一个set对于Elsie的当前卡片,找到最小的比当前卡片大的Bessie卡片(以最小的代价得分)
如果没有卡片比当前卡片大,使用最小的卡片”以最小的代价毁掉了”大卡片,维护前i个卡片最多能”高分赢”的得分数
第二个set反其道而行之,维护后i个卡片最少能”低分赢”的得分数
则答案就是{max(pre[i]+sum[i+1])}
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<set>
#define fp(i,a,b) for(int i=a;i<=b;i++)
#define fq(i,a,b) for(int i=a;i>=b;i--)
#define il inline
#define ll long long
using namespace std;
bool l[100005]={};
int n,ans=0,pre[100005]={},las[100005]={},s[100005]={};//pre前缀,las后缀,s是Elsie的数字序列,a是Bessie升序,b是Bessie降序
set<int> a;
set<int,greater<int> >b;
il int gi()
{
int x=0;
short int t=1;
char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
int main()
{
freopen("cardgame.in","r",stdin);
freopen("cardgame.out","w",stdout);
set<int>::iterator ac;
set<int,greater<int> >::iterator bc;
int x;n=gi();
fp(i,1,n)
x=gi(),s[i]=x,l[x]=1;
fp(i,1,2*n)
if(!l[i]) a.insert(i),b.insert(i);
int tot=0;
fp(i,1,n)
{
ac=a.upper_bound(s[i]);
if(ac==a.end()) a.erase(a.begin());//如果没有更大的,以小抵大
else a.erase(ac),tot++;//否则找到最小的比当前值大的
pre[i]=tot;//维护高分前缀
}
tot=0;
fq(i,n,1)
{
bc=b.upper_bound(s[i]);
if(bc==b.end()) b.erase(b.begin());//如果没有更小的,以大抵小
else b.erase(bc),tot++;
las[i]=tot;//维护低分后缀
}
fp(i,0,n) ans=max(ans,pre[i]+las[i+1]);//注意边界,枚举改变规则的分界点
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}
【n^2部分】
枚举分割点,然后分割点上面的排序,分割点下面的排序,做个贪心。
【nlogn】
还是枚举分割点,然后我们需要快速地知道分割点上面的答案f[i]以及分割点下面的答案g[i+1]。以f为例看看怎么求。
许多人会想到排序贪心,这搞了半天又回归到部分分。我们不必贪心,用线段树那样的分治就好了。
把WWT的牌叫做a,你的牌叫做b。考虑一棵值域线段树(轴为1~2n),我插入1个a[i],它只能被a[i]+1~2n打掉;我插入1个b[i],它只能打掉1~b[i]-1。
于是我们每个区间维护两个值:ta表示区间内有多少个a没被打掉,tb表示区间内有多少个b剩余。(想想如果a在区间的很右边,那它在这个区间内就打不掉的了,得留着放在更大的区间来打。b如果在区间很左边,那它在这个区间内打不到a的话,就留着在更大的区间去打)
接下来就是两个区间的合并。假设当前区间的值为ta、tb,左儿子为ta[l]、tb[l],右儿子为ta[r]、tb[r]。那么ta=ta[r]+max(0,ta[l]-tb[r]),tb=tb[l]+max(0,tb[r]-ta[l])。如果你理解了上面那段话,这个合并就很好理解了。以ta为例,ta[l]是可以被tb[r]干掉的,而ta[r]则不能被干掉,所以就这样合并啦。。。
回归本题。我们还是以f为例:顺序处理每一局。对于第i局,插入a,然后插入b,然后直接询问根节点的ta。注意根节点的ta就表示第1局到第i局共输了多少局,所以f[i]=i-ta。g数组同理。这样就做完啦。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int maxn=(1e5)+5;
int n,m,a[maxn],b[maxn];
int tr1[4*maxn][2],tr2[4*maxn][2]; //第二维的0表示ta,1表示tb
void tr_xg1(int k,int l,int r,int x,int z)
{
if (l==r)
{
if (z==1) tr1[k][0]=1; else tr1[k][1]=1;
return;
}
int t=k<<1, t1=(l+r)>>1;
if (x<=t1) tr_xg1(t,l,t1,x,z); else tr_xg1(t+1,t1+1,r,x,z);
tr1[k][0]=tr1[t][0]+max(0,tr1[t+1][0]-tr1[t][1]);
tr1[k][1]=tr1[t+1][1]+max(0,tr1[t][1]-tr1[t+1][0]);
}
void tr_xg2(int k,int l,int r,int x,int z)
{
if (l==r)
{
if (z==1) tr2[k][0]=1; else tr2[k][1]=1;
return;
}
int t=k<<1, t1=(l+r)>>1;
if (x<=t1) tr_xg2(t,l,t1,x,z); else tr_xg2(t+1,t1+1,r,x,z);
tr2[k][0]=tr2[t+1][0]+max(0,tr2[t][0]-tr2[t+1][1]);
tr2[k][1]=tr2[t][1]+max(0,tr2[t+1][1]-tr2[t][0]);
}
bool bz[maxn];
int f[maxn],g[maxn];
int main()
{
freopen("cardgame.in","r",stdin);
freopen("cardgame.out","w",stdout);
scanf("%d",&n);
m=2*n;
fo(i,1,n)
{
scanf("%d",&a[i]);
bz[a[i]]=1;
}
int b0=0;
fd(i,m,1) if (!bz[i]) b[++b0]=i;
fo(i,1,n)
{
tr_xg1(1,1,m,a[i],-1);
tr_xg1(1,1,m,b[i],1);
f[i]=i-tr1[1][1];
}
fd(i,n,1)
{
tr_xg2(1,1,m,a[i],-1);
tr_xg2(1,1,m,b[i],1);
g[i]=(n-i+1)-tr2[1][1];
}
int ans=0;
fo(i,0,n) ans=max(ans,f[i]+g[i+1]);
printf("%d\n",ans);
}
方法三:
前面做一遍,后面做一遍,开线段树存一存每个点所能得到的最大分数,扫一遍最大值, OVER(正确性:前面是最大,后面是最小,不会重叠)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int maxn=1<<17;
struct node
{
int co,canco,points;
}h[maxn*2];
int r[maxn+10],r1[maxn+10],n,a[maxn],b[maxn];
bool check[maxn*2];
node comb(node x,node y)
{
node r;
int ep=min(x.canco,y.co);
r.points=x.points+y.points+ep;
r.co=x.co+y.co-ep;
r.canco=x.canco+y.canco-ep;
return r;
}
void fix(int x)
{
while(x!=1)
{
x>>=1;h[x]=comb(h[2*x],h[2*x+1]);
}
}
void solve(vector<int> a,vector<int> b)
{
memset(h,0,sizeof(h));
for(int i=0;i<n;i++)
{
h[maxn+b[i]].co=1;
h[maxn+a[i]].canco=1;
fix(maxn+b[i]);fix(maxn+a[i]);
r[i+1]=h[1].points;
}
}
void solve1(vector<int> a,vector<int> b)
{
memset(h,0,sizeof(h));
for(int i=0;i<n;i++)
{
h[maxn+b[i]].co=1;
h[maxn+a[i]].canco=1;
fix(maxn+b[i]);fix(maxn+a[i]);
r1[i+1]=h[1].points;
}
}
int main()
{
freopen("cardgame.in","r",stdin);
freopen("cardgame.out","w",stdout);
scanf("%d",&n);
vector<int> a(n);
vector<int>b;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
a[i]--;check[a[i]]=1;
}
for(int i=2*n-1;i>=0;i--)
if(!check[i]) b.push_back(i);
solve(a,b);
reverse(a.begin(),a.end());
reverse(b.begin(),b.end());
for(int i=0;i<n;i++)
{
a[i]=2*n-1-a[i];
b[i]=2*n-1-b[i];
}
solve1(a,b);
int res=0;
for(int i=0;i<=n;i++)
res=max(res,r[i]+r1[n-i]);
printf("%d\n",res);
return 0;
}