首先关于最短路,我目前只会dijstra,floyd,spfa 这三个算法。还剩一个没看…后面学了再说。。小比赛还是可以水一水spfa算法的…
然后就是acm我应该是会向 图论,数据结构,字符串,这几
个方面发展吧。挺感兴趣的。字符串水了水基础的kmp,图论就学了最短路,最小生成树也看明白了,大概是边刷生成树边看最大流,然后就去学学二分匹配,先把学校内的月赛周赛混过去再说…数据结构线段树之类的放后面吧。。那个刷了一两个题发现太恶心了…
---------------------------------------------------------------------------
---------------------------------------------------------------------------
最短路专题地址
A - Til the Cows Come Home POJ - 2387
最最最基础的模板,裸题了
n个点,m条边(双向),求出起点到终点最短距离…
这个没啥好说的吧。。。就是入门模板题,然后我为了锻炼自己,用了好几种写法来写(堆优化dijstra,朴素spfa,邻接表存图spfa)
朴素dijstra我没写。。那个写的太多了,而且我前面有篇博客总结了模板
堆优化+邻接表存图的dijstra
const int maxn=2e3+5;
struct node
{
int to;
int cost;
int nx;
}edge[maxn*2];
int vis[maxn],dis[maxn],head[maxn];
int cnt;
void add(int st,int ed,int val)
{
cnt++;
edge[cnt].to=ed;
edge[cnt].cost=val;
edge[cnt].nx=head[st];
head[st]=cnt;
}
void init()
{
ms(vis,0),ms(head,-1),ms(dis,inf);
}
struct cmp
{
bool operator()(const int a,const int b)
{
return dis[a]>dis[b];
}
};
void dijstra(int st,int ed,int len)
{
dis[st]=0;
priority_queue<int,vector<int>,cmp>pq;
pq.push(st);
while(!pq.empty())
{
int now=pq.top();
pq.pop();
if(vis[now])
{
continue;
}
vis[now]=1;
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
int cost=edge[i].cost;
if(!vis[to]&&dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
pq.push(to);
}
}
}
}
void solve()
{
init();
cnt=0;
int t=read(),n=read();
while(t--)
{
int a=read(),b=read(),c=read();
add(a,b,c);
add(b,a,c);
}
dijstra(n,1,n);
cout<<dis[1]<<endl;
}
我们要注意的是加边操作,如果是无向图就要反过来也加一次比如
a–>b c就还要再加一个 b–>a c
如果是有向图就不需要反向加边了,邻接表大小maxn指的是变的数量,如果是无向图且输入m条边,那你就需要保证邻接表要能装下2倍m的大小。。
spfa朴素写法
int arr[maxn][maxn];
int vis[maxn],dis[maxn];
int n,m;
void spfa(int st)
{
rep(i,1,n)
{
vis[i]=0;
dis[i]=inf;
}
vis[st]=1;
dis[st]=0;
queue<int>que;
que.push(st);
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
rep(i,1,n)
{
if(dis[i]>dis[now]+arr[now][i])
{
dis[i]=dis[now]+arr[now][i];
if(!vis[i])
{
vis[i]=1;
que.push(i);
}
}
}
}
}
void solve()
{
m=read(),n=read();
rep(i,1,n)
{
rep(j,1,n)
{
arr[i][j]=inf;
if(i==j)
{
arr[i][j]=0;
}
}
}
while(m--)
{
int a=read(),b=read(),c=read();
///注意重边问题!!
arr[a][b]=arr[b][a]=min(arr[a][b],c);
}
spfa(n);
printf("%d\n",dis[1]);
}
spfa邻接表写法
const int maxn=1e3+5;
const int maxm=2e3+5;
struct node
{
int from;
int to;
int cost;
int nx;
}edge[maxm*2];
int vis[maxn],dis[maxn],head[maxn];
int num=0;
void add(int st,int ed,int val)
{
num++;
edge[num].from=st;
edge[num].to=ed;
edge[num].cost=val;
edge[num].nx=head[st];
head[st]=num;
}
void spfa(int st)
{
vis[st]=1,dis[st]=0;
queue<int>que;
que.push(st);
while(!que.empty())
{
int now=que.front();
vis[now]=0;
que.pop();
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
int cost=edge[i].cost;
if(dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
if(!vis[to])
{
vis[to]=1;
que.push(to);
}
}
}
}
}
void init()
{
ms(vis,0);
ms(head,-1);
ms(dis,inf);
}
void solve()
{
int m=read(),n=read();
init();
while(m--)
{
int a=read(),b=read(),c=read();
add(a,b,c);
add(b,a,c);
}
spfa(n);
printf("%d\n",dis[1]);
}
B - Frogger POJ - 2253
题意:青蛙王子想要去青蛙公主家,家在石头上,青蛙不想下水弄脏衣服,
就不断在石头上跳,求出他能到达终点的路径上的最大距离最小是多少,
据说可以用最小生成树的最大边来做,但我还没试,我搞的最短路
--------
其实这个题是可以用最短路来写的,只不过dis数组的含义需要魔改一下,原来定义是从起点到i的最短距离,我们可以把它的含义修改为从起点到i的每一段路径里面的最大值
比如dis[2] 然后起点为1,经过1–>3=3 3–>2=1
那么dis[2]含义就是1到2的每条路径值中最大的,即max(v1,v2,v3…)结合到这里就是max(3,1)=3 所以dis[2]=3
既然dis数组修改了,那么我们转移状态就是
if(dis[to] > max(dis[now],e[now][to]))
dis[to]=max(dis[now],e[now][to]
这样想 如果从起点到to的dis数组可以被now和now到to的权来松弛,我们就更新一下就好
const int maxn=2e2+5;
double dis[maxn],vis[maxn];
double arr[maxn][maxn];
double nx[maxn],ny[maxn];
int cas;
void init(int n)
{
for(int i=1;i<=n;i++)
{
初始化
vis[i]=0;
dis[i]=inf;
for(int j=1;j<=n;j++)
{
if(i==j)
{
arr[i][j]=0;
}
else
{
arr[i][j]=inf;
}
}
}
}
double sum(int a,int b)
{
return sqrt(pow(nx[a]-nx[b],2)+pow(ny[a]-ny[b],2));
}
void dijstra(int n)
{
vis[1]=1;
dis[1]=0;
rep(i,1,n)
{
dis[i]=arr[1][i];
}
while(1)
{
int pos=-1;
for(int i=1;i<=n;i++)
{
if(!vis[i]&&(pos==-1||dis[i]<dis[pos]))
{ 找到dis最小值
pos=i;
}
}
if(pos==-1)
{
break;
}
vis[pos]=1;
for(int i=1;i<=n;i++)
{
尽量使dis[i]变小
dis[i]=min(dis[i],max(dis[pos],arr[pos][i]));
}
}
}
void solve(int n)
{
init(n);
rep(i,1,n)
{
scanf("%lf %lf",&nx[i],&ny[i]);
}
rep(i,1,n)
{
rep(j,1,n)
{
更新图
arr[i][j]=sum(i,j);
// cout<
}
}
dijstra(n);
printf("Scenario #%d\n",++cas);
printf("Frog Distance = %.3f\n\n",dis[2]);
}
C - Heavy Transportation POJ - 1797
这道题和上面那个恰恰相反…求的是路径里面最小值的最大为多少
既然是求出最大值,我们需要初始化dis为0,然后dis代表的就是起点到i的路径里面的最小值,同上,只是做了少数修改,比如说转移时
rep(i,1,n)
{
if(!vis[i]&&dis[i]<min(dis[pos],arr[pos][i]))
{
dis[i]=min(dis[pos],arr[pos][i]);
}
}
我们是尽量使当前dis变得更大
其实在松弛的时候判断不判断vis差不多,只是判断了时间复杂度会低一点且不易出错
既然和B差不多,我就直接放代码了
const int maxn=1e3+5;
const int maxm=1e3+5;
int vis[maxn],dis[maxn];
int arr[maxn][maxn];
void init(int n)
{
for(int i=1;i<=n;i++)
{
vis[i]=0;
for(int j=1;j<=n;j++)
{
arr[i][j]=0;
}
}
}
void dijstra(int st,int n)
{
vis[st]=1;
rep(i,1,n){dis[i]=arr[st][i];}
//dis[st]=0;
while(1)
{
int pos=-1;
rep(i,1,n)
{
if(!vis[i]&&(pos==-1||dis[pos]<dis[i]))
{
pos=i;
}
}
if(pos==-1)
{
break;
}
vis[pos]=1;
rep(i,1,n)
{
if(!vis[i]&&dis[i]<min(dis[pos],arr[pos][i]))
{
dis[i]=min(dis[pos],arr[pos][i]);
}
}
}
}
void solve()
{
int t=read();
rep(i,1,t)
{
int n=read(),m=read();
init(n);
while(m--)
{
int a=read(),b=read(),c=read();
arr[a][b]=arr[b][a]=max(arr[a][b],c);
}
dijstra(1,n);
printf("Scenario #%d:\n",i);
printf("%d\n\n",dis[n]);
}
}
值得一提的是,可能我disjstra堆优化理解的不够透彻,这个题用优化的写一直wa…改成暴力朴素的就过了…以后还是对应数据范围写相应的算法吧…
D - Silver Cow Party POJ - 3268
给出n个地方m条路(有向图),目的地为x(n内)
每个地方派一头大黄牛去x参加party,参加完了再回来,总时间是去加会
找出每头牛所消耗的时间的最大值,即n个点 求出每个点到n再从n回来所消耗的时间 最大是多少
因为是有向图(题目说了) 所以去的时候最短路和回来的时候是不一样的
思想是
1–floyd无敌算法 多源最短路直接出
可惜数据是1e3 n3的算法卡住了…再小个几百就可以过了
2–直接n次dijstra求出多源最短路
肯定会超时…
3–反向建图
这个牛逼,我学到了
首先回来的时候很好算,直接从x开始跑一个最短路求出每个数回来时的时间就OK。这题难点在于,怎么求出每个点到x的最短路…其实图论里面反向建图就可以解决这个问题,比如说你输入了m条边,然后你跑个最短路,结果是一个点到其他各个点的最短距离,
但是如果你输入边的时候反向建图! 比如说
输入 a–>b=c 正常建图是add(a,b,c) 即建立a到b的权值为c的边
如果你这个时候反过来add(b,a,c) 建立的是b到a的权值为c的边,那么你跑出来的结果就是所有点到起始点的最短距离!
所以我们就有思路了
先dijstra正向跑一下算出回来的时间,再开个数组存下来,之后初始化最短路的,反向建边,再跑一下最短路 这个时候dis1[i]+dis2[i]就是i点的牛去和回来的最小总时间了,之后遍历所有地点,取最大值即可
注意这个题范围小,所以我们用的是朴素dijstra+邻接矩阵,反向建图时对矩阵更改就好了,
例如
rep(i,1,n)
{
rep(j,i+1,n)
{
int tmp=arr[i][j];
arr[i][j]=arr[j][i];
arr[j][i]=tmp;
}
}
这样就直接反向图了,
如果题目数据大的话,用邻接表存图,我们需要单独开个结构体记录输入的边,跑一遍之后遍历邻接表反向建图就好,稍微复杂了一点点…
代码如下,部分有注释
int arr[maxn][maxn],dis[maxn],ans[maxn],vis[maxn];
void dijstra(int st,int n)
{
ms(vis,0);
rep(i,1,n)
{
dis[i]=arr[st][i];
}
vis[st]=1;
while(1)
{
int pos=-1;
rep(i,1,n)
{
if(!vis[i]&&(pos==-1||dis[i]<dis[pos]))
{
pos=i;
}
}
if(pos==-1)
{
break;
}
vis[pos]=1;
rep(i,1,n)
{
if(!vis[i]&&dis[i]>dis[pos]+arr[pos][i])
{
dis[i]=dis[pos]+arr[pos][i];
}
}
}
}
void solve()
{
int n=read(),m=read(),x=read();
rep(i,1,n)
{
rep(j,1,n)
{
if(i==j)
{
arr[i][j]=0;
}
else
{
arr[i][j]=inf;
}
}
}
while(m--)
{
int a=read(),b=read(),c=read();
arr[a][b]=min(arr[a][b],c);防止重边
}
ms(ans,0);
dijstra(x,n);
rep(i,1,n)
{
if(i==x)continue;
ans[i]+=dis[i];
}
rep(i,1,n)反向建图
{
rep(j,i+1,n)
{
int tmp=arr[i][j];
arr[i][j]=arr[j][i];
arr[j][i]=tmp;
}
}
dijstra(x,n);
rep(i,1,n)储存答案
{
if(i==x)continue;
ans[i]+=dis[i];
}
int sum=0;
rep(i,1,n)遍历最大值
{
if(i==x)continue;
sum=max(ans[i],sum);
}
printf("%d\n",sum);
}
E - Currency Exchange POJ - 1860
这个题题意好难读…
第一行输入钱的种类数目n,交换方式数目m,初始钱的种类st,以及初始的钱的数量num
下面m行输入交换方式
a,b,r1,c1,r2,c2 交换为
从a到b ans=(a-c1)*r1
从b到a ans=(b-c2)*r2
求问是否存在一种方式可以使得 初始钱的种类st交换为其他的钱,不停交换,最后能再次回来到st且此时钱的数量大于tot从而不断循环牟取暴利
(钱生钱生钱生钱生钱…)
我们可以发现本质就是图论…即求出图中是否有正环
常规的建图…因为要牟利所以每次尽量使当前的钱更大,所以初始化起点价钱是tot其他都初始化为0
然后跑最短路,然后判断最后st的结果是否大于tot大于则可以利滚利滚利滚利·输出yes反之no
可能有环且可能为负环
所以我们跑spfa就好,另外一个判断环的算法没学…
我们每次取出to的点,当前不是st就尽量变得更大,是st就直接判断是否变大了,return结果就好
const int maxn=1e3+5;
const int maxm=1e5+5;
struct node
{
int to,nx;
double r,c;
}edge[maxn];
int head[maxn],vis[maxn],num,n;
double dis[maxn];
void init()
{
rep(i,1,n)
{
head[i]=-1;
vis[i]=0;
dis[i]=0;
}
num=0;
}
void add(int st,int ed,double r,double c)
{
num++;
edge[num].to=ed;
edge[num].nx=head[st];
edge[num].r=r;
edge[num].c=c;
head[st]=num;
}
bool spfa(int st,double cost)
{
vis[st]=1;
dis[st]=cost;
queue<int>que;
que.push(st);
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
double val=(dis[now]-edge[i].c)*edge[i].r;
if(dis[to]<val)
{
dis[to]=val;
if(to==st&&val>cost)当前有环了且增大了
{
return true;
}
if(!vis[to])
{
vis[to]=1;
que.push(to);
}
}
}
}
return false;
}
void solve()
{
int m,s;
double v;
scanf("%d %d %d %lf",&n,&m,&s,&v);
init();
int a,b;
double r1,c1,r2,c2;
while(m--)
{
scanf("%d %d %lf %lf %lf %lf",&a,&b,&r1,&c1,&r2,&c2);
add(a,b,r1,c1);
add(b,a,r2,c2);
}
if(spfa(s,v))
{
puts("YES");
}
else
{
puts("NO");
}
}
F - Wormholes POJ - 3259
题意:t组样例,n个点,m条边,w个虫洞
边为无向,虫洞为有向且(权值代表所需的时间的绝对值,虫洞代表负的,就是时光倒流)
求问是否存在一种路径可以使得它从1开始,回来的时候小于起始时间,即是否存在负环回路
负权回路 代表这条路存在权值为负的情况且可以构成回路,且回路权值之和小于0
我们记住
dijstra不能处理含负权的图
spfa能处理含负权的图,不能处理负权回路,但可以判断是否有负权回路
那么就简单了,用spfa判断是否有负权回路就可以了,有的话就可以回到更早之前,
spfa判断是否有负权回路的标准是单独开一个inq数组记录每个节点进入队列的次数
当某个节点进入队列次数大于等于n的时候代表含有负权回路!
const int maxn=1e4+5;
const int maxm=1e5+5;
struct node
{
int from,to,cost,nx;
}edge[maxn];
int head[maxn],vis[maxn];
int dis[maxn],cnt[maxn];
int n,m,w,num;
void init()
{
rep(i,1,n)
{
cnt[i]=0;
head[i]=-1;
vis[i]=0;
dis[i]=inf;
}
num=0;
}
void add(int st,int ed,int cost)
{
num++;
edge[num].from=st;
edge[num].to=ed;
edge[num].nx=head[st];
edge[num].cost=cost;
head[st]=num;
}
bool spfa(int st)
{
vis[st]=1;
dis[st]=0;
queue<int>que;
que.push(st);
cnt[st]++;
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
int cost=edge[i].cost;
if(dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
if(!vis[to])
{
vis[to]=1;
que.push(to);
cnt[to]++;记录进队次数
if(cnt[to]>=n)
{
return true;
}
}
}
}
}
return false;
}
void solve()
{
n=read(),m=read(),w=read();
init();
while(m--)
{
int a=read(),b=read(),c=read();
add(a,b,c);
add(b,a,c);
}
while(w--)
{
int a=read(),b=read(),c=read();
add(a,b,-c);
}
if(spfa(1))
{
puts("YES");
}
else
{
puts("NO");
}
}
G - MPI Maelstrom POJ - 1502
题意太长且全是废话。。。我就不贴了 直接说题目意思
输入一个矩阵当i=j时不输入,自动设置为0 其他的aij代表i到j的权值
当输入是x时代表不连通
求出1到其他每个点的最小值的最大为多少
就是个普通dijstra 只是恶心的是读入不好写…
因为含有x所以我们需要读入字符串,再转换成数字
有一个神器叫做stringstream
用于处理string类等等的转换吧…感兴趣可以去查阅一下stringstream
直接贴代码吧,,水题
const int maxn=1e2+5;
const int maxm=1e5+5;
int arr[maxn][maxn],dis[maxn],vis[maxn];
int input()
{
string str;cin>>str;
if(str=="x")
{
return inf;
}
else
{
int tmp;
stringstream stream;
stream<<str;
stream>>tmp;
return tmp;
}
}
void dijstra(int st,int n)
{
ms(vis,0);
rep(i,1,n)
{
dis[i]=arr[st][i];
}
vis[st]=1;
while(1)
{
int pos=-1;
rep(i,1,n)
{
if(!vis[i]&&(pos==-1||dis[i]<dis[pos]))
{
pos=i;
}
}
//de(pos);
if(pos==-1)
{
break;
}
vis[pos]=1;
rep(i,1,n)
{
if(!vis[i]&&dis[i]>dis[pos]+arr[pos][i])
{
dis[i]=dis[pos]+arr[pos][i];
}
}
}
}
void solve()
{
int n=read();
rep(i,1,n)
{
rep(j,1,i)
{
if(i==j)
{
arr[i][j]=0;
}
else
{
arr[i][j]=arr[j][i]=input();
//cout<
}
}
//de(1);
}
dijstra(1,n);
int ans=0;
rep(i,1,n)
{
if(dis[i]>ans)
{
ans=dis[i];
}
}
printf("%d\n",ans);
}
H - Cow Contest POJ - 3660
思维题
第一行输入牛的数目n和比赛数目m
下面m行输入a,b代表a打败了b
我们定义比赛具有传递性,如果a打败了b且b打败了c那我们就可断定a可以打败c
求问你可以确定多少个牛的名次,题目保证数据不冲突
我们如果可以确定一个牛的名次 就代表我们可以确定它和其他n-1牛的关系
且具有传递性,我们可以类比于floyd暴力循环就好
邻接矩阵存储结果 aij 0代表没结果 1代表a>b -1代表b>a
对于每两个数i,j之间的关系找出一个k使得 aik=1&&ajk=-1或者aik=-1&&ajk=1
即乘积为-1,就可以建立i,j之间的关系了,不断延伸暴力更新就好
最后判断关系,枚举每个点到其他点关系不为0的个数,为n-1则可以确定
就OK了
const int maxn=1e2+5;
const int maxm=1e5+5;
int dis[maxn][maxn];
int floyd(int n)
{
rep(k,1,n)
{
rep(i,1,n)
{
rep(j,1,n)
{
int a=dis[i][k],b=dis[j][k];
if(a*b<0)判断是否可以确定关系
{
if(a>0)
{
dis[i][j]=1;
dis[j][i]=-1;
}
else
{
dis[i][j]=-1;
dis[j][i]=1;
}
}
}
}
}
int cnt=0;
rep(i,1,n)
{
int sum=0;
rep(j,1,n)
{
if(dis[i][j]!=0)
{
sum++;
}
}
if(sum==n-1)
{
cnt++;
}
}
return cnt;
}
void solve()
{
int n=read(),m=read();
ms(dis,0);
while(m--)
{
建立关系
int a=read(),b=read();
dis[a][b]=1;
dis[b][a]=-1;
}
printf("%d\n",floyd(n));
}
先写到这吧,明天后天抽时间继续更
------------------------------------
------------------------------------
I - Arbitrage POJ - 2240
题目意思
先给出n代表钱的种类,再给出m代表交换钱的方式,求问是否可以利滚利滚利滚利
和前面那个题是一样的,判断是否有正环即可。唯一不同的是这道题用的是字符串代表钱,我们只需要开个map记录字符串,转换为数字标号就可以了
判断正环的方式依旧是跑最短路,并判断初始点的值是否变大变大了就是有正环‘
const int maxn=50;
const int maxm=1e5+5;
double arr[maxn][maxn];
double dis[maxn];
int vis[maxn];
int n,m;
map<string,int>mp;
bool spfa(int st)
{
ms(vis,0);
ms(dis,0);
vis[st]=1;
dis[st]=1.0;
queue<int>que;
que.push(st);
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
rep(i,1,n)
{
if(dis[now]*arr[now][i]>dis[i])
{
dis[i]=dis[now]*arr[now][i];
if(dis[st]>1)出现了正环
{
return true;
}
if(!vis[i])
{
que.push(i);
vis[i]=1;
}
}
}
}
return false;
}
int main()
{
int cas=0;
while(cin>>n)
{
if(n==0)
{
break;
}
mp.clear();
rep(i,1,n)
{
string tmp;cin>>tmp;
mp[tmp]=i;
}
cin>>m;
ms(arr,0);
rep(i,1,n)
{
arr[i][i]=1.0;
}
while(m--)
{
string a,b;
double val;
cin>>a>>val>>b;
arr[mp[a]][mp[b]]=val*1.0;
}
string ans="No";
rep(i,1,n)
{
if(spfa(i))
{
ans="Yes";
break;
}
}
cout<<"Case "<<++cas<<": "<<ans<<endl;
}
#ifdef _LOCALE_DEBUG_PAUSE
system("pause");
#endif //_LOCALE_DEBUGE_PAUSE
return 0;
}
J - Invitation Cards POJ - 1511
题意,
t组样例,每组样例给出车站数量和路线数目
每个人从1到n-1出发到n再从n回来,单向路,求出所有人车费之和 最小能为多少
很明显是一道反向建图的题,正向建图求出从终点回来的时间,反向建图求出去的时间,最后再累和每个点所耗费的时间即可
const int maxn=1e6+5;
const int maxm=2e3+5;
struct node
{
ll from;
ll to;
ll cost;
ll nx;
}edge[maxn];
ll vis[maxn],head[maxn],dis[maxn],ans[maxn];
struct nd
{
ll a,b,c;
}cup[maxn];
ll num;
void init(ll n)
{
rep(i,1,n)
{
vis[i]=0;
dis[i]=INF;
head[i]=-1;
}
num=0;
}
void add(ll a,ll b,ll c)
{
num++;
edge[num].to=b;
edge[num].from=a;
edge[num].cost=c;
edge[num].nx=head[a];
head[a]=num;
}
void spfa(ll st)
{
vis[st]=1;
dis[st]=0;
queue<ll>que;
que.push(st);
while(!que.empty())
{
ll now=que.front();
que.pop();
vis[now]=0;
for(ll i=head[now];i!=-1;i=edge[i].nx)
{
ll to=edge[i].to;
ll cost=edge[i].cost;
if(dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
if(!vis[to])
{
vis[to]=1;
que.push(to);
}
}
}
}
}
void solve()
{
ll n=read(),m=read();
init(n);
rep(i,1,m)
{
cup[i].a=read();
cup[i].b=read();
cup[i].c=read();
}
rep(i,1,m)
{
ll a=cup[i].a;
ll b=cup[i].b;
ll c=cup[i].c;
add(a,b,c);
}
spfa(1);
rep(i,1,n)
{
ans[i]=dis[i];
}
init(n);
rep(i,1,m) 反向建图
{
ll a=cup[i].a;
ll b=cup[i].b;
ll c=cup[i].c;
add(b,a,c);
}
spfa(1);
ll val=0;
rep(i,1,n)
{
val+=dis[i]+ans[i];
}
printf("%lld\n",val);
}
K - Candies POJ - 3159
题意: 给出孩子的数量n以及约束条件个数m
后面m行给出m个约束条件,条件为A,B,C代表 B孩子获得的糖果数量不能超过A孩子获得的糖果数量加上C
大意是Bn-An<=C
让你求出一号孩子和n号孩子糖果数量差值最大为多少(n号减去1号)
这是一个基础的差分约束
-----------------------------------------------
-----------------------------------------------
差分约束
首先这个东西我还没彻底理解它和最短路之间的关系,但是我会用式子了!!,难点是怎么转换为差分约束,那么放后面学,先把基础式子弄明白就好了
首先要记住差分约束不能适用于负环回路,但可以处理负权
所以用spfa算法判负环就好了
比如说,我们假定a-b>=c那就等价于从a到b建立一条权值为-c的边
对于任何一个不等式,我们先把它转化成为这样
st+cost>=ed 就是从st到ed一条边为cost,注意正负号问题就OK了
如果题目给出st-ed=cost,那我们就转换成两条边
st-ed<=cost&&st-ed>=cost
还有,如果题目是<而不是<=,那我们改变一下,从a-b
求最小距离(最长路),就初始化dis为0
-----------------------------------------
-----------------------------------------
转化到这个题就直接套用上面的公式建边就OK了,B-A<=C
A–>B=C
const int maxn=3e4+5;
const int maxm=15e4+5;
struct node
{
int from;
int to;
int cost;
int nx;
}edge[maxm];
int vis[maxn],head[maxn],dis[maxn];
int num;
void init(int n)
{
rep(i,1,n)
{
vis[i]=0;
dis[i]=inf;
head[i]=-1;
}
num=0;
}
void add(int a,int b,int c)
{
num++;
edge[num].from=a;
edge[num].to=b;
edge[num].cost=c;
edge[num].nx=head[a];
head[a]=num;
}
void spfa(int st)
{
vis[st]=1;
dis[st]=0;
stack<int>sta;
sta.push(st);
while(!sta.empty())
{
int now=sta.top();
sta.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
int cost=edge[i].cost;
if(dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
if(!vis[to])
{
vis[to]=1;
sta.push(to);
}
}
}
}
}
void solve()
{
int n=read(),m=read();
init(n);
while(m--)
{
int a=read(),b=read(),c=read();
add(a,b,c);
}
spfa(1);
printf("%d\n",dis[n]);
}
L - Subway POJ - 2502
第一行给出起始点坐标与终点坐标
之后多组输入到文件末尾
每一行代表一列地铁所经过的车站,以-1,-1结尾
可以在任意车站下车且上车,不同车站之间,可以坐地铁或者走路
地铁速度和走路速度中给出来了(注意转换单位)
求起点到终点最短时间
直接建图,同一列地铁上的每个车站,建立权值为车站之间的距离除于车速,注意,地铁车站到达顺序是固定的所以不是相邻的车站之间建图不能用距离除于车速(我因为这点wa了一次),应该是他上一个车站的时间,加上这俩车站之间的时间
同时我们还要对于没两个点之间用走路的时间更新一下,再开始跑最短路
这样输入
double x1,y1;
st=3,ed=2;
while(~scanf("%lf %lf",&x1,&y1))
{
if(x1==-1&&y1==-1)
{
rep(i,st+1,ed)
{
maze[i][i-1]=maze[i-1][i]=min(maze[i-1][i],get_dis(arr[i-1].x,arr[i-1].y,arr[i].x,arr[i].y)/car);
}
st=ed+1;
continue;
}
ed++;
arr[ed].x=x1,arr[ed].y=y1;
}
代码是
const int maxn=500+5;
const int maxm=15e4+5;
struct node
{
double x,y;
}arr[maxn];
double get_dis(double x1,double y1,double x2,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int st,ed;
double dis[maxn];
double maze[maxn][maxn];
int vis[maxn];
double car=2000.0/3.0;
double walk=500.0/3.0;
void dijstra(int st)
{
ms(vis,0);
rep(i,1,ed)
{
dis[i]=maze[st][i];
}
vis[st]=1;
dis[st]=0;
while(1)
{
int pos=-1;
rep(i,1,ed)
{
if(!vis[i]&&(pos==-1||dis[i]<dis[pos]))
{
pos=i;
}
}
if(pos==-1)
{
break;
}
vis[pos]=1;
rep(i,1,ed)
{
if(!vis[i]&&dis[i]>dis[pos]+maze[pos][i])
{
dis[i]=dis[pos]+maze[pos][i];
}
}
}
}
void solve()
{
rep(i,1,300)
{
rep(j,1,300)
{
if(i==j)
{
maze[i][j]=0;
}
else
{
maze[i][j]=1.0*inf;
}
}
}
rep(i,1,2)
{
scanf("%lf %lf",&arr[i].x,&arr[i].y);
}
double x1,y1;
st=3,ed=2;
while(~scanf("%lf %lf",&x1,&y1))
{
if(x1==-1&&y1==-1)
{
rep(i,st+1,ed)
{
maze[i][i-1]=maze[i-1][i]=min(maze[i-1][i],get_dis(arr[i-1].x,arr[i-1].y,arr[i].x,arr[i].y)/car);
}
st=ed+1;
continue;
}
ed++;
arr[ed].x=x1,arr[ed].y=y1;
}
rep(i,1,ed)
{
rep(j,1,ed)
{
maze[i][j]=min(maze[i][j],get_dis(arr[i].x,arr[i].y,arr[j].x,arr[j].y)/walk);
}
}
dijstra(1);
printf("%d\n",(int)(dis[2]+0.5));
}
M - 昂贵的聘礼 POJ - 1062
读题可以很轻易发现这本质就是最短路
直接购买礼物花费就是这个价钱,换礼物的话就是给出一个松弛边的选项,可以花费cost加下一个物品的价钱,不断松弛就好
关于题目所要求的地位贵贱不可交易,我们在输入时记录下每个位置的地位
然后直接枚举区间的左边界为l,对于每个数,只有这个数地位在l和l+范围m
这个区间内才可以松弛,不断枚举区间且更新答案就好了
const int maxn=1e3+5;
const int maxm=15e4+5;
int n,m;
int vis[maxn],val[maxn],ank[maxn],dis[maxn];
int arr[maxn][maxn];
void init()
{
rep(i,0,n)
{
val[i]=0;
ank[i]=0;
rep(j,0,n)
{
if(i==j)
{
arr[i][j]=0;
}
else
{
arr[i][j]=inf;
}
}
}
}
bool check(int pos)
{
if(ank[pos]>=ank[0]&&ank[pos]<=ank[0]+m)
{
return true;
}
return false;
}
void dijstra()
{
rep(i,1,n)
{
vis[i]=0;
dis[i]=val[i];
}
while(1)
{
int pos=-1;
rep(i,1,n)
{
if(!vis[i]&&(pos==-1||dis[i]<dis[pos]))
{
pos=i;
}
}
if(pos==-1)
{
break;
}
vis[pos]=1;
if(!check(pos))
{
continue;
}
rep(i,1,n)
{
if(!vis[i]&&check(i)&&dis[i]>dis[pos]+arr[pos][i])
{
dis[i]=dis[pos]+arr[pos][i];
}
}
}
}
void solve()
{
m=read(),n=read();
init();
rep(i,1,n)
{
val[i]=read();
ank[i]=read();
int x=read();
while(x--)
{
int t=read(),v=read();
arr[t][i]=v;
}
}
int ans=inf;
rep(i,1,n)
{
ank[0]=ank[i];
dijstra();
ans=min(ans,dis[1]);
}
printf("%d\n",ans);
}
N - Tram POJ - 1847
第一行给出拐弯路口数目n,和起点a,终点b
之后n行
第i行代表第i个节点的情况,先给出连接点数目num,之后num个数,代表从i到这个点可以相连,第一个连接的不用拨动方向牌,后面的需要拨动,找最小次数转动,从1到n
我们可以把这个看成权值,为0或者为1,我们不要考虑走过后这个节点方向如何了,因为只需要走一次,多次经过同一节点毫无意义
所以我们对每个点连接情况来建图,第一个为0,其他为1
跑个最短路就好了,dis数组代表转动方向牌的次数,
const int maxn=1e3+5;
const int maxm=15e4+5;
int vis[maxn],arr[maxn][maxn],dis[maxn];
void init(int n)
{
ms(arr,inf);
rep(i,1,n)
{
vis[i]=0;
arr[i][i]=0;
}
}
void dijstra(int st,int n)
{
vis[st]=1;
rep(i,1,n)
{
dis[i]=arr[st][i];
}
dis[st]=0;
while(1)
{
int pos=-1;
rep(i,1,n)
{
if(!vis[i]&&(pos==-1||dis[i]<dis[pos]))
{
pos=i;
}
}
if(pos==-1)
{
break;
}
vis[pos]=1;
rep(i,1,n)
{
if(!vis[i]&&dis[i]>dis[pos]+arr[pos][i])
{
dis[i]=dis[pos]+arr[pos][i];
}
}
}
}
void solve()
{
int n=read(),a=read(),b=read();
init(n);
rep(i,1,n)
{
int num=read();
rep(j,1,num)
{
int to=read();
arr[i][to]=1;
if(j==1)
{
arr[i][to]=0;
}
}
}
dijstra(a,n);
printf("%d\n",( dis[b]==inf ? -1:dis[b]));
}
O - Extended Traffi
题意:
t组样例,每组给出节点个数n,并在下一行依次输入每个节点的值
然后给出道路数目m,并给出m行,每行两个数a,b
代表a,b之间有一条路,权值是终点值减去起点值的立方,比如,假设a点为a,b点为b,那么建图为a–>b = (b-a)的三次方 (有向图)
求出最短1到n距离,小于3或者不存在就输出-1
很明显发现权值可以为负,且可能存在负环,所以我们跑个spfa,并且发现负环就标记负环,找出最后结果,如果最后结果在负环里面,或者小于3就输出 问号
const int maxn=2e2+5;
const int maxm=1e4+5;
int val[maxn],head[maxn],vis[maxn],cycle[maxn],dis[maxn],cnt[maxn];
int get_val(int t){return t*t*t;}
int num,cas;
struct node
{
int from;
int to;
int cost;
int nx;
}edge[maxn*maxn];
void add(int a,int b,int c)
{
num++;
edge[num].from=a;
edge[num].to=b;
edge[num].cost=c;
edge[num].nx=head[a];
head[a]=num;
}
void dfs(int now)
{
cycle[now]=1;
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
if(!cycle[to])
{
dfs(to);
}
}
}
void spfa(int st,int n)
{
vis[st]=1;
dis[st]=0;
queue<int>que;
que.push(st);
cnt[st]++;
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
int cost=edge[i].cost;
if(cycle[to])
{
continue;
}
if(dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
if(!vis[to])
{
vis[to]=1;
que.push(to);
cnt[to]++;
if(cnt[to]>=n)
{
dfs(to);
}
}
}
}
}
}
void solve()
{
num=0;
int n=read();
rep(i,1,n)
{
val[i]=read();
}
ms(head,-1),ms(vis,0),ms(cycle,0),ms(dis,inf),ms(cnt,0);
int m=read();
while(m--)
{
int a=read(),b=read();
int c=get_val(val[b]-val[a]);
add(a,b,c);
}
int q=read();
spfa(1,n);
printf("Case %d:\n",++cas);
while(q--)
{
int pos=read();
if(cycle[pos]||dis[pos]==inf||dis[pos]<3)
{
puts("?");
}
else
{
printf("%d\n",dis[pos]);
}
}
}
注意我们在找到负环时,我们需要把这个负环所有能到达的点都标记上,因为能进入负环的点都可以权值无限小了,所以必定小于3
P - The Shortest Path in Nya Graph HDU - 4725
t组数据
每组数据第一行先输入节点数目n,再输入边的数目m,再输入相邻层之间花费w
注意只有相邻层才可以跨,如果1跨层到3,不考虑点点之间,必须要求2上有点才能1,2,3不可以直接两层
之后第二行输入n个数代表每个节点所在的层数,然后m行边,代表a->b=c
(无向图)
我们先用标记数组in记录下哪些层有点
rep(i,1,n)
{
pos[i]=read();
in[pos[i]]=1;
}
然后我们建立虚拟点,来连接层与层之间
虚拟点设为i+n,就是第i层的虚拟点,每个虚拟点与自己层的点权值为0,与相邻层权值为w
我们遍历每个点,如果这个点前一层有点,我们就把这个点和前一层的虚拟点连接起来,同理连接后一层的虚拟点
add(pos[i]+n,i,0);添加自己层
if(in[pos[i]+1])
{
add(i,pos[i]+n+1,x);上一层
}
if(in[pos[i]-1])
{
add(i,pos[i]+n-1,x);下一层
}
之后就可以跑最短路了
const int maxn=1e5+5;
const int maxm=2e4+5;
struct node
{
int to;
int cost;
int nx;
}edge[maxn*5];
int pos[maxn],vis[maxn*2],dis[maxn*2],head[maxn*5],in[maxn],num;
void init()
{
ms(pos,0),ms(vis,0),ms(dis,inf),ms(head,-1),ms(in,0);
num=0;
}
void add(int a,int b,int c)
{
num++;
edge[num].to=b;
edge[num].cost=c;
edge[num].nx=head[a];
head[a]=num;
}
struct cmp
{
bool operator () (const int a,const int b)
{
return dis[a]>dis[b];
}
};
void dijstra(int st,int n)
{
dis[st]=0;
priority_queue<int,vector<int>,cmp>pq;
pq.push(st);/*
rep(i,1,n)
{
cout<
while(!pq.empty())
{
int now=pq.top();
//de(now);
pq.pop();
if(vis[now])
{
continue;
}
vis[now]=1;
//de(now);
for(int i=head[now];i!=-1;i=edge[i].nx)
{
//de(i);
int to=edge[i].to;
int cost=edge[i].cost;
if(!vis[to]&&dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
pq.push(to);
}
}
}
}
void solve()
{
int t=read();
rep(cas,1,t)
{
init();
int n=read(),m=read(),x=read();
rep(i,1,n)
{
pos[i]=read();
in[pos[i]]=1;
}
while(m--)
{
int a=read(),b=read(),c=read();
add(a,b,c);
add(b,a,c);
}
rep(i,1,n)
{
add(pos[i]+n,i,0);
if(in[pos[i]+1])
{
add(i,pos[i]+n+1,x);
}
if(in[pos[i]-1])
{
add(i,pos[i]+n-1,x);
}
}
dijstra(1,n);/*
rep(i,1,n)
{
cout<
// de(000000);
if(dis[n]==inf){dis[n]=-1;}
printf("Case #%d: %d\n",cas,dis[n]);
}
}
Q - Marriage Match IV HDU - 3416
求出最短路的个数,相同的路不能重复走,但是节点可以
很明显是一道最大流问题…刚学的,不熟练
先双向spfa一遍,然后对于每一条路假设为a–>b=cost
如果正向dis1[a]+反向dis[b]+cost等于正向终点dis[n]就代表a–>b=cost这条路在最短路里面,注意我防止数据溢出,改成了dis1[a]+dis2[b]==dis1[a]-cost
既然在最短路里面,我们就建一个新图,只存放最短路里面的路径,权值设置为1,就可以实现每条路只能走一次这个功能了,然后跑一个最大流就OK了,我只学了EK和dinc还不熟悉,等熟悉了再去学sap吧
贴一个dinc讲解的链接–>点这
然后就放代码吧,,最大流还不熟悉
const int maxn=1e3+5;
const int maxm=1e5+5;
int S,T,n,m,num;
int head[maxm][2],vis[maxn],dis[maxn][2],hhead[maxn],ddis[maxn],cnt;
struct node
{
int from;
int to;
int cost;
int nx;
}edge[maxm][2],g[maxm*2];
void init()
{
ms(vis,0),ms(head,-1),ms(dis,inf);
num=-1;
cnt=-1;
}
void add(int a,int b,int c,int pos)
{
num++;
edge[num][pos].from=a;
edge[num][pos].to=b;
edge[num][pos].cost=c;
edge[num][pos].nx=head[a][pos];
head[a][pos]=num;
edge[num][pos^1].from=b;
edge[num][pos^1].to=a;
edge[num][pos^1].cost=c;
edge[num][pos^1].nx=head[b][pos^1];
head[b][pos^1]=num;
}
void spfa(int st,int pos)
{
ms(vis,0);
vis[st]=1;
dis[st][pos]=0;
queue<int>que;
que.push(st);
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
for(int i=head[now][pos];i!=-1;i=edge[i][pos].nx)
{
int to=edge[i][pos].to;
int cost=edge[i][pos].cost;
if(dis[to][pos]>dis[now][pos]+cost)
{
dis[to][pos]=dis[now][pos]+cost;
if(!vis[to])
{
vis[to]=1;
que.push(to);
}
}
}
}
}
void add1(int a,int b,int c)
{
cnt++;
g[cnt].to=b;
g[cnt].cost=c;
g[cnt].nx=hhead[a];
hhead[a]=cnt;
}
bool bfs()
{
ms(ddis,-1);
queue<int>que;
ddis[S]=0;
que.push(S);
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=hhead[now];i!=-1;i=g[i].nx)
{
int to=g[i].to;
int cost=g[i].cost;
if(ddis[to]==-1&&cost>0)
{
ddis[to]=ddis[now]+1;
que.push(to);
}
}
}
return ddis[T]!=-1;
}
int dfs(int pos,int exp)
{
if(pos==T)
{
return exp;
}
int flow=0,tmp=0;
for(int i=hhead[pos];i!=-1;i=g[i].nx)
{
int to=g[i].to;
int cost=g[i].cost;
if((ddis[to]==(ddis[pos]+1))&&cost>0)
{
tmp=dfs(to,min(exp,cost));
if(tmp==0)
{
continue;
}
exp-=tmp;
flow+=tmp;
g[i].cost-=tmp;
g[i^1].cost+=tmp;
if(!exp)
{
break;
}
}
}
return flow;
}
void dinc()
{
ms(hhead,-1);
for(int i=0;i<=num;i++)
{
int from=edge[i][0].from;
int to=edge[i][0].to;
int cost=edge[i][0].cost;
if(dis[from][0]+dis[to][1]==dis[T][0]-cost)
{
//cout<
add1(from,to,1);
add1(to,from,0);
}
}
int ans=0;
while(bfs())
{
//de(ans);
ans+=dfs(S,inf);
}
printf("%d\n",ans);
}
void solve()
{
init();
n=read(),m=read();
while(m--)
{
int a=read(),b=read(),c=read();
if(a==b) continue;
add(a,b,c,0);
//add(b,a,c,1);
}
S=read(),T=read();
spfa(S,0);
spfa(T,1);
//de(dis[T][0]);
//de(dis[S][1]);
dinc();
}
R - 0 or 1 HDU - 4370
说实话,这个题我到现在都没理解为什么对应位置的点乘就是最短路了…
矩阵形式很像最短路,我看出来了,但是点乘怎么就是最短路了??可能我理解错了那个运算符号的意思吧…
直接放代码吧,我讲不了为啥这就是最短路,,等我理解了再填坑
const int maxn=3e2+5;
const int maxm=2e4+5;
int arr[maxn][maxn];
int vis[maxn],dis[maxn];
void spfa(int st,int n)
{
queue<int>que;
rep(i,1,n)
{
if(i==st)
{
vis[i]=0;
dis[i]=inf;
}
else
{
vis[i]=1;
dis[i]=arr[st][i];
que.push(i);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
for(int i=1;i<=n;i++)
{
if(dis[i]>dis[now]+arr[now][i])
{
dis[i]=dis[now]+arr[now][i];
if(!vis[i])
{
vis[i]=1;
que.push(i);
}
}
}
}
}
void solve()
{
int n;
while(~scanf("%d",&n))
{
rep(i,1,n)
{
rep(j,1,n)
{
arr[i][j]=read();
}
}
int loop1,loop2;
spfa(n,n);
loop1=dis[n];
spfa(1,n);
loop2=dis[1];
printf("%d\n",min(loop1+loop2,dis[n]));
}
}
S - Layout POJ - 3169
题意:给出牛的数量n,给出m1,m2
之后m1行代表b不得超过a距离为c 即b-a<=c
m2行代表b起码超过a距离为c 即b-a>=c
求出1到n的最大距离
题目隐含意思b不可能在a前面,最多相同位置,因为从前往后排,后面的可能挤在它前面的位置,或者往后排,且a
const int maxn=1e3+5;
const int maxm=2e4+5;
struct node
{
int to;
int cost;
int nx;
}edge[maxm*2];
int head[maxn],inque[maxn],vis[maxn],dis[maxn],num;
void init()
{
ms(head,-1),ms(vis,0),ms(inque,0),ms(dis,inf);
num=0;
}
void add(int a,int b,int c)
{
num++;
edge[num].to=b;
edge[num].cost=c;
edge[num].nx=head[a];
head[a]=num;
}
bool spfa(int st,int n)
{
vis[st]=1;
dis[st]=0;
inque[st]++;
queue<int>que;
que.push(st);
while(!que.empty())
{
int now=que.front();
vis[now]=0;
que.pop();
for(int i=head[now];i!=-1;i=edge[i].nx)
{
int to=edge[i].to;
int cost=edge[i].cost;
if(dis[to]>dis[now]+cost)
{
dis[to]=dis[now]+cost;
if(!vis[to])
{
vis[to]=1;
que.push(to);
if(++inque[to]>=n)
{
return true;
}
}
}
}
}
return false;
}
void solve()
{
init();
int n=read(),m1=read(),m2=read();
while(m1--)
{
int a=read(),b=read(),c=read();
add(a,b,c);
}
while(m2--)
{
int a=read(),b=read(),c=read();
add(b,a,-c);
}
if(spfa(1,n))
{
puts("-1");
}
else if(dis[n]==inf)
{
puts("-2");
}
else
{
printf("%d\n",dis[n]);
}
}
完结了!!
结束了最短路专题,接下来把并查集复习一下解决掉生成树,再看看二分匹配和最大流