jzoj4684. 【GDOI2017模拟8.11】卡牌游戏
作者:互联网
原来想这道题时田忌赛马问题。。。从贪心想。。。一直没想出来。。。naive
事实上,这道题其实就是田忌赛马问题,但是有添加操作,所以单纯排序不行了
注意到d同学的贪心策略也可以表示为如下:(默认规则为牌大的赢)
1.将d同学的牌从小到大排序
2.从小到大枚举d同学的每一张牌,对于现在这张牌,找到p同学手上第一个比它小的牌,删去这张牌(没有就不删)
另一种规则类似
我们显然不可以暴力维护,考虑权值线段树
每次只添加一张牌,相当于单点修改,每次添加的牌直接加到其权值上
对每个节点记l1表示d同学还有几张牌,r1表示p同学有几张牌,c1表示答案个数
将树上每个节点右儿子的l1用于消去左儿子的r1,然后上传到该节点
这样为什么是正确的呢?因为这样子可以让d的牌首先和区间较小但是对应值大的p的牌匹配,再让未匹配好的d的牌和祖先的左儿子的区间更大而对应值小的p的牌匹配(有点绕,菜鸡博主语文太差,请理解)
只要访问根节点即可完成查询
最后对n+1种规则改变情况取最大值就好了
代码:
#include<bits/stdc++.h> using namespace std; #define N 100010 int n,a[N],b[N],v[N],f[N],g[N],l1[N*10],r1[N*10],c1[N*10],l2[N*10],r2[N*10],c2[N*10],ans; void i1(int o,int l,int r,int x,int t){ if(l==r){ if(!t)l1[o]++; else r1[o]++; return; } int md=(l+r)/2; if(x<=md)i1(o*2,l,md,x,t); else i1(o*2+1,md+1,r,x,t); c1[o]=r1[o]=l1[o]=0; if(l1[o*2+1]>=r1[o*2]){ c1[o]+=r1[o*2]; l1[o]+=l1[o*2+1]-r1[o*2]; } else{ c1[o]+=l1[o*2+1]; r1[o]+=r1[o*2]-l1[o*2+1]; } r1[o]+=r1[o*2+1]; l1[o]+=l1[o*2]; c1[o]+=c1[o*2]+c1[o*2+1]; } void i2(int o,int l,int r,int x,int t){ if(l==r){ if(!t)l2[o]++; else r2[o]++; return; } int md=(l+r)/2; if(x<=md)i2(o*2,l,md,x,t); else i2(o*2+1,md+1,r,x,t); c2[o]=l2[o]=r2[o]=0; if(l2[o*2]>=r2[o*2+1]){ c2[o]+=r2[o*2+1]; l2[o]+=l2[o*2]-r2[o*2+1]; } else{ c2[o]+=l2[o*2]; r2[o]+=r2[o*2+1]-l2[o*2]; } r2[o]+=r2[o*2]; l2[o]+=l2[o*2+1]; c2[o]+=c2[o*2]+c2[o*2+1]; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); v[a[i]]=1; } int ct=0; for(int i=1;i<=2*n;i++) if(!v[i])b[++ct]=i; reverse(b+1,b+n+1); for(int i=1;i<=n;i++){ i1(1,1,n*2,a[i],1); i1(1,1,n*2,b[i],0); f[i]=c1[1]; } ans=max(ans,f[n]); for(int i=n;i;i--){ i2(1,1,n*2,a[i],1); i2(1,1,n*2,b[i],0); ans=max(ans,c2[1]+f[i-1]); } printf("%d",ans); }
标签:r1,r2,int,jzoj4684,卡牌,l2,l1,c1,GDOI2017 来源: https://www.cnblogs.com/rilisoft/p/11106583.html