【2-SAT】【优化连边】【AtCoder Regular Contest 069 F】Flag

35 篇文章 0 订阅
4 篇文章 0 订阅

Description

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

2-SAT

模型还是比较显然的。看到双最值想到二分,转为判定性问题
我们拆点,b[i][1]表示位置i选,b[i][0]表示不选
这时边的意义就是“推出”,比如说b[xi][0]向b[yi][1]连边,因为xi不放就必然要放yi
考虑限制条件:判定是否有方案,使得选择的点间距>=lim
对于每个点x,如果它选,那么b[x][1]就要向所有的dis(x,y)< lim 的b[y][0]连边,因为一旦选择了x,就不能选与它间距< lim的点
然而这样最坏情况下,边数是 O(n2) 的,我们需要优化连边
考虑到连边总是向一个去心领域内连,我们有对应的优化方法

分块优化连边

自己yy吧,很简单。这样会多出 O(n0.5) 个中转点

线段树优化连边

用线段树上的结点结构来优化,父节点向子节点连边。这样会多出 O(n) 个中转点

进行完连边优化之后,边数变成 O(n1.5) O(nlogn)

接着要求解2-SAT
2-SAT问题一般有两个解决方法

有向图的Tarjan算法

对构出的图跑Tarjan,对于任意x判定b[x][0],b[x][1]是否在同一连通块。若是则有矛盾。
不太清楚这种方法如何构造一组可行解。

构造一组可行解

17.12.27 UPD:可以构造可行解,但是只能保证是任意一种。
tarjan算法跑完之后,将SCC缩起来,将图连反边之后跑拓扑序(其实相当于跑拓扑逆序),每遇到一个未被染色的点就染成选,并将其对立点(预处理)染成不选。
例题:https://jzoj.net/senior/#main/show/4350

    while(!q.empty())
    {
        int u=q.front();q.pop();
        if(!col[u]) col[u]=1,col[inv[u]]=2;
        efo(i,u,v)
            if(!--in[v]) q.push(v);
    }

dfs染色 UPD:大暴力

对于当前遇到的每个未染色的点,尝试染b[x][0],如果不可行再染b[x][1]
一个点开始的dfs染色会把它所在的SCC的所有点的0/1确定下来
这样可以通过最后每个点的颜色来构造可行解

可是,我们线段树优化连边时新建了O(n)个点,如果很多个点都dfs重新染色,可能会重复经过这O(n)个点多次。这个复杂度我不太会算,但是这种方法实测很快,在Atcoder上交也过了(虽然说跑得挺慢)
17.12.27 UPD:
dfs染色过程算法本身就是O(nm)的,是2-SAT问题的暴力解法。用处大概是构造字典序最小解。

Code

