[USACO21FEB] Minimizing Edges P 题解

12 篇文章 0 订阅
2 篇文章 0 订阅

Description

传送门

Solution

显然,图 G ′ G' G 满足要求当且仅当 ∀ i ∈ [ 1 , n ] \forall i \in [1,n] i[1,n] G G G G ′ G' G 1 1 1 i i i 的奇最短路与偶最短路分别相同。

首先,我们通过一遍 bfs,对 G G G 的各个点 u u u 跑出其奇最短路 a u a_u au 和偶最短路 b u b_u bu,那么我们就可以抛弃原图,将各个 ( a u , b u ) (a_u,b_u) (au,bu) 抽出来建图。

根据最短路的线性规划形式,可以看出,我们并不关心 a u , b u a_u,b_u au,bu 熟奇熟偶,于是可以钦定 a u < b u a_u<b_u au<bu。与此同时, ( a u , b u ) (a_u,b_u) (au,bu) 周围的点 ( a v , b v ) (a_v,b_v) (av,bv) 必然满足 b v = a u ± 1 ,   a v = b u ± 1 b_v=a_u ± 1,\ a_v=b_u ± 1 bv=au±1, av=bu±1,所以可以将各个 u u u a u + b u a_u+b_u au+bu 为第一关键字, a u a_u au 为第二关键字从小到大排序。那么,我们可以将排序后的点集分成若干 a u + b u a_u+b_u au+bu 相等的极长段。

S ( a , b ) S(a,b) S(a,b) 为满足 a u = a , b u = b a_u=a,b_u=b au=a,bu=b u u u 的集合。依次扫描各个 S ( _ , _ ) S(\_,\_) S(_,_)。注意到,在依次扫描并维护的这个过程中,可能 S ( a , b ) S(a,b) S(a,b) 中有一定数量(令其为 t t t)的点不满足 b b b 的限制,需要后面的 S ( a + 1 , b − 1 ) S(a+1,b-1) S(a+1,b1) 来扶助它,所以需要在扫描过程的每一段传一个值 t t t,表示前一个 S ( a − 1 , b + 1 ) S(a-1,b+1) S(a1,b+1) 对后一个 S ( a , b ) S(a,b) S(a,b) 的需求数。

在做分类讨论之前,我们要明确一点:各段中,前面的 a a a 较小,会满足后面的 a a a;后面的 b b b 较小,会满足前面的 b b b;传 t t t 个需求过来,我们至少要拉过去 t t t 条边,毕竟欠债还钱不能少还,多还是可以的

令当前考虑到了 S ( a , b ) S(a,b) S(a,b),且 ∣ S ( a , b ) ∣ = s |S(a,b)|=s S(a,b)=s

  • t ≤ s t \le s ts
    • ∣ S ( a − 1 , b − 1 ) ∣ = 0 |S(a-1,b-1)|=0 S(a1,b1)=0,则为了满足 S ( a , b ) S(a,b) S(a,b) 中的所有 a a a(本段中越往后 a a a 越大,后面不可能满足前面的 a a a),我们要拉 s s s 条边到 S ( a − 1 , b + 1 ) S(a-1,b+1) S(a1,b+1),对答案的贡献为 s s s,且 t : = s t:=s t:=s
    • 否则(即 ∣ S ( a − 1 , b − 1 ) ∣ > 0 |S(a-1,b-1)|>0 S(a1,b1)>0),要先从 S ( a , b ) S(a,b) S(a,b) 中抽取 t t t 个点以满足 S ( a − 1 , b + 1 ) S(a-1,b+1) S(a1,b+1) 对当前的需求。这时,注意到还剩 s − t s-t st a a a 没有满足,对于这些点有两种选择,一是连向前一个 S ( a − 1 , b + 1 ) S(a-1,b+1) S(a1,b+1),二是连向 S ( a − 1 , b − 1 ) S(a-1,b-1) S(a1,b1),我们显然要选择后者,因为这样不光满足了它们的 a a a,还满足了它们的 b b b,显然更优。于是对答案的贡献为 s s s t t t 不变。
  • t > s t>s t>s
    • 首先,拉 t t t 条边连向 S ( a − 1 , b + 1 ) S(a-1,b+1) S(a1,b+1);接着,令 t : = s t:=s t:=s
  • 处理完上述情况后,还需注意,若 S ( a , b ) S(a,b) S(a,b) 是本段中的最后一个集合,那么是传不下去的(怎么可能传到下一个段去呢),那么需要进行特判:
    • a + 1 = b a+1=b a+1=b,则进行内部消化(两两匹配),对答案的贡献为 ⌈ t 2 ⌉ \lceil \frac {t} {2} \rceil 2t
    • a + 1 < b a+1<b a+1<b,则无法进行两两匹配,只能硬着头皮分别往 S ( a − 1 , b − 1 ) S(a-1,b-1) S(a1,b1) 连,对答案的贡献为 t t t。至于为什么此时 ∣ S ( a − 1 , b − 1 ) ∣ > 0 |S(a-1,b-1)|>0 S(a1,b1)>0,读者自证不难。
    • 由于这个段已处理完毕,无论 a + 1 a+1 a+1 是否等于 b b b,均须令 t : = 0 t:=0 t:=0

