上讲习题
AcWing 1144
有强制连边,先连上。因为本题是稀疏图,所以用 K r u s k a l Kruskal Kruskal更优。本题只有两种边权:1、纵向边的边权;2、横向边的边权。所以我们不需要排序,顺序已经出来了:先试纵向边,再试横向边。
#include<bits/stdc++.h>
using namespace std;
int n,m,fa[1000004];
int num(int x,int y)
{
return (x-1)*m+y;
}
int find(int u)
{
return fa[u]==0?u:fa[u]=find(fa[u]);
}
int main()
{
scanf("%d%d",&n,&m);
int x1,y1,x2,y2;
while(scanf("%d%d%d%d",&x1,&y1,&x2,&y2)!=EOF)
{
int fx=find(num(x1,y1)),fy=find(num(x2,y2));
if(fx!=fy)
fa[fx]=fy;
}
int ans=0;
for(int i=1;i<n;i++)
for(int j=1;j<=m;j++)
{
int fx=find(num(i,j)),fy=find(num(i+1,j));
if(fx!=fy)
{
fa[fx]=fy;
ans++;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<m;j++)
{
int fx=find(num(i,j)),fy=find(num(i,j+1));
if(fx!=fy)
{
fa[fx]=fy;
ans+=2;
}
}
printf("%d",ans);
return 0;
}
AcWing 1145
先求出最小生成树,可以给边长最长的几条边建立卫星通信,一共有 k − 1 k-1 k−1条边可以用卫星通信。本题是稠密图,用 P r i m Prim Prim。没有用卫星通信的最长的一条边就是 d i − k + 1 d_{i-k+1} di−k+1, d d d是执行完 P r i m Prim Prim后从小到大排序的点权的数组。
#include<bits/stdc++.h>
using namespace std;
const int NN=504;
int x[NN],y[NN],d[NN];
bool vis[NN];
int sum(int q,int p)
{
return (x[q]-x[p])*(x[q]-x[p])+(y[q]-y[p])*(y[q]-y[p]);
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
memset(d,0x3f,sizeof(d));
d[1]=0;
for(int i=1;i<=n;i++)
{
int k=0;
for(int j=1;j<=n;j++)
if(!vis[j]&&d[j]<d[k])
k=j;
vis[k]=true;
for(int j=1;j<=n;j++)
if(!vis[j])
d[j]=min(d[j],sum(k,j));
}
sort(d+1,d+1+n);
printf("%.2lf",sqrt(d[n-k+1]));
return 0;
}
AcWing 346
既然要求扩充成完全图,那么每两个点都会有边,所以在两个集合合并时这两个集合左右每个点都要有边。如果想要扩充成完全图时,唯一的最小生成树是这棵树,那么连接这两个集合的所有边都必须要比这条边多一。设 s i z i siz_i sizi表示连通块 i i i的点的个数,合并的两个集合是 a a a和 b b b,合并的边是 k k k,第 i i i条边的边权是 w i w_i wi,则需要新加的边权就是: ( s i z i × s i z j − 1 ) × ( w i + 1 ) (siz_i\times siz_j-1)\times (w_i+1) (sizi×sizj−1)×(wi+1)减一的原因是最小生成树的边不是新加的边。最开始所有连通块内结点的个数为 1 1 1。
#include<bits/stdc++.h>
using namespace std;
const int NN=6004;
struct node
{
int u,v,w;
bool operator<(const node&it)const
{
return w<it.w;
}
}edge[NN];
int siz[NN],fa[NN];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
edge[i]={
u,v,w};
}
for(int i=1;i<=n;i++)
{
fa[i]=i;
siz[i]=1;
}
sort(edge+1,edge+n);
int ans=0;
for(int i=1;i<n;i++)
{
int fx=find(edge[i].u),fy=find(edge[i].v);
if(fx!=fy)
{
ans+=(siz[fx]*siz[fy]-1)*(edge[i].w+1);
siz[fy]+=siz[fx];
fa[fx]=fy;
}
}
printf("%d\n",ans);
}
return 0;
}
概念
负环,即指环上边权和为负数的环。在最短路中如果存在从源点能到的负环,最短路就是负无穷或者说不存在最短路。
方法
S P F A SPFA SPFA可以判断负环。如果某个点入队了 n n n次及以上,那么就有负环。其他的算法也是这样,只要某个点被更新了 n n n次以上就有负环。只有 d i j k s t r a dijkstra dijkstra不能判断负环。
例题
AcWing 904
想要回到起点时比出发时间早,那么只有出现了以下两种情况才可以:1、转了一圈回来比出发时间早;2、在某个地方有一个负环,在那里转了很多圈再回来。我们发现,其实第一种情况也是一个负环,所以问题就变成了判断负环。题目没有说连通,要枚举每个连通块。
#include<bits/stdc++.h>
using namespace std;
const int NN=504,MM=5204;
int head[NN],e[MM],ne[MM],w[MM],d[NN],cnt[NN],idx,n,m,k;
bool vis[NN],st[NN];
void add(int u,int v,int l)
{
e[++idx]=v;
ne[idx]=head[u];
w[idx]=l;
head[u]=idx;
}
bool spfa(int s)
{
memset(d,0x3f,sizeof(d));
memset(cnt,0,sizeof(cnt));
memset(st,false,sizeof(st));
queue<int>q;
q.push(s);
d[s]=0;
while(q.size())
{
int t=q.front();
vis[t]=true;
st[t]=false;
q.pop();
for(int i=head[t];i;i=ne[i])
{
int v=e[i];
if(d[v]>d[t]+w[i])
{
d[v]=d[t]+w[i];
cnt[v]=cnt[t]+1;
if(cnt[v]>=n)
{
puts("YES");
return true;
}
if(!st[v])
{
st[v]=true;
q.push(v);
}
}
}
}
return false;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(vis,false,sizeof(vis));
memset(head,0,sizeof(head));
idx=0;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
{
int u,v,l;
scanf("%d%d%d",&u,&v,&l);
add(u,v,l);
add(v,u,l);
}
for(int i=1;i<=k;i++)
{
int u,v,l;
scanf("%d%d%d",&u,&v,&l);
add(u,v,-l);
}
bool ok=false;
for(int i=1;i<=n;i++)
if(!vis[i]&&spfa(i))
{
ok=true;
break;
}
if(!ok)
puts("NO");
}
return 0;
}
AcWing 361
这个题是一个 a b \cfrac{a}{b} ba求最值的形式,可以用 01 01 01分数规划来求。设 E E E是边集, V V V是点集, ∣ X ∣ |X| ∣X∣表示集合 X X X内的权值之和,则原式为 ∣ V ∣ ∣ E ∣ \cfrac{|V|}{|E|} ∣E∣∣V∣。设一个值 g g g为原式的值,则有: h ( g ) = ∣ V ∣ − ∣ E ∣ × g h(g)=|V|-|E|\times g h(g)=∣V∣−∣E∣×g得到了这个式子,思考如何二分出来这个最小的 g g g。如果最优方案下如果这个式子小于0,那么说明平均值太大了,要更小。如果这个式子大于零,那么就还可以更大。如果这个式子等于 0 0 0,那么就说明 g g g刚刚好。接下来思考这个式子在图中的最大值怎么算,先考虑建图。原式拆出来之后: h ( g ) = ∑ v ∈ V p i − ∑ e ∈ E w i × g h(g)=\displaystyle\sum_{v\in V}p_i-\displaystyle\sum_{e\in E}w_i\times g h(g)=v∈V∑pi−e∈E∑wi×g把边权代入原式,则每条边边权为 p i − w j × g p_i-w_j\times g pi−wj×g。如果能找到一个环权值和是正数,原式就大于 0 0 0,全都是负环就没有办法找到一个环使 h ( g ) h(g) h(g)大于 0 0 0。于是,求 c h e c k check check函数就是找正环,可以求最长路再用找负环的方法找正环。可以从任何地方出发找环,初始化每个点为 0 0 0,每个点最开始都入队。
#include<bits/stdc++.h>
using namespace std;
const int NN=1004;
vector<pair<int,int> >g[NN];
int p[NN],cnt[NN],n,m;
double d[NN];
bool st[NN];
bool check(double mid)
{
memset(st,false,sizeof(st));
memset(d,0,sizeof(d));
memset(cnt,0,sizeof(cnt));
queue<int>q;
for(int i=1;i<=n;i++)
{
q.push(i);
st[i]=true;
}
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=0;i<g[t].size();i++)
{
int v=g[t][i].first,w=g[t][i].second;
if(d[v]<d[t]+p[t]-mid*w)
{
d[v]=d[t]+p[t]-mid*w;
cnt[v]=cnt[t]+1;
if(cnt[v]>=n)
return true;
if(!st[v])
{
st[v]=true;
q.push(v);
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&p[i]);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
g[u].push_back({
v,w});
}
double l=0,r=1e9;
while(r-l>1e-6)
{
double mid=l+(r-l)/2;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%.2lf",l);
return 0;
}
习题
AcWing 1165
解析和代码在下一篇博客——差分约束给出