2020 Multi-University Training Contest 5---- HDU--6824、Exam (2-sat,线段树优化建图)

题目链接

题面:
在这里插入图片描述

题意:
n n n 门考试,每门考试有两个时间段可以进行 [ a , a + a t ] , [ b , b + b t ] [a,a+at],[b,b+bt] [a,a+at],[b,b+bt],每门考试必须选择其中的一个时间段进行,且两门考试的时间严格不相交。即 [ 3 , 5 ] , [ 5 , 7 ] [3,5],[5,7] [3,5],[5,7]进行两门考试是不合法的。

问完成 n n n 门考试的最短时间。

题解:
在这里插入图片描述

上面是官方题解:
我们把考试转换为 2 − s a t 2-sat 2sat 问题(显然要转化为 2 − s a t 2-sat 2sat)。
我们设考试 i i i 选择 [ a , a + a t ] [a,a+at] [a,a+at] 时间段的变量为 i i i,选择 [ b , b + b t ] [b,b+bt] [b,b+bt] 时间段的变量为 i ′ i' i

但是题目要求输出完成所有考试的最小时间,而 2 − s a t 2-sat 2sat 只能解决有解和无解的问题,那么很容易就能想到,我们可以把这个问题转化为判定性问题。即 二分完成所有考试的最小时间,然后和判定在当前最小时间下是否有解即可。

假设当前二分的值为 m i d mid mid,如果 a + a t > m i d a+at>mid a+at>mid,那么说明考试 i ′ i' i 是必选的,则 i − > i ′ i->i' i>i
如果 b + b t > m i d b+bt>mid b+bt>mid,那么就说明考试 i i i 是必选的,则 i ′ − > i i'->i i>i

剩下的只需要将有冲突的考试场次连边即可。
如果 [ a i , a i + a t i ] [a_i,a_i+at_i] [ai,ai+ati] [ a j , a j + a t j ] [a_j,a_j+at_j] [aj,aj+atj] 有交,则说明是冲突的,则 i − > j ′ , j − > i ′ i->j',j->i' i>j,j>i

如果 [ a i , a i + a t i ] [a_i,a_i+at_i] [ai,ai+ati] [ b j , b j + b t j ] [b_j,b_j+bt_j] [bj,bj+btj] 有交,则说明是冲突的,则 i − > j , j ′ − > i ′ i->j,j'->i' i>j,j>i

如果 [ b i , b i + b t i ] [b_i,b_i+bt_i] [bi,bi+bti] [ a j , a j + a t j ] [a_j,a_j+at_j] [aj,aj+atj] 有交,则说明是冲突的,则 i ′ − > j ′ , j − > i i'->j',j->i i>j,j>i

如果 [ b i , b i + b t i ] [b_i,b_i+bt_i] [bi,bi+bti] [ b j , b j + b t j ] [b_j,b_j+bt_j] [bj,bj+btj] 有交,则说明是冲突的,则 i ′ − > j , j ′ − > i i'->j,j'->i i>j,j>i

对于答案,我们发现如果答案存在,那么最优的结束时间一定是在某一场比赛的结束。
即我们只需要记录下 { a + a t , b + b t } \{a+at,b+bt\} {a+at,b+bt} 这个集合,然后排序,在其里面二分即可。

现在新的问题来了。
虽然上述解法可以解决当前问题,但是时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn),空间复杂度为 O ( n 2 ) O(n^2) O(n2),是我们所不能接受的。

我们将 [ a , a + a t ] , [ b , b + b t ] [a,a+at],[b,b+bt] [a,a+at],[b,b+bt] 都记为一个四元组 ( i d n , i d f , a , a + a t ) (idn,idf,a,a+at) (idn,idf,a,a+at),其中 i d n idn idn 选择当前区间的变量, i d f idf idf选择另一区间的变量。
例如:
对于 [ a i , a i + a t i ] [a_i,a_i+at_i] [ai,ai+ati] 记为 ( i d n = i , i d f = i + n , a i , a i + a t i ) (idn=i,idf=i+n,a_i,a_i+at_i) (idn=i,idf=i+n,ai,ai+ati)
对于 [ b i , b i + b t i ] [b_i,b_i+bt_i] [bi,bi+bti] 记为 ( i d n = i + n , i d f = i , b i , b i + b t i ) (idn=i+n,idf=i,b_i,b_i+bt_i) (idn=i+n,idf=i,bi,bi+bti)

考虑对于某一场考试 i i i,我们只需要考虑与其冲突的的考试 j j j a i ≤ a j ≤ a i + a t i a_i\le a_j\le a_i+at_i aiajai+ati,那么起始就涵盖了所有的情况。

我们将所有的考试按照考试的开始时间排序,那么我们的 a i ≤ a j ≤ a i + a t i a_i\le a_j\le a_i+at_i aiajai+ati,中的 a j a_j aj 一定是一段连续的区间。

也就是说,我们对于某一个点,考虑一个区间 ,类似于 x − > [ l , r ] x->[l,r] x>[l,r]
所以我们来考虑线段树优化建图。

我们枚举 i ∈ [ 1 , n ∗ 2 ] i\in[1,n*2] i[1,n2],找到 a i ≤ a j ≤ a i + a t i a_i\le a_j\le a_i+at_i aiajai+ati,假设区间为 [ l , r ] [l,r] [l,r],那么我们如果选择 i i i,则不能选择区间 [ l , r ] [l,r] [l,r] 中的任何一个点,所以我们从 i ( i d n i ) i(idn_i) i(idni) [ l , r ] [l,r] [l,r] 的每个点的 i d f idf idf 连边。如果我们选择了 [ l , r ] [l,r] [l,r] 区间中的任意一个点,那么不能选择 i i i,那么我们从 [ l , r ] [l,r] [l,r] 区间的每个点的 i d n idn idn i ′ ( i d f i ) i'(idf_i) i(idfi) 连边。

