【51NOD 1816】小C的二分图

Description

小C有一个特殊的二分图(有着X部与Y部)。
对于一个X部的点x,对应在Y部的相邻点只会是一个连续区间。
然后你需要找一个最大匹配,这个匹配经过小C的膜法也变得特殊了。
两个匹配边只有当不相交时候才是小C的匹配(即对于一个比配xi->yi,xj->yj,如果xi

Solution

先来想一个比较靠谱的暴力,
对于Y部开一个数组,
对于每个X部的点,把它的Y部的 l r 从后往前遍历,对于每一个位置i,更新为:

fi=max(fi,max{f1...fi1}+1)

最后直接输出f的最大值即可
复杂度: O((rili+1))

想一下怎么优化,
首先发现如果把f变成不下降的,答案也不会变;
那么,每次的改变只是把一段相同的f的开头向前移动几位,
做每个X部的点时,如果一段相同f的开头在区间内,那么这个开头一定会移动到它前面一段的开头后面一位(当然不能移动到区间之外),(读者可以自己画图试试看)

那么,试着只记录每段相同的开头,那么每次操作只要把区间内的数更改即可,
我们发现,每次找到对应的区间后,把这个区间的最后一个数删掉,把剩下的数全部+1,再在区间前插入 l 这个数,这样就相当于把区间的每个数都改成它前面的数的+1(当然不能移动到区间之外);

这个用Splay维护一下,

复杂度:O(nlog(n))

Code

#include <cstdio>
#include <cstdlib>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define min(q,w) ((q)<(w)?(q):(w))
#define max(q,w) ((q)>(w)?(q):(w))
using namespace std;
const int N=300500;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,ans,mx;
int a[N][2],b0;
struct qqww
{
    int v,mi,la,mx,l,r,fa,si;
}b[N];
int root;
void merge(int q)
{
    b[q].si=b[b[q].l].si+1+b[b[q].r].si;
    b[q].mi=min(b[q].v,min(b[b[q].l].mi,b[b[q].r].mi));
    b[q].mx=max(b[q].v,max(b[b[q].l].mx,b[b[q].r].mx));
}
void doit(int q)
{
    if(!b[q].la)return;
    b[q].mi+=b[q].la,b[q].mx+=b[q].la;
    b[q].v+=b[q].la;
    if(b[q].l)b[b[q].l].la+=b[q].la;
    if(b[q].r)b[b[q].r].la+=b[q].la;
    b[q].la=0;
}
int search1(int q,int e)
{
    doit(b[q].l),doit(b[q].r);
    if(b[b[q].l].mx>e&&b[q].l)return search1(b[q].l,e);
    if(b[q].v>e||!b[q].r)return q;
    return search1(b[q].r,e);
}
int search2(int q,int e)
{
    doit(b[q].l),doit(b[q].r);
    if(b[b[q].r].mi<e&&b[q].r)return search2(b[q].r,e);
    if(b[q].v<e||!b[q].l)return q;
    return search2(b[q].l,e);
}
bool SD(int q){return b[b[q].fa].r==q;}
void rotate(int q)
{
    int t=b[q].fa;
    if(SD(t))b[b[t].fa].r=q;
        else b[b[t].fa].l=q;
    if(SD(q))b[t].r=b[q].l,b[b[q].l].fa=t,b[q].l=t;
        else b[t].l=b[q].r,b[b[q].r].fa=t,b[q].r=t;
    b[q].fa=b[t].fa;b[t].fa=q;
    merge(t),merge(q);
}
void Splay(int q,int T)
{
    while(b[q].fa!=T)
    {
        if(b[b[q].fa].fa!=T)
            if(SD(q)==SD(b[q].fa))rotate(b[q].fa);
                else rotate(q);
        rotate(q);
    }
    if(!T)root=q;
}
void delt(int q)
{
    q=search2(root,q);
    if(q==2)return;
    Splay(q,0);
    Splay(q=search2(b[q].r,-1),root);
    if(q==2)return;
    b[root].r=b[q].r;
    b[b[q].r].fa=root;
    merge(root);
}
void change_jia(int q,int w)
{
    Splay(q=search2(root,q),0);
    w=search1(root,w);
    if(q==w)return;
    Splay(w,root);
    q=b[w].l;if(!q)return;
    b[q].la++;
    doit(q);
    merge(b[q].fa),merge(root);
}
void change_join(int q1)
{
    int q;
    Splay(q=search2(root,q1),0);
    b0++;
    b[b0].fa=q;b[b0].v=q1;
    b[b0].r=b[q].r;b[b[q].r].fa=b0;
    b[q].r=b0;
    merge(b0),merge(root);
}
int main()
{
    read(n);
    fo(i,1,n)read(a[i][0]),read(a[i][1]);
    root=1;b[0].mi=b[0].v=1e9;
    b[1].r=2;
    b[2].fa=1,b[2].v=1e9;
    merge(2),merge(1);b0=2;
    fo(i,1,n)
    {
        delt(a[i][1]);
        change_jia(a[i][0],a[i][1]);
        change_join(a[i][0]);
    }
    printf("%d\n",b[root].si-2);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值