看到这里,你可能会有一个问题:为什么我们总是优先选择往右传,而不是优先选择连向 S ( a − 1 , b − 1 ) S(a-1,b-1) S(a1,b1) 呢?看似连向 S ( a − 1 , b − 1 ) S(a-1,b-1) S(a1,b1) 既满足了当前的 a a a,也满足了当前的 b b b,但是其实说到底,这些都局限在了 S ( a , b ) S(a,b) S(a,b) 这个小小的集合中。目光要放长远一点!无论下传与否,都会对答案产生 t t t 的贡献;但如果我们往下传的话,不会影响 S ( a , b ) S(a,b) S(a,b) 的合法性,而且如果传到底,只会产生 ⌈ t 2 ⌉ \lceil \frac {t} {2} \rceil 2t 的贡献,何乐而不为呢!综上所述,优先选择往右传,相比优先选择连向 S ( a − 1 , b − 1 ) S(a-1,b-1) S(a1,b1) 总是不劣的。

于是我们解决了这道大贪心题,时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

Code

注意:

  1. 排序后的首对 ( a , b ) (a,b) (a,b) 不应对答案产生贡献,但也需要更新 g g g
  2. 注意到程序中间有 continue,不能在最后再清空,一定要在开始时清空。
  3. 注意加上 r<=n,不然之前的测试点会影响到当前的测试点。

实现得有些垃圾。

#include <bits/stdc++.h>
#define inf 1000000007
#define PA pair<int,int>
#define fi first
#define se second
#define MP make_pair
using namespace std;
const int maxl=100005;

int read(){
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int T,n,m,t,ans,a[maxl][2],b[maxl][2],p[maxl];
vector<int> GA[maxl];map<PA,bool> ma;
bool cmp(int p,int q){
	int suma=a[p][0]+a[p][1],sumb=a[q][0]+a[q][1];
	return (suma==sumb)?(a[p][0]<a[q][0]):(suma<sumb);
}
bool bfs(){
	for (int i=1;i<=n;i++)  a[i][0]=a[i][1]=inf;
	queue<PA >que;a[1][0]=0,que.push(MP(1,0));
	while (!que.empty()){
		int u=que.front().fi,x=que.front().se,xx=(x^1);que.pop();
		for (auto v:GA[u]){
			if (a[v][xx]==inf)  a[v][xx]=a[u][x]+1,que.push(MP(v,xx));
		}
	}
	for (int i=1;i<=n;i++){
		if (a[i][0]>a[i][1])  swap(a[i][0],a[i][1]);
	}
	for (int i=1;i<=n;i++){
		if (a[i][0]<inf&&a[i][1]<inf)  return true;
	}
	return false;
}

signed main(){
	T=read();
	while (T--){
		n=read(),m=read(),t=ans=0,ma.clear();
		for (int i=1;i<=n;i++)  GA[i].clear(),a[i][0]=a[i][1]=b[i][0]=b[i][1]=0;
		for (int i=1;i<=n;i++)  p[i]=i;
		for (int i=1;i<=m;i++){
			int u=read(),v=read();
			GA[u].push_back(v),GA[v].push_back(u);
		}
		if (!bfs()) {printf("%d\n",n-1);continue;}
		
		sort(p+1,p+n+1,cmp);
		for (int i=1;i<=n;i++)  swap(b[i],a[p[i]]);
		for (int i=1;i<=n;i++)  swap(a[i],b[i]);
		for (int i=1;i<=n;){
			int r=i,s=0,flg=ma[MP(a[i][0]-1,a[i][1]-1)];
			while (r<=n&&a[r][0]==a[i][0]&&a[r][1]==a[i][1])  r++,s++;
			
			if (t<=s){
				ans+=(i>1)*s;
				if (!flg)  t=s;
			}
			else ans+=(i>1)*t,t=s;
			
			if (r==n+1||MP(a[r][0],a[r][1])!=MP(a[r-1][0]+1,a[r-1][1]-1)){
				if (a[r-1][0]+1==a[r-1][1])  ans+=(t+1)/2;
				else ans+=t;
				t=0;
			}
			ma[MP(a[i][0],a[i][1])]=1,i=r;
		}
		printf("%d\n",ans);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值