[JZOJ5446]【NOIP2017提高A组冲刺11.3】高考是不可能高考的(2-SAT问题)【Atcoder regular context 069F】

Description

Snuke 喜欢旗子.
Snuke 正在将N 个旗子摆在一条线上.
第i 个旗子可以被放在位置xi 或yi 上.
Snuke 认为两个旗子间的最小距离越大越好. 请你求出最大值
对于100% 的数据, 1 <= N <= 10^4; 1 <= xi, yi <= 10^9

Solution

首先二分答案

那么考虑相互关系

二分出来的答案使得,选了某一个,就会让其他的某些不能选
然后使得不能选的对应点成为必选

那么就是N个布尔变量(因为同一个旗子要么放x,要么放y,可以看作是真或者假)

然后有一些布尔关系,
即选了某一个,必须选某些不能选的对应点

那么就是经典的2-SAT问题

直接按照必选关系连边,跑Tarjan判断同一个旗子的x,y是否在同一个强联通分量中,或者直接跑标记判断是否矛盾

直接连边边数是N^2级别的

可以用线段树或者分块,添加辅助点增加点数,优化连边

总的复杂度线段树 O(Nlog2N) ,分块 O(NNlogN)

Code

我打的是分块

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 1000005
#define M 10000005
using namespace std;
int n,a[N][2],fs[N],m1,nt[M],dt[M],t[2*N],rt[N],n1,m2;
bool bz[2*N];
struct node
{
    int w,p;
    friend bool operator <(node x,node y)
    {
        return x.w<y.w;
    }
}a1[2*N];
void link(int x,int y)
{
    nt[++m1]=fs[x];
    dt[fs[x]=m1]=y;
}
bool make(int k,int v)
{
    bz[k]=v;
    if(bz[k]&&bz[t[k]]) return 0;
    for(int i=fs[k];i;i=nt[i])
    {
        if(bz[dt[i]]!=v) 
        {
            if(!make(dt[i],v)) return 0; 
        }
    }
    return 1;
}
int get(int k) 
{
    return (k-1)/(int)sqrt(2*n)+1;
}
bool pd(int lim)
{
    memset(fs,0,sizeof(fs));
    memset(bz,0,sizeof(bz));
    m1=m2;
    int ls=1;
    fo(i,1,2*n)
    {
        int x=get(i),lt;
        while(a1[i].w-a1[ls].w>=lim) ls++;
        lt=get(ls);
        fo(j,lt+1,x-1) link(i,rt[j]);
        fo(j,ls,i-1) 
        {
            if(get(j)!=lt) break;
            link(i,t[j]),link(j,t[i]);
        }
        if(lt!=x)
        fod(j,i-1,ls)
        {
            if(get(j)!=x) break;
            link(i,t[j]),link(j,t[i]);
        }
    }
    fo(i,1,n)
    {
        if(bz[a[i][1]]&&bz[a[i][0]]) return 0;
        if(!bz[a[i][0]]&&!bz[a[i][1]])
        {
            if(!make(a[i][0],1)||bz[a[i][1]]) 
            {
                make(a[i][0],0);
                if(!make(a[i][1],1)||bz[a[i][0]]) return 0;
            }
        }
    }
    return 1;
}
int main()
{
    cin>>n;
    fo(i,1,n)
    { 
        int x,y;
        scanf("%d%d",&x,&y);
        a1[2*i-1].w=x,a1[2*i].w=y;
        a1[2*i-1].p=a1[2*i].p=i;
    }
    sort(a1+1,a1+2*n+1);
    n1=2*n;
    fo(i,1,2*n) 
    {
        if(a[a1[i].p][0]==0) a[a1[i].p][0]=i;
        else a[a1[i].p][1]=i,t[i]=a[a1[i].p][0],t[t[i]]=i;
    }
    fo(i,1,2*n) 
    {
        if(rt[get(i)]==0) rt[get(i)]=++n1;
        link(n1,t[i]);
    }
    m2=m1;
    int l=0,r=1802201963;
    while(l+1<r)
    {
        int mid=(l+r)/2;
        if(pd(mid)) l=mid;
        else r=mid;
    }
    if(pd(r)) printf("%d\n",r);
    else printf("%d\n",l);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值