所以:
我们的 x − > [ l , r ] x->[l,r] x>[l,r] 线段树每个父亲向儿子节点连边,最终叶子节点向每个点的 i d f idf idf 连边。
我们的 [ l , r ] − > x [l,r]->x [l,r]>x 线段树每个儿子向父亲节点连边,每个点的 i d n idn idn 向叶子节点连边。

这里我们的叶子节点不共用,而是每棵树的叶子节点都都有自己的编号之后再向 i d f idf idf i d n idn idn 连边。因为 i d f idf idf i d n idn idn 会出现编号相同但是在不同的叶子节点上的问题(即两个序列不一致)。

时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),空间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码:

#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<unordered_set>
#include<set>
#include<ctime>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
//#define lc (cnt<<1)
//#define rc (cnt<<1|1)
#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define fhead(x) for(int i=head[(x)];i;i=nt[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;

const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const double alpha=0.75;
const int mod=1e9+7;
const double eps=1e-8;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=100100;
const int maxm=100100;
const int maxp=100100;
const int up=1100;

int head[maxn<<3],ver[maxn<<5],nt[maxn<<5],tot=1;


void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}

int n;
struct node
{
    int s,t;
    int idn,idf;
}a[maxn<<1];

bool cmp(const node &a,const node &b)
{
    return a.s<b.s;
}

struct tree
{
    int l,r,lc,rc;
}t[maxn<<3];
int rtin,rtout,cnt=0;

int build(int l,int r,bool flag)
{
    int p=++cnt;
    t[p].l=l,t[p].r=r;
    t[p].lc=t[p].rc=0;
    if(l==r)
    {
        flag?add(p,a[l].idf):add(a[l].idn,p);
        return p;
    }
    t[p].lc=build(l,tmid,flag);
    t[p].rc=build(tmid+1,r,flag);
    flag?add(p,t[p].lc):add(t[p].lc,p);
    flag?add(p,t[p].rc):add(t[p].rc,p);
    return p;
}

void  add(int x,int l,int r,int p,bool flag)
{
    if(l<=t[p].l&&t[p].r<=r)
    {
        flag?add(x,p):add(p,x);
        return ;
    }
    if(l<=t[t[p].lc].r) add(x,l,r,t[p].lc,flag);
    if(r>=t[t[p].rc].l) add(x,l,r,t[p].rc,flag);
}

int dfn[maxn<<3],low[maxn<<3],st[maxn<<3],c[maxn<<3];
int sum=0,top=0,index=0;
bool ha[maxn<<3];
void tarjan(int x)
{
    dfn[x]=low[x]=++sum;
    st[++top]=x,ha[x]=true;

    for(int i=head[x];i;i=nt[i])
    {
        if(!dfn[ver[i]])
        {
            tarjan(ver[i]);
            low[x]=min(low[x],low[ver[i]]);
        }
        else if(ha[ver[i]])
            low[x]=min(low[x],low[ver[i]]);
    }

    if(dfn[x]==low[x])
    {
        index++;
        int y;
        do
        {
            y=st[top--];
            ha[y]=false;
            c[y]=index;
        }while(x!=y);
    }
}

bool check(void)
{
    for(int i=1;i<=cnt;i++)
        dfn[i]=low[i]=ha[i]=c[i]=0;
    sum=top=index=0;
    for(int i=1;i<=cnt;i++)
        if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++)
        if(c[i]==c[i+n]) return false;
    return true;
}

int d[maxn<<3],e[maxn<<3];

void init(int n)
{
    for(int i=1;i<=n;i++) head[i]=0,t[i].lc=t[i].rc=0;
    tot=1;
    rtin=rtout=cnt=0;

}

int main(void)
{
    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            ++cnt;
            scanf("%d%d",&a[cnt].s,&a[cnt].t);
            a[cnt].idn=i,a[cnt].idf=i+n;
            a[cnt].t+=a[cnt].s;
            ++cnt;
            scanf("%d%d",&a[cnt].s,&a[cnt].t);
            a[cnt].idn=i+n,a[cnt].idf=i;
            a[cnt].t+=a[cnt].s;
        }
        sort(a+1,a+n*2+1,cmp);
        rtin=build(1,n*2,true);
        rtout=build(1,n*2,false);

        for(int i=1;i<=n*2;i++)
            d[i]=a[i].s,e[i]=a[i].t;
        for(int i=1;i<=n*2;i++)
        {
            int pos=upper_bound(d+1,d+n*2+1,a[i].t)-d-1;
            if(pos<=i) continue;
            add(a[i].idn,i+1,pos,rtin,true);
            add(a[i].idf,i+1,pos,rtout,false);
        }

        int l=1,r=n*2;
        int pos=-1,mid;
        sort(e+1,e+n*2+1);
        while(l<=r)
        {
            mid=(l+r)>>1;
            for(int i=1;i<=n*2;i++)
            {
                if(a[i].t>e[mid])
                    add(a[i].idn,a[i].idf);
            }

            if(check()) pos=mid,r=mid-1;
            else l=mid+1;

            for(int i=1;i<=n*2;i++)
            {
                if(a[i].t>e[mid])
                    head[a[i].idn]=nt[head[a[i].idn]],tot--;
            }
        }
        printf("%d\n",pos==-1?-1:e[pos]);
        init(cnt);

    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值