2018深圳-Balance of the Force-(思维+二分图+孪生问题)

72 篇文章 1 订阅
54 篇文章 2 订阅

B

题意:
就是给你n个人,然后每个点有两个权值,然后有两个阵营,每个人可以进黎明阵营那么权值就是第一个,进黑暗阵营权值就是第二个。还有m个条件就是a和b不能在一个阵营。然后问你所有人分配好之后,最大的权值减去最小的权值最小为多少,如果非法输出impossible。

思考:
对于非法很明显就是有奇数环。然后就是分配阵营了,如何分配对吧,对于给的m条边,可以发现每个连通块的状态只有两种,一旦一个人的阵营确定的,剩下的人全部确定。对于每个连通块都有两种状态可言,对于状态我们只要最大值和最小值,因为只有最大值和最小值会影响最后的答案,所以把两个状态存下来。
然后就是怎么能让答案最小,可以先看这个简单问题:给你n种物品,每个物品有a,b两种权值,但是只能选其中一个,问你最后选完之后,最大的值减去最小的是多少。我们称a和b是孪生的,也就是不能同时选。其中这种答案变小的一般都是按值从小到大排序,然后当走到i点,如果当前的孪生没出现,放进去。如果出现了,那么让当前的值和出现过的值让最大的在里面,因为越往后权值越大,所以multiset里面的权值越大越好。如果种类达到n,那么就是multiset里面的最大值减去最小值。
所以这个题就是这个子问题再加上二分图,同时对于图里面的每个连通块的最大值和最小值都是可以用的。对于这个图的孪生是:颜色为1的去黎明还是颜色为1的去黑暗。同理这个也是这样,按最大值排序,如果出现了孪生,那么如果当前的最小值>孪生的最小值,那么肯定用当前的,把孪生删掉。到此这个题目就是这样了,很经典,感觉和之前做的cf的一次排序后再搞的差不多。

代码:

子问题:

int T,n,m,k;
PII va[N];
PII vb[N];
int vis1[N],vis2[N],vis[N];
int cnt,sum,minn = inf;

multiset<int > s;

signed main()
{
	IOS;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>va[i].fi>>va[i].se;
		vb[++cnt] = {va[i].fi,i}; //对孪生编号,是左边还是右边,便于下面的使用
		vb[++cnt] = {va[i].se,i+n}; 
	}
	sort(vb+1,vb+1+cnt); //从小到大排序
	for(int i=1;i<=cnt;i++)
	{
		int x = vb[i].fi,id = vb[i].se;
		if(id<n)
		{
			if(vis[id]&&x>va[id].se) //如果孪生已经出现了并且用当前的更好
			{
				s.erase(s.find(va[id].se));
				s.insert(x);
			}
			else //否则就直接放进去
			{
				sum++;
				vis[id] = 1;
				s.insert(x);
			}
		}
		else
		{
			id -= n;
			if(vis[id]&&x>va[id].fi)
			{
				s.erase(s.find(va[id].fi));
				s.insert(x);
			}
			else
			{
				sum++;
				vis[id] = 1;
				s.insert(x);
			}
		}
		if(sum==n) minn = min(minn,*s.rbegin()-*s.begin()); //更新答案
	}
	cout<<minn;
	return 0;
}

本问题:
struct Node{
	int minn,maxn;
	int id;
}node[N];

int T,n,m,k;
PII va[N];
int col[N],vis[N];
int suc,cnt,sum;

vector<int > hav;
vector<int > e[N];

void init()
{
	col[0] = 1;
	cnt = sum = 0;suc = 1;
	for(int i=1;i<=n;i++)
	{
		e[i].clear();
		vis[i] = col[i] = 0;
	}
}

void dfs(int now,int p)
{
	hav.pb(now);
	col[now] = 3-col[p];
	for(auto spot:e[now])
	{
		if(spot==p) continue;
		if(!col[spot]) dfs(spot,now);
		else if(col[spot]==col[now]) suc = 0; //非法
	}
}

bool cmp(Node A,Node B)
{
	return A.maxn<B.maxn;
}

int solve()
{
	multiset<int > s;
	int anw = inf,res = 0;
	sort(node+1,node+1+cnt,cmp); //按最大值从小到大排序,因为最小值肯定小于最大值,所以这样是可以保证答案更优的
	for(int i=1;i<=cnt;i++)
	{
		int minn = node[i].minn,maxn = node[i].maxn,id = node[i].id;
		if(!vis[id]) //如果孪生没出现
		{
			vis[id] = i;
			s.insert(minn);s.insert(maxn);
			if(++res==sum) anw = min(anw,*s.rbegin()-*s.begin()); //如果选够了sum个种类就更新答案
		}
		else //孪生的已经出现了
		{
			if(minn>node[vis[id]].minn) //如果用当前的更好
			{
				s.erase(s.find(node[vis[id]].minn));
				s.erase(s.find(node[vis[id]].maxn));
				s.insert(minn);s.insert(maxn);
			}
			if(res==sum) anw = min(anw,*s.rbegin()-*s.begin()); 
		}
	}
	return anw;
}

signed main()
{
	IOS;
	cin>>T;
	for(int cs=1;cs<=T;cs++)
	{
		cout<<"Case "<<cs<<": ";
		cin>>n>>m;init();
		for(int i=1;i<=m;i++)
		{
			int a,b;
			cin>>a>>b;
			e[a].pb(b);
			e[b].pb(a);
		}
		for(int i=1;i<=n;i++) cin>>va[i].fi>>va[i].se;
		for(int i=1;i<=n;i++)
		{
			if(!col[i])
			{
				hav.clear();sum++;
				dfs(i,0);
				for(int j=1;j<=2;j++)
				{
					int minn = inf,maxn = -inf; //对这个连通块的其中一种状态求出来最大值和最小值
					for(auto t:hav)
					{
						if(col[t]==j)
						{
							minn = min(minn,va[t].fi);
							maxn = max(maxn,va[t].fi);
						}
						else
						{
							minn = min(minn,va[t].se);
							maxn = max(maxn,va[t].se);
						}
					}
					node[++cnt] = {minn,maxn,sum}; //sum种类的两个孪生都放进去了
				}
			}
		}
		if(!suc) cout<<"IMPOSSIBLE\n";
		else cout<<solve()<<"\n";
	}
	return 0;
}

总结:
多多思考,多多积累经验呀。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值