【CodeForces429E】Points and Segments(欧拉回路)

题目大意

n(1n105) 条线段,每条覆盖 [li,ri](1li,ri109,liri) 的区间,给每条线段染红色或蓝色,使得数轴上每个点(不一定是整点)的红色覆盖数与蓝色覆盖数差的绝对值小于等于1。输出任意一种染色方案。

题解

用到的欧拉回路性质

先说一说我们在本题中需要用到的一些欧拉回路的一些性质。
欧拉
(红色是从左往右走,蓝色是从右往左走)

当图的结点都在一个数轴上时,如果存在欧拉回路,
当一个点被一条边从左往右越过,或发出一条像有的边,因为存在环,所以这个点一定被另一条从右往左的边越过,或收到一条向左的边。
这样,每个点向右发出或越过的边一定等于向左接收或越过的边,利用这个性质红蓝染色,就可以解决本题。
注意:一个点接收到的向右的边,或发出的向左的边是没有计算在上述性质中的。

建图

我们需要考虑的是数轴上所有点,而不是整点。
可以发现数轴上很多点的覆盖情况是一样的,所以应该区间一段一段的考虑。
可以发现,这样的区间
区间1
与这样的区间
区间2
是没有区别的,也就是说,我们可以把右端点右移一点,没有影响。
我们甚至可以把上图的第一条线段的右端点右移到无限逼近与5,但和5中间还是有段空间。
可以把坐标5拆成 51,52 51 是一个无限逼近与5的位置, 52 就是5, 51 52 之间依然有距离。
然后把上面所说的点当做结点,将线段连边,第一条线段就是 1>51 连走无限次的边,每个数字的两个结点都要连边(这些边最后不染色,是无关的)。

为什么要把原本线段是 [1,4] 的,最后连成 [1,51] 呢?
因为线段 [1,4] 是包含4的,而前面讲的欧拉图性质是没有计算向右进入的边,如果直接连向4,相当于把4忽略掉了,所以连向5,保证了区间覆盖情况不变。
(实际上,实现时,每个数字并不需要拆成两个点,因为这两个点之间欧拉路直接带过,没有卵用,只是为了方便理解)

连好这些边后,有可能并不存在欧拉回路,因为还有很多奇点,而我们并不能随便把这些奇点连接起来,因为关于这些点的边有可能跨越了大片其它与之不连通的点,乱连会造成错误。(每条欧拉路径,没有环,都会造成一些点,覆盖的两种颜色正好差一,而欧拉路径多了,叠加后可能造成一种颜色过多,如下图,红色太多)
反例
注意到走完欧拉路径,起点和终点两个是奇点,它们这之间一定是颜色覆盖数不相等的,而其他区域都是红蓝相等。
由所以如果从一个奇点走到另一个奇点,中间如果还夹着其它奇点,就会造成中间一个区间,被多个奇点连线覆盖,导致这一区间多次缺少某一颜色。所以如果我们按顺序把相邻的奇点连在一起,就可以避免,原因:

  • 如果相邻的奇点是不同连通块,连起来后,连成一个连通块,会成为一个更大的连通块,而这个大连通块要么已经是回路(此时只有这两个奇点之间缺一颜色),要么仍有起点和终点,而起点和终点一定在这两个奇点后面了,永远无法影响此处,这里就完事了。
  • 如果相邻的奇点是同一连通块的起点和终点,连接后,以后其它的连通块也不会再影响这里。

所以,总体过程,把输入的线段右端点+1,然后离散化,然后直接连边,把相邻的奇点连边,跑欧拉路。从左往右走的路染红色,从右往左的路染蓝色。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=400005;
int id[MAXN*2],edge[MAXN][2];
struct Edge
{
    int u,v,segid;
    bool vis;
    Edge *next,*back;
}*V[MAXN*2],E[MAXN*2],*cur=E;
void add_edge(int a,int b,int i)
{
    cur->u=a;
    cur->v=b;
    cur->segid=i;
    cur->next=V[a];
    cur->back=cur+1;
    V[a]=cur++;
    cur->u=b;
    cur->v=a;
    cur->segid=i;
    cur->next=V[b];
    cur->back=cur-1;
    V[b]=cur++;
}
Edge *path[MAXN*2];
int pcnt;
void euler(int id)
{
    for(Edge *p=V[id];p;p=p->next)
        if(!p->vis)
        {
            p->vis=1;
            p->back->vis=1;
            euler(p->v);
            path[++pcnt]=p;
        }
}
bool vis[MAXN*2];
int deg[MAXN*2];
int obb[MAXN*2],ocnt;
bool ans[MAXN];
int main()
{
    int n,m,l,r;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",edge[i],edge[i]+1);
        edge[i][1]++;
        id[i*2-1]=edge[i][0];
        id[i*2]=edge[i][1];
    }
    sort(id+1,id+n*2+1);
    m=unique(id+1,id+n*2+1)-id-1;
    for(int i=1;i<=n;i++)
    {
        l=lower_bound(id+1,id+m+1,edge[i][0])-id;
        r=lower_bound(id+1,id+m+1,edge[i][1])-id;
        add_edge(l,r,i);
        deg[l]++;
        deg[r]++;
    }
    for(int i=1;i<=m;i++)
        if(deg[i]&1)
            obb[++ocnt]=i;
    for(int i=1;i<=ocnt;i+=2)
        add_edge(obb[i],obb[i+1],0);
    for(int i=1;i<=m;i++)
        if(!vis[i])
        {
            pcnt=0;
            euler(i);
            vis[path[pcnt]->u]=1;
            for(int j=pcnt;j>0;j--)
            {
                vis[path[j]->v]=1;
                ans[path[j]->segid]=(path[j]->u<path[j]->v);
            }
        }
    for(int i=1;i<n;i++)
        printf("%d ",(int)ans[i]);
    printf("%d\n",(int)ans[n]);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值