3278 、城市通电
看题解做的,是虚拟源点的思想解决 建发电站和连线路两个问题统一的、
prim算法:基本思想是每次找离某个连通图(因为有多个发电站)最近的点。
这个最小的value的点如果没有和发电厂就让它建发电厂。如果已经和某个发电厂连接,这个点就是最小生成树的组成部分。无论有没连接发电厂,每次找出的最小点都要去更新与其相连的其他结点。
在prim循环的时候找最小的dis的点的时候不用担心其本身建厂的dis小于其连通后的dis导致差错。找到某个点的时候一定是其呈现最小状态的时候。
也不用担心 后面出现某个点建厂之后会将这个点更新成更小的点。因为这个点出现在最小的位置就说明未来的所有要建厂的点的成本都大于目前这个点的成本。
此题遇到了一个小逻辑错误:
找dis最小点的时候必须这样写,此前的习惯是把t赋值为1。由于我们要找的是st为0的,但是这样写会导致1这个点一直霸占t,即使其st已经为1。
int t=-1;
for(int j=1;j<=n;j++)//找当前最小的
//找到的这个最小的,要么建站要么其值已经确定
{
if(!st[j]&&(t==-1||dis[j]<dis[t]))
{
t=j;
}
}
#include<bits/stdc++.h>
using namespace std;
//3278城市通电
typedef long long LL;
typedef pair<int,int>PII;
const int N=2010;
LL dis[N];
int x[N],y[N],k[N],c[N],st[N],fa[N];
LL n,ans=0;
vector<int>res1;
vector<PII>res2;
//求两点之间拉电线的花费
LL getdis(int a,int b)//给出点,求两点之间的距离
{
int dx = abs(x[a] - x[b]);
int dy = abs(y[a]- y[b]);
return (LL)(dx + dy) * (k[a] + k[b]);
}
void prim()
{
for(int i=0;i<n;i++)//prim的结构
{
int t=-1;
for(int j=1;j<=n;j++)//找当前最小的
//找到的这个最小的,要么建站要么其值已经确定
{
if(!st[j]&&(t==-1||dis[j]<dis[t]))
{
t=j;
}
}
//cout<<"**"<<t<<endl;
//要确定这个最小值是否已经被确定
st[t]=1;
if(!fa[t])//判断是否是已经归属于某个发电站的连通图
{
res1.push_back(t);//t建发电站
ans+=dis[t];
}
//否则就是要搭电线
else
{
res2.push_back({t,fa[t]});
ans+=dis[t];
}
for(int u=1;u<=n;u++)
{
if(dis[u]>getdis(u,t))
{
dis[u]=getdis(u,t);
fa[u]=t;
}
}
}
}
int main()
{
memset(dis,0x3f,sizeof(dis));
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i];
}
for(int i=1;i<=n;i++)
{
cin>>c[i];
dis[i]=c[i];//相当于建立了一个虚拟起点,到每个发电厂之间的距离
}
for(int i=1;i<=n;i++)
{
cin>>k[i];
}
prim();
cout<<ans<<endl;
cout<<res1.size()<<endl;
for(auto i:res1)
{
cout<<i<<" ";
}
cout<<endl;
cout<<res2.size()<<endl;
for(auto i:res2)
{
cout<<i.first<<" "<<i.second<<endl;
}
}
858 Prim算法最小生成树
prim算法每次只找距离连通块最小的节点,所以不可能出现某个点的已经加入连通块了,后面又出现将其纳入连通块的更小的边。
st数组的含义是某个点已经被纳入。
错误点:很容易想到如果一个点已经被纳入了就continue,但是对第一轮循环会造成错误。
因为还有一个任务就是根据dis最小的点将邻居点放入队列。所以无需判断st数组。
但是在纳入数组的时候一定要判断st,并且如果某个点到连通图的距离被更新了,就将其放入队列。
对于有环路的图,以及负边的图,在构造树的过程中可能会循环次数大于n-1次。】
所以在循环中添加限制条件。
#include<bits/stdc++.h>
using namespace std;
//858 Prim算法求最小生成树
const int N=500;
int w[N],st[N],f[N][N];
typedef pair<int,int>PII;
priority_queue<PII,vector<PII>,greater<PII>>q;
int n,m,idx=0,dis[N];
int ans=0;
int prim()
{
//cout<<"调用"<<endl;
int cnt=0;
q.push({0,1});
dis[1]=0;
st[1]=1;
while(q.size()&&cnt<n)
{
PII t=q.top();
q.pop();
int pos=t.second,dist=t.first;
//if(st[pos])continue;
//cout<<"出队了"<<pos<<endl;
ans+=dist;
cnt++;
//从队列里面拿出来的点也有可能是已经确定的
st[pos]=1;
for(int i=1;i<=n;i++)
{
//只有dis与pos相连的dis变小的才可以入队
if(dis[i]>f[pos][i]&&!st[i])
{
dis[i]=f[pos][i];
q.push({dis[i],i});//加入队列
}
}
}
return cnt;
}
int main()
{
cin>>n>>m;
memset(dis,0x3f,sizeof(dis));
memset(f,0x3f,sizeof(f));
for(int i=1;i<=m;i++)
{
int x,y,v;
cin>>x>>y>>v;
f[x][y]=f[y][x]=min(f[x][y],v);
}
int cnt=prim();
//if(cnt!=n)cout<<"impossible"<<cnt<<endl;
//else
//{
cout<<ans<<endl;
//}
}
kruskal算法
找n-1个符合条件的边一定最后会生成一个连通的树。
因为首先有n-1条边一定是连通的,不会出现多个连通块。
每次找最小的边的贪心策略。
kruska算法在更换某个点的f数组的时候,不用考虑方向性。两个连通块一开始f不同。但是在一个连通块的f一定相同。所以整体的f一直在更新变化。
也不用考虑某个点是否已经纳入最小生成树中,因为不到最后根本不知道谁是最小生成树的根只要两个点f不同就代表着两个连通块。将其连接即可。这种方式同时避免出现环路。
错误点:
将发现的边所连接的连通块进行连接的时候,f[x]=getfa(y)是错误的。
例如这样会导致1-2的f为2,1-3的f为3,
应该让一个连通块的根的f等于另外一个连通块的根。
#include<bits/stdc++.h>
using namespace std;
//859 Kruskal算法求最小生成树
const int N=2e5+10;
typedef pair<int,pair<int,int>>PII;
priority_queue<PII,vector<PII>,greater<PII>>p;
int f[N];
int n,m;
int getfa(int i)
{
if(f[i]==i)return i;
else
{
f[i]=getfa(f[i]);
return f[i] ;
}
}
int ans=0;
int cnt=0;
void dijkstra()
{
//while(cnt!=n-1)这个地方不能单纯你用一个条件
//否则无法判断有几个cnt
while(cnt!=n-1&&p.size())
{
PII t=p.top();
p.pop();
int x=t.second.first;
int y=t.second.second;
int v=t.first;
//不是第一个边而且这两个点已经在树上了
if(getfa(x)!=getfa(y))
{
//cout<<getfa(2)<<"***" <<getfa(3)<<endl;
f[getfa(x)]=getfa(y);
// cout<<x<<"***找到的边" <<y<<endl;
// cout<<getfa(x)<<"更新后***" <<getfa(y)<<endl;
// cout<<"边权为"<<v<<endl;
cnt++;
ans+=v;
}
}
}
int main()
{
cin>>n>>m;
while(m--)
{
int x,y,v;
cin>>x>>y>>v;
p.push({v,{x,y}});
}
for(int i=1;i<=n;i++)
{
f[i]=i;
}
dijkstra();
if(cnt!=n-1)cout<<"impossible"<<endl;
else
{
cout<<ans<<endl;
}
}
3555 二叉树
最开始的思路是用f数组保存每个结点的父亲结点。因为节点的编号是从上往下依次递增的。外层循环是x不等于y。
内层循环是谁大让谁跟新为其f,并记录更新次数。
结果是最后一个数据过不了WA。
修改成计算deep,然后比较之后,还是过不了
#include<bits/stdc++.h>
using namespace std;
//3555 二叉树
const int N=1010;
int f[N],d[N],l[N],r[N];
int n,t,m;
//衡量标准:深度
void getdeep(int root,int depth)
{
if(root==-1)return;
else
{
d[root]=depth;
getdeep(l[root],depth+1);
getdeep(r[root],depth+1);
}
//cout<<"deep函数"<<endl;
}
//求两个点到最近公共祖先节点的距离之差
int getdis(int x,int y)
{
//cout<<"dis函数"<<endl;
int t=0,u=0;
while(x!=y)
{
if(d[x]<d[y])
{
while(y!=-1&&x!=y)y=f[y],t++;
}
else if(d[x]>d[y])
{
while(x!=-1&&x!=y)x=f[x],u++;
}
else
{
y=f[y],t++;
x=f[x],u++;
}
}
return t+u;
}
int main()
{
cin>>t;
while(t--)
{
memset(f,-1,sizeof(f));
memset(d,0,sizeof(f));
memset(r,-1,sizeof(f));
memset(l,-1,sizeof(f));
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
if(x!=-1)f[x]=i;
if(y!=-1)f[y]=i;
l[i]=x;
r[i]=y;
}
// cout<<getdis(4,6)<<endl;
getdeep(1,0);
//cout<<getdis(4,6)<<endl;
while(m--)
{
int x,y;
cin>>x>>y;
cout<<getdis(x,y)<<endl;
}
}
}
按照y的做法之后 TLE
#include<bits/stdc++.h>
using namespace std;
//3555 二叉树
const int N=1010;
int f[N],d[N],l[N],r[N];
int n,t,m;
//衡量标准:深度
void getdeep(int root,int depth)
{
d[root]=depth;
getdeep(l[root],depth+1);
getdeep(r[root],depth+1);
}
//求两个点到最近公共祖先节点的距离之差
int getdis(int a,int b)
{
if (d[a] > d[b]) swap(a, b);
while (d[b] > d[a]) b = f[b];
while (a != b) a = f[a], b = f[b];
return a;
}
int main()
{
cin>>t;
while(t--)
{
memset(f,-1,sizeof(f));
memset(r,-1,sizeof(f));
memset(l,-1,sizeof(f));
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
if(x!=-1)f[x]=i;
if(y!=-1)f[y]=i;
l[i]=x;
r[i]=y;
}
// cout<<getdis(4,6)<<endl;
getdeep(1,0);
//cout<<getdis(4,6)<<endl;
while(m--)
{
int a,b;
cin>>a>>b;
int lca = getdis(a, b);
printf("%d\n", d[a] + d[b] - d[lca] * 2);
}
}
}
1394 完美牛棚
采用拉链法存图TLE
错误点:st数组作用:向前找的时候,不让前面的节点考虑st为1的点。
拉链法存图的idx的意义:是边,而不是点,所以st下标为e【i】。idx并不能唯一代表某个节点。
#include<bits/stdc++.h>
using namespace std;
//1394. 完美牛棚
const int N=210;
int match[N],st[N];
int e[N],ne[N],h[N],idx=0;
void add(int x,int y)
{
e[idx]=y;
ne[idx]=h[x];
h[x]=idx++;
}
int Hungary(int t)
{
for(int i=h[t];i!=-1;i=ne[i])
{
if(!st[e[i]])
{
st[e[i]]=true;//考虑这个点
//这个点没人匹配,或者匹配了但是可以修改
//向回修改的过程st的作用就是让其不要再考虑走这条路了
if(!match[e[i]]||Hungary(match[e[i]]))
{
match[e[i]]=t;
return 1;
}
}
}
return 0;
}
int main()
{
int n,m;
memset(match,0,sizeof(match));
memset(st,0,sizeof(st));
memset(h,-1,sizeof(h));
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int cnt;
cin>>cnt;
while(cnt--)
{
int num;
cin>>num;
add(i,num);
}
}
int cnt=0;
for(int i=1;i<=n;i++)
{
memset(st, 0, sizeof st);
if(Hungary(i))cnt++;
}
cout<<cnt<<endl;
}
860 染色法判定二分图
拉链法存无向图的时候,e和ne数组规模是2*N
最后一个数据点 TLE
#include<bits/stdc++.h>
using namespace std;
//860 染色法判定二分图
const int N=1e5+30;
int e[2*N],ne[2*N],h[N],idx=0,color[N];
int n,m;
void add(int x,int y)
{
e[idx]=y;
ne[idx]=h[x];
h[x]=idx++;
}
//如果不是二分图就会有奇数环
int ranse(int pos,int c)
{
//一定要先染色
color[pos]=c;
for(int i=h[pos];i!=-1;i=ne[i])
{
int t=e[i];
if(!color[t])
{
if(ranse(t,3-c))
return 1;
}
else
{
if(color[t]==c)
return 1;
}
}
return 0;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof(h));
for(int i=0;i<m;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
int flag=0;
for(int i=1;i<=n;i++)
{
if(!color[i])
{
memset(color,0,sizeof(color));
if(ranse(i,1))
{
flag=1;
break;
}
}
}
if(flag)cout<<"No"<<endl;
else
{
cout<<"Yes"<<endl;
}
}
861 二分图的最大匹配
每次判断某个点是否可以匹配上的时候一定要记得把st数组初始化。
#include<bits/stdc++.h>
using namespace std;
//861 二分图的最大匹配
const int N=1e5+30;
int cnt=0,match[N],st[N];
int ne[N],h[N],e[N],idx;
int n1,n2,m;
//判断某个节点是否可以匹配上
void add(int x,int y)
{
e[idx]=y;
ne[idx]=h[x];
h[x]=idx++;
}
int xiongyali(int u)
{
for(int i=h[u];i!=-1;i=ne[i])
{
int t=e[i];
//可以考虑这个点
if(!st[t])
{
st[t]=1;//这行代码之后不能尝试连这个点了
if(!match[t]||xiongyali(match[t]))
{
match[t]=u;
return 1;
}
}
}
return 0;
}
int main()
{
cin>>n1>>n2>>m;
memset(h,-1,sizeof(h));
while(m--)
{
int x,y;
cin>>x>>y;
add(x,y);
}
for(int i=1;i<=n1;i++)
{
memset(st,0,sizeof(st));
if(xiongyali(i))cnt++;
}
cout<<cnt<<endl;
}
257关押罪犯
要找到一个值,将仇恨值大于这个值的所有的罪犯对分开,然后判断是否是二分图。
要注意一个边界问题:在判断是否有奇数环的时候,一定是找大于limit的,否则就是错的。
#include<bits/stdc++.h>
using namespace std;
//257 关押罪犯
const int N=2e4+10,M=2e5+20;
int e[M],ne[M],h[N],w[M],idx,color[N];
int n,m;
void add(int x,int y,int c)
{
e[idx]=y;
ne[idx]=h[x];
w[idx]=c;
h[x]=idx++;
}
//检测是否存在奇数环
int ranse(int pos,int c,int limit)
{
color[pos]=c;//染上色
for(int i=h[pos]; i!=-1; i=ne[i])
{
if (w[i] <= limit) continue;
int t=e[i];
if(!color[t])
{
if(ranse(t,3-c,limit))return 1;
}
else
{
if(color[t]==c)
{
return 1;
}
}
}
return 0;
}
//检测是否全部是二分图
int check(int limit)
{
memset(color,0,sizeof(color));
for(int i=1; i<=n; i++)
{
if(color[i]==0)
{
if(ranse(i,1,limit))
{
return 0;
}
}
}
return 1;
}
int main()
{
memset(h,-1,sizeof(h));
cin>>n>>m;
while(m--)
{
int x,y,c;
cin>>x>>y>>c;
add(x,y,c);
add(y,x,c);
}
//使用二分
int l=0,r=1e9;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))r=mid;
else
{
l=mid+1;
}
}
cout<<l<<endl;
}