就是两道很巧妙的题,记录在这里。
A题
反转边,使得任意一点总能走到底(随机走)。
但是坏的情况是,无限走环。
所以我们需要反转边使得无环,同时价值等于最大边,要求价值尽可能的小。
有价值是最大边的限制,我们很容易想到二分。
对于环的剔除,只要修改环的一条边即可,但是这样可能造成新的环。
我们反过来思考,对于一个无环图,怎么加边使得依旧无环呢。
无环图的拓扑排序是合法的,不存在从拓扑排序后面的指向前面的,所以对于无环图,我们把边永远从前面指向后面,同一拓扑序的随意。显然能满足情况。
二分最大权,去除权值以下的判断是否无环,无环则满足条件。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
#define ll long long
#define eps 1e-6
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn=205000;
const int maxm=2000000;
int n,m;
struct Edge{
int from,to,dist;
Edge(){}
Edge(int _from,int _to,int _dist):from(_from),to(_to),dist(_dist){}
};
Edge ed[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,int w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}
vector<int>G[maxn];
int ind[maxn];
bool topo(){
int num=0;
queue<int>q;
for(int i=1;i<=n;i++){
if(!ind[i])q.push(i);
}
while(!q.empty()){
int now=q.front();q.pop();
num++;
for(int i=0;i<G[now].size();i++){
int to=G[now][i];
if(!--ind[to])q.push(to);
}
}
if(num<n)return false;
return true;
}
bool check(int x){
memset(ind,0,sizeof(ind));
for(int i=1;i<=n;i++)G[i].clear();
for(int i=1;i<etop;i++){
Edge& e=ed[i];
if(e.dist>x){
G[e.from].push_back(e.to);
ind[e.to]++;
}
}
return topo();
}
int main(){
cin>>n>>m;
FOR(i,1,m){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
insert(x,y,z);
}
int l=0,r=(int)2e9,ans;
while(l<=r){
//cout<<l<<" "<<r<<endl;
int mid=l+(r-l)/2;
if(check(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
cout<<ans<<endl;
}
第二题是如果能免费一条边,求生成树的最小花费,和实现最小花费的免费边数。
就是开头选择免费一条边,能够通过一个生成树实现最小花费。
首先呢,最小生成树去掉最大边一定是最小花费,不可能再小了。
我们考虑的一个问题就是,我们用其他边替换最大边,这样免费了同样的效果。
加边是一定形成环的,如果加的边权大于等于最大边,且最大边在环上,那么这条边就是合法的。其实前面的条件可以不要,如果边权小于的话,最小生成树一定包括的是这条边了(去除原来的最大边)
同时最小生成树上的最大边本身也是答案,可能会有多个。
我们利用并查集,合并所有小于最大边的最小生成树的边。这样子判断的时候如果不连通,不是树上最大边,就是合法的树外边(不连通说明加的环上有最大边)。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
#define ll long long
#define eps 1e-6
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn=200050;
const int maxm=400050;
int n,m;
struct Edge{
int from,to,dist;
Edge(){}
Edge(int _from,int _to,int _dist):from(_from),to(_to),dist(_dist){}
friend bool operator < (Edge a,Edge b){return a.dist<b.dist;}};
Edge ed[maxm],edges[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,int w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}
int f[maxn],flag[maxm];
int ans=-1;
long long all=0;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void kruskal(){
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<etop;i++)edges[i]=ed[i];
sort(edges+1,edges+etop);
for(int i=1;i<etop;i++){
Edge &e = edges[i];
if(find(e.from)==find(e.to))continue;
else{
all+=(long long)(e.dist);
ans=max(ans,e.dist);
flag[i]=1;
f[find(e.from)]=find(e.to);
}
}
}
int res=0;
void slove(){
kruskal();
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<etop;i++)if(edges[i].dist<ans){
f[find(edges[i].from)]=find(edges[i].to);
}
for(int i=1;i<etop;i++){
if(find(ed[i].from)!=find(ed[i].to)){
res++;
i++;
}
}
cout<<all-(ll)ans<<" "<<res<<endl;
}
int main(){
cin>>n>>m;
FOR(i,1,m){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
insert(x,y,z);
insert(y,x,z);
}
slove();
}