y总说,图论题的难点不在于打板子,而是建图的过程
个人觉得,建图的过程分成以下阶段:
1.确定结点的意义
2.确定边权的意义
结点一般都很显然,但是边权的意义我们一般把它设成对答案(或需要维护的东西)的贡献
具体看以下的例子:
1.热浪
1129. 热浪 - AcWing题库
题意:
思路:
跑一遍最短路就行,spfa,dij,floyd都行
Code:
#include <bits/stdc++.h>
using namespace std;
const int mxn=1e4+10,mxe=1e4+10,mnf=0x3f3f3f3f;
struct ty{
int to,next,w;
}edge[mxe<<1];
queue<int> q;
int n,m,s,t,u,v,w,tot=0;
int head[mxn],dis[mxn],vis[mxn];
void add(int u,int v,int w){
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void init(){
tot=0;
for(int i=0;i<=n;i++){
head[i]=-1;
vis[i]=0;
dis[i]=mnf;
}
while(!q.empty()) q.pop();
}
void spfa(){
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];~i;i=edge[i].next){
if(dis[edge[i].to]>dis[u]+edge[i].w){
dis[edge[i].to]=dis[u]+edge[i].w;
if(!vis[edge[i].to]){
vis[edge[i].to]=1;
q.push(edge[i].to);
}
}
}
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m>>s>>t;
init();
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
spfa();
if(dis[t]==mnf) cout<<-1<<'\n';
else cout<<dis[t]<<'\n';
}
2.信使
1128. 信使 - AcWing题库
题意:
这个就是跑一遍最短路,预处理一下dis数组,然后去看dis最大值就行
Code:
#include <bits/stdc++.h>
using namespace std;
const int mxn=2e2+10,mxe=2e2+10,mnf=0x3f3f3f3f,inf=0xc0c0c0c0;
struct ty{
int to,next,w;
}edge[mxe<<1];
queue<int> q;
int n,m,x,y,w,tot=0;
int head[mxn],dis[mxn],vis[mxn];
void add(int u,int v,int w){
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void init(){
tot=0;
for(int i=0;i<=n;i++){
vis[i]=0;
head[i]=-1;
dis[i]=mnf;
}
while(!q.empty()) q.pop();
}
void spfa(){
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];~i;i=edge[i].next){
if(dis[edge[i].to]>dis[u]+edge[i].w){
dis[edge[i].to]=dis[u]+edge[i].w;
if(!vis[edge[i].to]){
q.push(edge[i].to);
vis[edge[i].to]=1;
}
}
}
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
cin>>x>>y>>w;
add(x,y,w);
add(y,x,w);
}
spfa();
int ans=inf;
for(int i=1;i<=n;i++){
if(dis[i]==mnf){
cout<<-1<<'\n';
return 0;
}
ans=max(ans,dis[i]);
}
cout<<ans<<'\n';
}
3.香甜的黄油
1127. 香甜的黄油 - AcWing题库
枚举就行,枚举奶油放在哪个点上,每个点都去跑一遍最短路,然后去看奶油放在哪个点上牛牛到奶油的距离之和最小
Code:
#include <bits/stdc++.h>
using namespace std;
const int mxn=2e3+10,mxe=2e3+10,mnf=0x3f3f3f3f;
struct ty{
int to,next,w;
}edge[mxe<<1];
struct ty2{
int x,dis;
bool operator< (const ty2 &a) const{
return a.dis<dis;
}
};
unordered_map<int,int> mp;
priority_queue<ty2> q;
int N,n,m,x,y,w,tot=0;
int head[mxn],dis[mxn],vis[mxn];
void add(int u,int v,int w){
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void init(){
tot=0;
mp.clear();
for(int i=0;i<=n;i++){
head[i]=-1;
dis[i]=mnf;
vis[i]=0;
}
while(!q.empty()) q.pop();
}
void init2(){
for(int i=0;i<=n;i++){
vis[i]=0;
dis[i]=mnf;
}
while(!q.empty()) q.pop();
}
int dij(int s){
init2();
dis[s]=0;
ty2 u;
u.dis=dis[s],u.x=s;
q.push(u);
while(!q.empty()){
ty2 u=q.top();
q.pop();
if(vis[u.x]) continue;
vis[u.x]=1;
for(int i=head[u.x];~i;i=edge[i].next){
if(vis[edge[i].to]) continue;
if(dis[edge[i].to]>dis[u.x]+edge[i].w){
dis[edge[i].to]=dis[u.x]+edge[i].w;
ty2 tmp;
tmp.dis=dis[edge[i].to];
tmp.x=edge[i].to;
q.push(tmp);
}
}
}
int res=0;
for(int i=1;i<=n;i++){
if(mp[i]){
if(dis[i]==mnf) return mnf;
res+=dis[i]*mp[i];
}
}
return res;
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>N>>n>>m;
init();
for(int i=1;i<=N;i++) cin>>x,mp[x]++;
for(int i=1;i<=m;i++){
cin>>x>>y>>w;
add(x,y,w);
add(y,x,w);
}
int ans=mnf;
for(int i=1;i<=n;i++){
ans=min(ans,dij(i));
}
cout<<ans<<'\n';
}
4.最小花费
1126. 最小花费 - AcWing题库
题意:
思路:
转化为A*w1*w2*....*wn=B,即求w1*w2*....*wn最小值,其中wi为扣除的百分比
那么我们可以建图:
结点表示人
边权表示扣除的百分比
建完图后,可以发现我们要求的就是边权最小积
那么我们怎么从最小和转换到最小积:取对数就好了
lg(w1*w2*....*wn)=lgw1+lgw2+....+lgwn
因为lg函数始终是单调递增的,因此要求w1*w2*....*wn的最小值,就求lg(w1*w2*....*wn)的最小值
即求lgw1+lgw2+....+lgwn的最小值
即求lgwi的最小值
因此我们可以按照wi的大小去跑一遍最短路,然后跑的时候dist的值维护边权积就可以了
Code:
#include <bits/stdc++.h>
using namespace std;
const int mxn=2e3+10,mxe=1e5+10,mnf=0x3f3f3f3f;
struct ty{
int to,next;
double w;
}edge[mxe<<1];
queue<int> q;
int n,m,x,y,w,tot=0,s,t;
int head[mxn],vis[mxn];
double dis[mxn];
void add(int u,int v,double w){
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void init(){
tot=0;
for(int i=0;i<=n;i++){
head[i]=-1;
vis[i]=0;
}
while(!q.empty()) q.pop();
}
double spfa(int u,int v){
dis[u]=1;
q.push(u);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];~i;i=edge[i].next){
if(dis[edge[i].to]<dis[u]*(1.0-edge[i].w)){
dis[edge[i].to]=dis[u]*(1.0-edge[i].w);
if(!vis[edge[i].to]){
vis[edge[i].to]=1;
q.push(edge[i].to);
}
}
}
}
return dis[t];
}
int main(){
//ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&w);
add(x,y,1.0*w/100);
add(y,x,1.0*w/100);
}
scanf("%d%d",&s,&t);
printf("%.8f\n",100/spfa(s,t));
}
5.最优乘车
920. 最优乘车 - AcWing题库
题意:
思路:
换乘次数=坐车次数-1
因此我们只需要维护坐车次数就行
首先先建图:
结点就是公交车站
边权意义就设成坐车次数
那么在同一线路上的车站之间的坐车次数都为1,因此同一线路上的车站之间都连一条边权为1的边
不同线路的车站之间的坐车次数就可以根据跑最短路来求了
时间复杂度为O(M*(N-1)*N/2)
Code:
#include <bits/stdc++.h>
using namespace std;
const int mxn=5e2+10,mxe=5e2+10,mnf=0x3f3f3f3f;
struct ty{
int to,next,w;
}edge[mxe<<1];
queue<int> q;
string s;
int m,n,len=0,p,tot=0;
int a[mxn],head[mxn],dis[mxn],vis[mxn];
void add(int u,int v,int w){
edge[tot].w=w;
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void init(){
tot=0;len=0;
s.clear();
for(int i=0;i<=n;i++){
head[i]=-1;
dis[i]=mnf;
}
while(!q.empty()) q.pop();
}
void spfa(){
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];~i;i=edge[i].next){
if(dis[edge[i].to]>dis[u]+edge[i].w){
dis[edge[i].to]=dis[u]+edge[i].w;
if(!vis[edge[i].to]){
vis[edge[i].to]=1;
q.push(edge[i].to);
}
}
}
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>m>>n;
init();
getline(cin,s);
for(int i=1;i<=m;i++){
len=0;
getline(cin,s);
stringstream ssin(s);
while(ssin>>p) a[++len]=p;
for(int i=1;i<=len;i++){
for(int j=i+1;j<=len;j++){
add(a[i],a[j],1);
add(a[j],a[i],1);
}
}
}
spfa();
if(dis[n]==mnf) cout<<"NO"<<'\n';
else cout<<dis[n]<<'\n';
}
总结:
建图的过程分成以下阶段:
1.确定结点的意义
2.确定边权的意义
结点一般都很显然,但是边权的意义我们一般把它设成对答案(或需要维护的东西)的贡献