【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;
}