分块优化连边+dfs染色

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v,u) for(int i=last[v],u=to[last[v]];i;i=nxt[i],u=to[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
void read(int &n)
{
    int t=0,p=1;char ch;
    for(ch=getchar();!('0'<=ch && ch<='9');ch=getchar())
        if(ch=='-') p=-1;
    for(;'0'<=ch && ch<='9';ch=getchar()) t=t*10+ch-'0';
    n=t*p;
}
const int N=4e5+5,M=50000005;
int n,nn,rtn,tot,ext,b[N][2];
struct node
{
    int x,id;
}a[N];
bool cmp(node a,node b){return a.x<b.x;}
int m,co[N],to[M],nxt[M],last[N],list[N],L[N],R[N];
void link(int u,int v)
{
    to[++m]=v,nxt[m]=last[u],last[u]=m;
}
bool dfs(int v)
{
    if(v<=tot && co[v^1]) return 0;
    if(v<=tot && co[v]) return 1;
    co[v]=1;
    list[++list[0]]=v;
    efo(i,v,u) if(!co[u])
        if(!dfs(u)) return 0;
    return 1;
}
bool check(int lim)
{
    m=0;mset(last,0);
    fo(i,1,n)
    {
        link(b[i+i-1][0],b[i+i][1]),link(b[i+i][1],b[i+i-1][0]);
        link(b[i+i-1][1],b[i+i][0]),link(b[i+i][0],b[i+i-1][1]);
    }
    fo(i,1,rtn)
        fo(j,L[i],R[i]) link(tot+i,b[a[j].id][0]);
    int l=1,r=1;
    fo(i,1,nn)
    {
        while(l<nn && a[i].x-a[l].x>=lim) ++l;
        while(r<nn && a[r+1].x-a[i].x<lim) ++r;
        int ql=min(rtn,(l-1)/rtn+1),qr=min(rtn,(r-1)/rtn+1),qi=min(rtn,(i-1)/rtn+1);
        if(ql==qr)
        {
            fo(j,l,r)
                if(j!=i) link(b[a[i].id][1],b[a[j].id][0]);
        }
        else
        {
            if(ql==qi)
                fo(j,l,i-1) link(b[a[i].id][1],b[a[j].id][0]);
            else
            {
                fo(j,l,R[ql]) link(b[a[i].id][1],b[a[j].id][0]);
                fo(j,ql+1,qi-1) link(b[a[i].id][1],tot+j);
                fo(j,L[qi],i-1) link(b[a[i].id][1],b[a[j].id][0]);
            }
            if(qi==qr)
                fo(j,i+1,r) link(b[a[i].id][1],b[a[j].id][0]);
            else
            {
                fo(j,i+1,R[qi]) link(b[a[i].id][1],b[a[j].id][0]);
                fo(j,qi+1,qr-1) link(b[a[i].id][1],tot+j);
                fo(j,L[qr],r) link(b[a[i].id][1],b[a[j].id][0]);
            }
        }
    }
    mset(co,0);
    fo(i,1,tot)
    {
        int x=i+i-1;
        if(!co[b[x][0]] && !co[b[x][1]])
        {
            list[0]=0;
            if(!dfs(b[x][0]))
            {
                fo(j,1,list[0]) co[list[j]]=0;
                list[0]=0;
                if(!dfs(b[x][1])) return 0;
            }
        }
    }
    return 1;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    int x,y;
    read(n);nn=n+n;
    fo(i,1,n) read(a[i+i-1].x),read(a[i+i].x);
    tot=1;
    fo(i,1,nn) a[i].id=i,b[i][0]=++tot,b[i][1]=++tot;
    rtn=sqrt(nn);
    fo(i,1,rtn-1) L[i]=1+rtn*(i-1),R[i]=rtn*i;
    L[rtn]=1+rtn*(rtn-1),R[rtn]=nn;
    sort(a+1,a+nn+1,cmp);
    int l=0,r=a[nn].x+1;
    while(l<r-1)
    {
        int mid=(l+r)>>1;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%d\n",l);
    return 0;
}

线段树优化连边+tarjan判可行

#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v,u) for(int i=last[v],u=to[last[v]];i;i=nxt[i],u=to[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
void read(int &n)
{
    int t=0,p=1;char ch;
    for(ch=getchar();!('0'<=ch && ch<='9');ch=getchar())
        if(ch=='-') p=-1;
    for(;'0'<=ch && ch<='9';ch=getchar()) t=t*10+ch-'0';
    n=t*p;
}
const int N=4e5+5,M=1e7+5;
int n,nn,vtot,tot,root,b[N][2];
struct node
{
    int x,id;
}a[N];
bool cmp(node a,node b){return a.x<b.x;}
int m,co[N],to[M],nxt[M],last[N],ls[N],rs[N];
void link(int u,int v)
{
    to[++m]=v,nxt[m]=last[u],last[u]=m;
}
void build(int &v,int l,int r)
{
    if(l==r)
    {
        v=b[a[l].id][0];
        return;
    }
    v=++tot;
    int mid=l+r>>1;
    build(ls[v],l,mid);build(rs[v],mid+1,r);
    link(v,ls[v]);link(v,rs[v]);
}
void add(int v,int l,int r,int x,int y,int p)
{
    if(x>y || l>r) return;
    if(l==x && r==y)
    {
        link(p,v);
        return;
    }
    int mid=l+r>>1;
    if(y<=mid) add(ls[v],l,mid,x,y,p);
    else
    if(x>mid) add(rs[v],mid+1,r,x,y,p);
    else add(ls[v],l,mid,x,mid,p),add(rs[v],mid+1,r,mid+1,y,p);
}
int now,top,num,low[N],dfn[N],scc[N],sta[N];
bool bz[N],vis[N];
void tarjan(int v)
{
    low[v]=dfn[v]=++now;
    bz[v]=vis[v]=1;
    sta[++top]=v;
    efo(i,v,u)
        if(!vis[u])
        {
            tarjan(u);
            low[v]=min(low[v],low[u]);
        }
        else
        if(bz[u]) low[v]=min(low[v],dfn[u]);
    if(low[v]==dfn[v])
    {
        sta[top+1]=0;++num;
        for(;sta[top+1]!=v;top--) bz[sta[top]]=0,scc[sta[top]]=num;
    }
}
bool check(int lim)
{
    m=0;mset(last,0);
    fo(i,1,n)
    {
        link(b[i+i-1][0],b[i+i][1]),link(b[i+i][1],b[i+i-1][0]);
        link(b[i+i-1][1],b[i+i][0]),link(b[i+i][0],b[i+i-1][1]);
    }
    tot=vtot;
    build(root,1,nn);
    int l=1,r=1;
    fo(i,1,nn)
    {
        while(l<nn && a[i].x-a[l].x>=lim) ++l;
        while(r<nn && a[r+1].x-a[i].x<lim) ++r;
        add(root,1,nn,l,i-1,b[a[i].id][1]);
        add(root,1,nn,i+1,r,b[a[i].id][1]);
    }
    now=top=num=0;mset(bz,0);mset(vis,0);mset(scc,0);
    fo(i,1,vtot)
        if(!vis[i]) tarjan(i);
    fo(i,1,nn)
        if(scc[b[i][0]]==scc[b[i][1]]) return 0;
    return 1;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    int x,y;
    read(n);nn=n+n;
    fo(i,1,n) read(a[i+i-1].x),read(a[i+i].x);
    vtot=1;
    fo(i,1,nn) a[i].id=i,b[i][0]=++vtot,b[i][1]=++vtot;
    sort(a+1,a+nn+1,cmp);
    int l=0,r=a[nn].x+1;
    while(l<r-1)
    {
        int mid=l+r>>1;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%d\n",l);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值