BZOJ4881: [Lydsy2017年5月月赛]线段游戏

本文介绍了一款关于线段的游戏算法实现,通过分析线段之间的相交情况来确定游戏的胜负策略。文章提供了完整的代码实现,并详细解释了二分图染色及线段树的应用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4881

Description

quailty和tangjz正在玩一个关于线段的游戏。在平面上有n条线段,编号依次为1到n。其中第i条线段的两端点坐
标分别为(0,i)和(1,p_i),其中p_1,p_2,...,p_n构成了1到n的一个排列。quailty先手,他可以选择一些互不相交
的线段,将它们拿走,当然他也可以一条线段也不选。然后tangjz必须拿走所有剩下的线段,若有两条线段相交,
那么他就输了,否则他就赢了。注意若quailty拿走了全部线段,那么tangjz也会胜利。quailty深深喜欢着tangjz
,所以他不希望tangjz输掉游戏,请计算他有多少种选择线段的方式,使得tangjz可以赢得游戏。

Input

第一行包含一个正整数n(1<=n<=100000),表示线段的个数。
第二行包含n个正整数p_1,p_2,...,p_n(1<=p_i<=n),含义如题面所述。

Output

输出一行一个整数,即tangjz胜利的方案数,因为答案很大,请对998244353取模输出。

Sample Input

5
1 2 4 5 3

Sample Output

8

若线段 i j 相交,那么在它们之间连一条边。若这个图不是二分图,那么无解,否则令 cnt 为连通块个数,那么 ans = 2cnt

 

在二分图染色的过程中,每个点只需要被访问一次。对于当前所在的点 x,它可以一步走到 [1; xp[i> p[x的所有i,以及 (x; np[j< p[x的所有j。用线段树维护所有没走过

的点,记录每个区间 p 最小与最大的两个位置。每次贪心取出最大/小的,看看是否满足条件,若满足则删除该点,然后递归染色,否则终止。

时间复杂度 O(n log n)

//二分图染色,线段树加速
#include<cstdio>
#include<cstdlib>
const int N=100010,M=262150;
int n,i,a[N],ma[M],mi[M],pos[N],tmp,col[N],f[3],ret,ans;
inline int mergema(int x,int y){
  if(!x||!y)return x+y;
  return a[x]>a[y]?x:y;
}
inline int mergemi(int x,int y){
  if(!x||!y)return x+y;
  return a[x]<a[y]?x:y;
}
inline void up(int x){
  ma[x]=mergema(ma[x<<1],ma[x<<1|1]);
  mi[x]=mergemi(mi[x<<1],mi[x<<1|1]);
}
void build(int x,int a,int b){
  if(a==b){
    ma[x]=mi[x]=a;
    pos[a]=x;
    return;
  }
  int mid=(a+b)>>1;
  build(x<<1,a,mid),build(x<<1|1,mid+1,b);
  up(x);
}
inline void del(int x){
  x=pos[x];
  ma[x]=mi[x]=0;
  for(x>>=1;x;x>>=1)up(x);
}
void askma(int x,int a,int b,int c,int d){
  if(c<=a&&b<=d){
    tmp=mergema(tmp,ma[x]);
    return;
  }
  int mid=(a+b)>>1;
  if(c<=mid)askma(x<<1,a,mid,c,d);
  if(d>mid)askma(x<<1|1,mid+1,b,c,d);
}
void askmi(int x,int a,int b,int c,int d){
  if(c<=a&&b<=d){
    tmp=mergemi(tmp,mi[x]);
    return;
  }
  int mid=(a+b)>>1;
  if(c<=mid)askmi(x<<1,a,mid,c,d);
  if(d>mid)askmi(x<<1|1,mid+1,b,c,d);
}
void dfs(int x,int y){
  del(x);
  col[x]=y;
  y=3-y;
  while(1){
    tmp=0;
    askmi(1,1,n,x,n);
    if(tmp&&a[tmp]<a[x])dfs(tmp,y);else break;
  }
  while(1){
    tmp=0;
    askma(1,1,n,1,x);
    if(tmp&&a[tmp]>a[x])dfs(tmp,y);else break;
  }
}
int main(){
  scanf("%d",&n);
  for(i=1;i<=n;i++)scanf("%d",&a[i]);
  build(1,1,n);
  for(i=1;i<=n;i++)if(!col[i])dfs(i,1),ret++;
  for(i=1;i<=n;i++){
    if(a[i]<f[col[i]])return puts("0"),0;
    f[col[i]]=a[i];
  }
  ans=1;
  while(ret--)ans=ans*2%998244353;
  printf("%d",ans);
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值