【NOIP2017提高A组模拟9.23】碎 题解

7 篇文章 0 订阅
4 篇文章 0 订阅

【NOIP2017提高A组模拟9.23】碎 题解

2 − S A T 2-SAT 2SAT详解请看这里

题目描述

在这里插入图片描述
在这里插入图片描述

解题方法

40分思路

设第一瓣陌生度为 f A f_A fA,第二瓣为 f B f_B fB
枚举 f A f_A fA,二分枚举 f B f_B fB(假设 f A ≥ f B f_A\geq f_B fAfB),然后用 2 − S A T 2-SAT 2SAT判断一下。

2 − S A T 2-SAT 2SAT判断

这里的就把每个点拆成是 A A A图还是 B B B图,用两个点。

  1. 大于 f A f_A fA的边,我们不能让他们在同一个集合,所以连成这样:
    在这里插入图片描述
  2. f B f_B fB f A f_A fA范围内的边,我们只能放在 A A A集合或者不放,所以连成这样:
    在这里插入图片描述

满分思路

然后考虑优化。
我们用并查集从大到小建图,然后判断环。

情况1

如果这是一个图的偶环,那么最短边不可能与答案有关,舍去。

情况2

如果这是一个图的奇环,那么最短边是最第一瓣陌生度的下界,也就是说,第一瓣的陌生度一定大于等于这条边,所以只要看大于等于它的边,因为是从大到小,所以直接 b r e a k break break

这就是优化,可以做到 O ( n 3 log ⁡ 2 n ) O(n^3\log_2^n) O(n3log2n)
听说好可以做到 O ( n 3 ) O(n^3) O(n3),不知道。

代码

#include<bits/stdc++.h>
#define N 160005
using namespace std;
struct Node{
	int x,y,v;
}a[N];
int A[N],M,f[N],g[N];
int n,m,ans,cnt,la[N],low[N],dfn[N],sta[N],sum,top,col[N],tot,vis[N],to[N],ne[N];
bool cmp(Node a1,Node a2){
	return a1.v>a2.v;
}
void add(int x,int y){
	to[++cnt]=y;
	ne[cnt]=la[x];
	la[x]=cnt;
}
void add1(int x,int y){
	add(x+n,y);
	add(x,y+n);
	add(y+n,x);
	add(y,x+n);
}
void add2(int x,int y){
	add(x+n,y);
	add(y+n,x);
}
void tarjan(int x){
	dfn[x]=low[x]=++tot;
	vis[x]=1;
	sta[++top]=x;
	for(int i=la[x];i;i=ne[i]){
		int y=to[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else{
			if(vis[y]) low[x]=min(low[x],dfn[y]);
		}
	}
	if(dfn[x]==low[x]){
		int y=0;
		sum++;
		while(y^x){
			y=sta[top--];
			vis[y]=0;
			col[y]=sum;
		}
	}
}
int check(int da,int db){
	memset(la,0,sizeof(la));
	cnt=0;
	for(int i=1;i<=m;i++)
		if(a[i].v>da) add1(a[i].x,a[i].y);
		else if(a[i].v>db) add2(a[i].x,a[i].y);
	memset(vis,0,sizeof(vis));
	memset(dfn,0,sizeof(dfn));
	memset(col,0,sizeof(col));
	sum=top=tot=0;
	for(int i=1;i<=(n<<1);i++)
		if(!dfn[i])
			tarjan(i);
	for(int i=1;i<=n;i++)
		if(col[i]==col[i+n])
			return 0;
	return 1;
}
int find(int x){
	if(x==f[x]) return x;
	else{
		int y=find(f[x]);
		g[x]^=g[f[x]];
		return f[x]=y;
	}
}
int main(){
	freopen("broken.in","r",stdin);
	freopen("broken.out","w",stdout);
	scanf("%d",&n);
	ans=2000000001;
	for(int i=1;i<n;i++)
		for(int j=i+1;j<=n;j++){
			int x;
			scanf("%d",&x);
			a[++m].v=x;
			a[m].x=i;
			a[m].y=j;
		}
	sort(a+1,a+m+1,cmp);
	M=0;
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=m;i++){
		int x=find(a[i].x);
		int y=find(a[i].y);
		if(x==y){
			if(g[a[i].x]==g[a[i].y]){
				A[++M]=a[i].v;
				break;
			}
		}
		else{
			if(x>y) swap(x,y);
			f[y]=x;
			g[y]=g[a[i].x]^g[a[i].y]^1;
			A[++M]=a[i].v;
		}
	}
	for(int i=1;i<=M;i++){
		if(A[i]>=ans) continue;
		if(check(A[i],0)){
			ans=min(ans,A[i]);
			continue;
		}
		int l=1,r=A[i],sum=-1;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(A[i],mid)) r=mid-1,sum=mid;
			else l=mid+1;
			if(sum+A[i]>=ans) break;
		}
		if(sum!=-1) ans=min(ans,sum+A[i]);
	}
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值