BZOJ4881 线段游戏(二分图+树状数组/动态规划+线段树)

  相当于将线段划分成两个集合使集合内线段不相交,并且可以发现线段相交等价于逆序对。也即要将原序列划分成两个单增序列。由dilworth定理,如果存在长度>=3的单减子序列,无解,可以先判掉。

  这个时候有两种显然的暴力。

  将点集划分成两部分使内部无边显然就是二分图,于是第一种暴力是在逆序对之间连边,答案即为2连通块个数,因为每个连通块都可以交换黑白点。问题在于暴力连边是n2的,而显然实际有用的边其实只有O(n)条。考虑这样一种连边方式:每个点向后缀最小值、前缀第一个比他大的点连边。瞎归纳归纳就可以证明连这些边就够了。这个前缀第一个比他大的随便找都行,比如弄个bit。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 100010
#define P 998244353
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<''||c>'')) c=getchar();return c;}
int gcd(int n,int m){return m==?n:gcd(m,n%m);}
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
int n,a[N],pre[N],suf[N],fa[N],tree[N],cnt;
inline void chkmax(int &x,int y){if (a[y]>a[x]) x=y;}
inline void chkmin(int &x,int y){if (a[y]<a[x]) x=y;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void ins(int k,int x){while (k<=n) tree[k]=min(tree[k],x),k+=k&-k;}
int query(int k){int s=n;while (k) s=min(s,tree[k]),k-=k&-k;return s;}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj4881.in","r",stdin);
freopen("bzoj4881.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read();
for (int i=;i<=n;i++) fa[i]=i,a[i]=read(),tree[i]=n+;a[]=,a[n+]=n+;
pre[]=;for (int i=;i<=n;i++) chkmax(pre[i]=pre[i-],i);
suf[n+]=n+;for (int i=n;i>=;i--) chkmin(suf[i]=suf[i+],i);
for (int i=;i<=n;i++)
if (pre[i]!=i&&suf[i]!=i) {cout<<;return ;}
else
{
if (suf[i]!=i) fa[find(i)]=find(suf[i]);
if (pre[i]!=i) fa[find(i)]=find(query(n+-a[i]));
ins(n+-a[i],i);
}
for (int i=;i<=n;i++) if (find(i)==i) cnt++;
int ans=;while (cnt--) ans=(ans<<)%P;
cout<<ans;
return ;
}

  另一种暴力是一个显然的dp,即设f[i][j]为dp到第i位时,不包含i的集合的最大值是第j个数的方案数。则有f[i][i-1]=Σf[i-1][j] (a[i]>a[j],j<i-1),f[i][j]=f[i-1][j] (a[i]>a[i-1],j<i-1)。将dp数组看成一维的,显然就可以用线段树优化了,即开一棵以a[]为下标的线段树,对f[i][i-1]在线段树上查询前缀更新,如果a[i]<a[i-1]就给整个线段树清零。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 100010
#define P 998244353
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<''||c>'')) c=getchar();return c;}
int gcd(int n,int m){return m==?n:gcd(m,n%m);}
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
int n,a[N],L[N<<],R[N<<],tree[N<<],lazy[N<<];
void update(int k){tree[k]=,lazy[k]=;}
void down(int k){update(k<<),update(k<<|),lazy[k]=;}
void up(int k){tree[k]=(tree[k<<]+tree[k<<|])%P;}
void build(int k,int l,int r)
{
L[k]=l,R[k]=r;
if (l==r) return;
int mid=l+r>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
}
void add(int k,int p,int x)
{
if (L[k]==R[k]) {tree[k]+=x;return;}
if (lazy[k]) down(k);
int mid=L[k]+R[k]>>;
if (p<=mid) add(k<<,p,x);
else add(k<<|,p,x);
up(k);
}
int query(int k,int l,int r)
{
if (L[k]==l&&R[k]==r) return tree[k];
if (lazy[k]) down(k);
int mid=L[k]+R[k]>>;
if (r<=mid) return query(k<<,l,r);
else if (l>mid) return query(k<<|,l,r);
else return (query(k<<,l,mid)+query(k<<|,mid+,r))%P;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj4881.in","r",stdin);
freopen("bzoj4881.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read();
for (int i=;i<=n;i++) a[i]=read();
build(,,n);add(,,);
for (int i=;i<=n;i++)
{
int x=query(,,a[i]);
if (a[i]<a[i-]) update();
add(,a[i-],x);
}
cout<<tree[];
return ;
}
上一篇:leetcode-174. Dungeon Game 地下城游戏


下一篇:[Scoi2014]方伯伯的玉米田 二维树状数组+动态规划