2020河南省赛F题——二分+2-SAT

题目链接

http://acm.zzuli.edu.cn/problem.php?cid=1566&pid=5

题解

首先不难想到可以二分枚举答案,然后再判断枚举的答案是否合法,因此关键就在于这个怎么能够快速地判断是否合法了。

根据题意我们可以发现一个景点最多有两种状态,要么不是商业区(值为a[i],用i来表示),要么是商业区(值为b[i],用i+n表示)。

因此可以联想到图论中经典的2-SAT问题,之后就只需要考虑怎么建图了。

 我们可以直接枚举每条边的两个点的所有状态,即x不是商业区和y不是商业区,x是商业区和y不是商业区,x不是商业区和y是商业区,以及x是商业区和y是商业区这四种情况。之后就需要判断这四个情况是否合法(即两个点的差值的绝对值是否小于等于我们二分枚举的答案mid)。

但是这里我们不能直接判断是否满足小于等于的条件,这样二分的时候不好判断,我们需要反向思考。

当两个点的差值的绝对值大于mid时,即表示这两个点的状态不合法,因此只能一个点取原来的状态,另一个点取相反的状态。

举个例子,当x选择a数组(即不是商业区),y选择a数组(不是商业区)时不符合条件(差值的绝对值大于mid)时。

那就只能满足下面两个条件:

要么如果x选择a数组,y就必须选择b数组。

要么如果y选择a数组,x就必须选择b数组。

转换为代码的话就是

 if(abs(a[x]-a[y])>mid) add(x,y+n),add(y,x+n);

剩下的三种情况类比上面进行建图就行了,然后就是2-sat的模板,对于这个算法不懂的可以参考:https://www.luogu.com.cn/blog/85514/post-2-sat-xue-xi-bi-ji

代码

#include <bits/stdc++.h>
#define PI atan(1.0)*4
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pil pair<int,ll>
#define m_p make_pair
#define p_b push_back
#define ins insert
#define era erase
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define dg if(debug)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n";
using namespace std;
int debug = 0;
ll gcd(ll a,ll b){
    return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b){
    return a/gcd(a,b)*b;
}
inline int read(){
    int s=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s*f;
}
const int N = 1e6+7;
const int maxn=1e6+7;
const int maxm=2e6+7;
int a[N],b[N];
int u[N],v[N];
int n,m;
struct node{
    int to,nex;
}e[maxm];
int dfncnt,sccnum,scc[maxn],dfn[maxn],low[maxn],s[maxn],top;
int head[maxn],cnt;
void add(int u,int v){//链式前向星
    e[++cnt].to=v,e[cnt].nex=head[u],head[u]=cnt;
}
void tarjan(int u){//tarjan求强联通
    dfn[u]=low[u]=++dfncnt;
    s[++top]=u;
    for(int i=head[u];i;i=e[i].nex){
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!scc[v]){
            low[u]=min(low[u],low[v]);
        }
    }
    if(dfn[u]==low[u]){//弹栈
        ++sccnum;
        while(s[top]!=u){
            scc[s[top]]=sccnum;//强连通分量编号
            top--;
        }
        scc[s[top]]=sccnum;
        top--;
    }
}
void init(){
    memset(head,-1,sizeof head);
    memset(e,0,sizeof e);
    memset(scc,0,sizeof scc);
    memset(dfn,0,sizeof dfn);
    memset(low,0,sizeof low);
    memset(s,0,sizeof s);
    cnt=dfncnt=top=sccnum=0;
}
int check(int mid){
    init();
    rp(i,1,m){
        int x=u[i],y=v[i];
        if(abs(a[x]-a[y])>mid) add(x,y+n),add(y,x+n);
        if(abs(a[x]-b[y])>mid) add(x,y),add(y+n,x+n);
        if(abs(b[x]-a[y])>mid) add(x+n,y+n),add(y,x);
        if(abs(b[x]-b[y])>mid) add(x+n,y),add(y+n,x);
    }
    rp(i,1,2*n) if(!dfn[i]) tarjan(i);
    rp(i,1,n) if(scc[i]==scc[i+n]) return 0;
    return 1;
}
void solve(){
    n=read(),m=read();
    rp(i,1,n) a[i]=read();
    rp(i,1,n) b[i]=read();
    rp(i,1,m) u[i]=read(),v[i]=read();
    int l=0,r=INF;
    int ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
 
}
int main(){
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifdef ONLINE_JUDGE
#else
    freopen("in.txt", "r", stdin);
    //debug = 1;
#endif
    time_t beg, end;
    //if(debug) beg = clock();
 
    solve();
 
    /*
    if(debug) {
        end = clock();
        printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
    }
    */
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值