【题解】atcoder2336 Flags

该博客介绍了如何解决atcoder上的2336 Flags问题,通过二分最大最小距离并转化为2-SAT问题。博主讨论了如何优化加边过程,利用虚拟顶点和线段树将边数降至O(n log n),从而解决原问题的复杂度为O(n^2)的难题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

题意:给定n个数对(xi,yi),从每个数对中选出一个数,使得得到的n个数之间的最小距离最大。求最大的最小距离。

分析:二分最大的最小距离mid。当选出某个数x后,对于数对(xi,yi),若|x-xi|<mid,则不能选xi,从而必须选yi。因此可以把问题转化为2-sat问题。如果暴力加边,那么最坏情况下会有O(n^2)条边,因此要对加边进行优化。注意到对每个数x,所加的边在某种意义是连续的(将2*n个数按其值从小到大排序之后),因此增加O(n)个虚拟顶点,用线段树维护加边,从而总边数变为O(nlgn)级别的。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5;
struct node
{
	int x,i;
	bool operator < (const node &o) const
	{
		return x<o.x;
	}
}nd[maxn];
int n,mp[maxn];
int sz,lc[maxn],rc[maxn];
vector<int> G[maxn];
void init()
{
	for (int i=0;i<maxn;i++) G[i].clear();
	sz=2*n;
}
void add(int u,int v)
{
	G[u].push_back(v);
}
void build(int l,int r)
{
	int o=sz++;
	if (l==r)
	{
		lc[o]=rc[o]=0;add(o,mp[nd[l].i^1]);
		return ;
	}
	int mid=(l+r)/2;
	lc[o]=sz;build(l,mid);
	rc[o]=sz;build(mid+1,r);
	add(o,lc[o]);add(o,rc[o]);
}
int get1(int x)
{
	int l=0,r=2*n-1,mid,ret;
	while (l<=r)
	{
		mid=(l+r)/2;
		if (nd[mid].x>=x)
		{
			ret=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	return ret;
}
int get2(int x)
{
	int l=0,r=2*n-1,mid,ret;
	while (l<=r)
	{
		mid=(l+r)/2;
		if (nd[mid].x<=x)
		{
			ret=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	return ret;
}
void work(int o,int l,int r,int ql,int qr,int p)
{
	if (ql<=l&&r<=qr)
	{
		add(p,o);return ;
	}
	int mid=(l+r)/2;
	if (ql<=mid) work(lc[o],l,mid,ql,qr,p);
	if (qr>mid) work(rc[o],mid+1,r,ql,qr,p);
}
int dfs_clock,scc_cnt,sccno[maxn],pre[maxn],low[maxn];
stack<int> S;
void dfs(int u)
{
	pre[u]=low[u]=++dfs_clock;
	S.push(u);
	for (int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if (!pre[v])
		{
			dfs(v);low[u]=min(low[u],low[v]);
		}
		else if (!sccno[v]) low[u]=min(low[u],pre[v]);
	}
	if (low[u]==pre[u])
	{
		scc_cnt++;
		for (;;)
		{
			int x=S.top();S.pop();
			sccno[x]=scc_cnt;
			if (x==u) break;
		}
	}
}
void tarjan(int n)
{
	dfs_clock=scc_cnt=0;
	memset(sccno,0,sizeof(sccno));
	memset(pre,0,sizeof(pre));
	for (int i=0;i<n;i++) if (!pre[i]) dfs(i);
}
bool check(int mid)
{
	init();
	build(0,2*n-1);
	for (int i=0;i<2*n;i++)
	{
		int i1=get1(nd[i].x-mid+1),i2=get2(nd[i].x+mid-1);
		if (i1<i) work(2*n,0,2*n-1,i1,i-1,i);
		if (i<i2) work(2*n,0,2*n-1,i+1,i2,i);
	}
	tarjan(sz);
	for (int i=0;i<2*n;i++) if (sccno[i]==sccno[mp[nd[i].i^1]]) return 0;
	return 1;
}
int main()
{
	cin>>n;
	for (int i=0;i<2*n;i++) scanf("%d",&nd[i].x),nd[i].i=i;
	sort(nd,nd+2*n);
	for (int i=0;i<2*n;i++) mp[nd[i].i]=i;
	int l=1,r=1e9,mid,ans=0;
	while (l<=r)
	{
		mid=(l+r)/2;
		if (check(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值