• 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<'0'||c>'9')) c=getchar();return c;}
    int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
    int read()
    {
        int x=0,f=1;char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
        while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),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
    ";
    #else
        const char LL[]="%lld
    ";
    #endif
        n=read();
        for (int i=1;i<=n;i++) fa[i]=i,a[i]=read(),tree[i]=n+1;a[0]=0,a[n+1]=n+1;
        pre[0]=0;for (int i=1;i<=n;i++) chkmax(pre[i]=pre[i-1],i);
        suf[n+1]=n+1;for (int i=n;i>=1;i--) chkmin(suf[i]=suf[i+1],i);
        for (int i=1;i<=n;i++)
        if (pre[i]!=i&&suf[i]!=i) {cout<<0;return 0;}
        else
        {
            if (suf[i]!=i) fa[find(i)]=find(suf[i]);
            if (pre[i]!=i) fa[find(i)]=find(query(n+1-a[i]));
            ins(n+1-a[i],i);
        }
        for (int i=1;i<=n;i++) if (find(i)==i) cnt++;
        int ans=1;while (cnt--) ans=(ans<<1)%P;
        cout<<ans;
        return 0;
    }

      另一种暴力是一个显然的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<'0'||c>'9')) c=getchar();return c;}
    int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
    int read()
    {
        int x=0,f=1;char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
        while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    int n,a[N],L[N<<2],R[N<<2],tree[N<<2],lazy[N<<2];
    void update(int k){tree[k]=0,lazy[k]=1;}
    void down(int k){update(k<<1),update(k<<1|1),lazy[k]=0;}
    void up(int k){tree[k]=(tree[k<<1]+tree[k<<1|1])%P;}
    void build(int k,int l,int r)
    {
        L[k]=l,R[k]=r;
        if (l==r) return;
        int mid=l+r>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,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]>>1;
        if (p<=mid) add(k<<1,p,x);
        else add(k<<1|1,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]>>1;
        if (r<=mid) return query(k<<1,l,r);
        else if (l>mid) return query(k<<1|1,l,r);
        else return (query(k<<1,l,mid)+query(k<<1|1,mid+1,r))%P;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("bzoj4881.in","r",stdin);
        freopen("bzoj4881.out","w",stdout);
        const char LL[]="%I64d
    ";
    #else
        const char LL[]="%lld
    ";
    #endif
        n=read();
        for (int i=1;i<=n;i++) a[i]=read();
        build(1,0,n);add(1,0,2);
        for (int i=2;i<=n;i++)
        {
            int x=query(1,0,a[i]);
            if (a[i]<a[i-1]) update(1);
            add(1,a[i-1],x);
        }
        cout<<tree[1];
        return 0;
    }
  • 相关阅读:
    内存映射文件点滴
    [Buzz.Today]“估摸”手机:Google收购摩托罗拉
    QT程序中的事件处理
    又是一个开始
    HTTP 协议基础
    深入理解HTTP消息头
    HTTP请求模型和头信息
    十个让你变成糟糕的程序员的行为
    获取上层调用函数地址的代码
    MIME类型大全
  • 原文地址:https://www.cnblogs.com/Gloid/p/10025835.html
Copyright © 2020-2023  润新知