【USACO2021 February Contest Platinum】Minimizing Edges(图论,贪心)

传送门

\(d_0(u),d_1(u)\) 分别表示 \(1\)\(u\) 的偶数长最短路和奇数长最短路。那么即为要求 \(G,G'\)\(d_0,d_1\) 都相同。

先特判掉二分图的情况,这样任意 \(d_0(u),d_1(u)\) 都是有定义的。

我们用一个二元组 \((x,y)\) 来表示记录某点的 \(d_0(u)\)\(d_1(u)\),其中我们钦定 \(x<y\),到底哪个数是 \(d_0\) 哪个数是 \(d_1\) 可以根据奇偶甄别。

那么相当于给定 \(n\)\((x,y)\),然后要求构造一个图使得吻合。

考虑一点的 \((x,y)\),可以由两种情况转移得到:

  • 存在某点 \((x-1,y-1)\) 与它相连。

  • \(y\geq x+3\):存在两点 \((x-1,y+1),(x+1,y-1)\) 与它相连。

    \(y=x+1\):存在两点 \((x-1,y+1),(x,x+1)\) 与它相连。

把所有 \((x,y)\)\(x+y\) 为第一关键字,\(x\) 为第二关键字,升序排序。

那么相当于要给这些点连最少的边,使得每个点至少满足下面两点之一(注意有重合点):

  • 它与上一层对应点有连边。

  • \(y\geq x+3\),则它与左右相邻点各有连边。

    \(y=x+1\),则它与左侧相邻点有一条边,且与自己位置的某另一重合点有连边。此时也容易看出,它右侧不会存在点。

一层一层地处理。这里比较神奇的是可以直接贪心。严谨的说明如下:归纳地假设,已经考虑完这一层的前 \(i-1\) 个点集(将重合的点看成点集),且在最优策略中第 \(i-1\) 个点集要求至少向第 \(i\) 个点集连 \(k\) 条边,对第 \(i\) 个点集分类讨论如下:

  • \(y\geq x+3\)
    • 若该点集在上一层没有对应点,那么该点集只能左右连,更新 \(k\) 并考虑下一个点集即可。
    • 若该点集在上一层有对应点。首先对于那 \(k\) 条边连到的点,它们向上连或向右连都能满足条件,那么显然向右连是不劣的(这里一个小细节是,把向右连的这 \(k\) 条边的终点尽量设成不同的点,肯定是不劣的);然后对于其他点,发现无论在最优策略中右侧的点是否有和它连边,它都需要向上或向左连一条边,那么不妨向上连。
  • \(y=x+1\)。此时该点集右侧一定没有点。
    • 若该点集在上一层没有对应点,那么该点集只能向左连,并且内部要进行两两匹配连边。
    • 若该点集在上一层有对应点。首先对于那 \(k\) 条边没有连到的点,发现它们向上连一定是最优的;然后对于那 \(k\) 条边连到的点,先优先两两匹配连边,最后如果剩下来一个要单独向上连。

注意对 \(1\) 号点的二元组需要单独处理。

时间复杂度 \(O(n)\),需要两次桶排(二关键字排序)。

#include<bits/stdc++.h>

void Main()
{
	int n,m;
	std::cin>>n>>m;
	std::vector<std::vector<int>> e(n+1);
	for(int i=1;i<=m;i++)
	{
		int u,v; std::cin>>u>>v;
		e[u].push_back(v),e[v].push_back(u);
	}
	std::vector<std::array<int,2>> d(n+1,{INT_MAX,INT_MAX});
	auto bfs=[&]()
	{
		std::queue<std::pair<int,bool>> q;
		d[1][0]=0,q.push({1,0});
		while(!q.empty())
		{
			int u=q.front().first;
			bool o=q.front().second;
			q.pop();
			for(auto v:e[u])
			{
				if(d[u][o]+1<d[v][o^1])
				{
					d[v][o^1]=d[u][o]+1;
					q.push({v,o^1});
				}
			}
		}
	};
	bfs();
	if(d[1][1]==INT_MAX)
	{
		std::cout<<n-1<<std::endl;
		return;
	}
	int ans=0;
	std::vector<std::vector<int>> buc(n);
	for(int i=1;i<=n;i++) buc[std::min(d[i][0],d[i][1])].push_back(std::max(d[i][0],d[i][1]));
	std::vector<std::vector<std::pair<int,int>>> p(n<<1);
	for(int x=0;x<n;x++)
	{
		for(auto y:buc[x])
		{
			if(!p[x+y].empty()&&p[x+y].back().first==x) p[x+y].back().second++;
			else p[x+y].push_back({x,1});
		}
	}
	std::array<std::vector<int>,2> appear;
	for(int i:{0,1}) appear[i].resize(n);
	for(int s=1;s<n+n;s++)
	{
		bool f=(s&1);
		int k=0,lstx=-114514;
		for(auto pr:p[s])
		{
			int x=pr.first,t=pr.second;
			if(lstx+1!=x) k=0;
			lstx=x;
			if(!x) ans+=(s==1);
			else if(x+x+1==s)
			{
				if(appear[f][x-1]) ans+=std::max(0,t-k)+(std::min(k,t)+1)/2;
				else ans+=std::max(0,t-k)+(t+1)/2;
			}
			else
			{
				if(appear[f][x-1]) ans+=t,k=std::min(k,t);
				else ans+=std::max(0,t-k)+t,k=t;
			}
		}
		if(s>1) for(auto pr:p[s-2]) appear[f][pr.first]=0;
		for(auto pr:p[s]) appear[f][pr.first]=1;
	}
	printf("%d\n",ans);
}

int main()
{
	int T; std::cin>>T;
	while(T--) Main();
	return 0;
}
posted @ 2022-11-18 18:50  ez_lcw  阅读(39)  评论(0编辑  收藏  举报