hud 4460
多源最短路---交朋友
朋友链的最短长度,做n次SPFA,n个点依次作为起点,求出最短距离,如果有一次的结果是INF,那么就可以退出输出了,BFS就用在了SPFA上
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; //多源最短路问题 //SPFA+暴力 //求两点最大距离 struct node{ int to,dis,from; int next; }ed[151511]; int head[10010]; int dis[10010]; int vis[10010]; int cnt=0; int n,m; void add(int from,int to,int dis){ ed[cnt].to=to; ed[cnt].dis=dis; ed[cnt].next=head[from]; head[from]=cnt++; } int spfa(int s){ queue<int> q; q.push(s); for(int i=1;i<=n;i++) dis[i]=0x3f3f3f3f; dis[s]=0; memset(vis,0,sizeof(vis)); vis[s]=1; while(!q.empty()){ int t=q.front(); q.pop(); vis[t]=0; for(int i=head[t];~i;i=ed[i].next){ int v=ed[i].to; if(dis[v]>dis[t]+ed[i].dis){ dis[v]=dis[t]+ed[i].dis; if(vis[v]==0){ vis[v]=1; q.push(v); } } } } //直接在这判断最远 int mi=0; for(int i=1;i<=n;i++){ if(dis[i]>mi) mi=dis[i]; } if(mi==0x3f3f3f3f) return -1; else return mi; } int main(){ while(~scanf("%d",&n)){ char ss[1000]; if(n==0) break; map<string,int> aa; int tot=0; for(int i=0;i<n;i++){ scanf("%s",ss); ///!!! if(aa[ss]==0){ aa[ss]=++tot; //避免重复 } } scanf("%d",&m); tot=0; memset(head,-1,sizeof(head)); for(int i=0;i<m;i++){ scanf("%s",ss); int u=aa[ss]; scanf("%s",ss); int v=aa[ss]; add(u,v,1); add(v,u,1); } //做n次spfa bool isok=0; int maxx=-INF,temp; for(int i=1;i<=n;i++){ temp=spfa(i); if(temp==-1) { isok=1; break; } else maxx=max(maxx,temp); } if(isok) printf("-1 "); else printf("%d ",maxx); } return 0; }
1448:【例题1】电路维修
这道题难在建图这里
我们可以把电路板上的每个格点(横线与竖线的交叉点)看作无向图中的结点。
若两个结点x和y是某个小方格的两个对角,则在x与y之间连边。若该方格中的标准件(对角线)与x到y的线段重合,则边权为0;若垂直相交,则边权为1(说明需要旋转1次才能连通)。然后,
我们在这个无向图中求出从左上角到右下角的最短距离,就得到了结果。
这是一个边权要么是0,要么是1的无向图。在这样的图上,我们可以通过双端队列广度搜索计算。
分支边权为1,从队尾入
分支边权为0,从队首入
保证两段性和单调性
//!!!!if((r+c)%2) 那么无解
这个双端队列和平时写的不一样,是从中点分开的向两边扩展
还是这个讲的比较好https://www.cnblogs.com/xsl19/p/12380226.html
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3f3f3f3f; typedef long long LL; typedef unsigned long long ull; //还是这个讲的比较好https://www.cnblogs.com/xsl19/p/12380226.html const int M=1e6+10; int dis[M],vis[M]; int n,m,t,tot; int head[M]; char mp[510][510]; struct node{ int to,w,nex; }ed[M<<1]; void adde(int x,int y,int w){ ed[++tot].to=y; ed[tot].w=w; ed[tot].nex=head[x]; head[x]=tot; } int id(int x,int y){ return (x)*(m+1)+y; } void bfs(int x){ memset(vis,0,sizeof(vis)); memset(dis,INF,sizeof(dis)); dis[0]=0; deque<int> q; //双端队列 q.push_back(x); while(!q.empty()){ int u=q.front(); q.pop_front(); if(vis[u]) continue; vis[u]=1; for(int i=head[u];i;i=ed[i].nex){ int tt=ed[i].to; if(vis[tt]) continue; if(dis[tt]>dis[u]+ed[i].w){ dis[tt]=dis[u]+ed[i].w; if(!ed[i].w) q.push_front(tt); //如果是正向的话,就插在队首 else q.push_back(tt); } } } } int main(){ scanf("%d",&t); while(t--){ scanf("%d %d",&n,&m); tot=0; memset(head,0,sizeof(head)); for(int i=1;i<=n;i++) scanf("%s",mp[i]+1); if((n+m)%2) { printf("NO SOLUTION "); continue; } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(mp[i][j]=='\'){ adde(id(i-1,j-1),id(i,j),0); //起点--终点原本的方向 adde(id(i,j),id(i-1,j-1),0); //终点--起点原本的方向 adde(id(i,j-1),id(i-1,j),1); //终点--起点 反向 adde(id(i-1,j),id(i,j-1),1); //起点--终点 反向 } else{ adde(id(i,j-1),id(i-1,j),0); adde(id(i-1,j),id(i,j-1),0); adde(id(i,j),id(i-1,j-1),1); adde(id(i-1,j-1),id(i,j),1); } } } bfs(0); printf("%d ",dis[id(n,m)]); } return 0; }
1452:Keyboarding
出自 World Final 2015 F. Keyboarding
给定一个 r 行 c 列的在电视上的“虚拟键盘”,通过「上,下,左,右,选择」共 5 个控制键,你可以移动电视屏幕上的光标来打印文本。一开始,光标在键盘的左上角,每次按方向键,光标总是跳到下一个在该方向上与当前位置不同的字符,若不存在则不移动。每次按选择键,则将光标所在位置的字符打印出来。
现在求打印给定文本(要在结尾打印换行符)的最少按键次数。
很容易想到是 BFS,但直接用 BFS 来会 TLE,因此我们需要进行优化
考虑对后置点进行优化,我们知道,通过 BFS 到达的点,如果之前被访问过,那么肯定之前访问的步数最小,因此在 BFS 的过程中,可以记录打印出的字符的位置,这样一来,
如果一个后置点的位置要小于前置点,那么无论如何距离也不可能更小,这样就不用向下进行扩展了,同时,如果后置点的位置更大,那么就将当前点的位置赋给后置点
原文链接:https://blog.csdn.net/u011815404/article/details/100811183
其实就是最优化剪枝:如果搜索到的点在之前已经搜索到了并且那一次搜索时匹配的字符串长度大于本次的长度,剪枝。(最优性剪枝)
下一步走到的点先进行预处理,方便广搜。
剪枝操作:
if(vis[nx][ny]<opp){
vis[nx][ny]=opp; //这里进行了剪枝
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=55; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; struct node{ int x,y; int op,step; ///选择次数,按键次数 node(){} node(int x,int y,int op,int step):x(x),y(y),op(op),step(step){} }; int n,m; //每个点的移动对象 struct nex{ int x,y; nex(){} nex(int x,int y):x(x),y(y){} }nxt[maxn][maxn][4]; char str[maxn][maxn],s[10005]; int g[maxn][maxn],vis[maxn][maxn]; void bfs(int len){ queue<node> q; memset(vis,-1,sizeof(vis)); q.push(node(1,1,0,0)); while(!q.empty()){ node top=q.front(); q.pop(); int xx=top.x,yy=top.y,opp=top.op,steps=top.step; if(str[xx][yy]==s[opp+1]){ q.push(node(xx,yy,opp+1,steps+1)); //这里如果是正确的话,opp这个值会加1,然后就会逐步形成最优 if(opp+1==len){ printf("%d ",steps+1); return; } } else{ for(int i=0;i<4;i++){ //这里就需要main里面预处理 int nx=nxt[xx][yy][i].x,ny=nxt[xx][yy][i].y; if(nx==0) continue; //超出范围 if(vis[nx][ny]<opp){ vis[nx][ny]=opp; //这里进行了剪枝 q.push(node(nx,ny,opp,steps+1)); } } } } } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) scanf("%s",str[i]+1); scanf("%s",s+1); int len=1; while(s[len]) len++; //这个判断条件就可以了 s[len]='*'; //表示最后还需要一个断行 //预处理能走的地方 for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=j-1;k>=1;k--){ //向上 if(str[i][j]!=str[i][k]){ //光标总是跳到下一个在该方向上与当前位置不同的字符 nxt[i][j][0]=nex(i,k); break; } } for(int k=j+1;k<=m;k++){ //向下 if(str[i][j]!=str[i][k]){ nxt[i][j][1]=nex(i,k); break; } } for(int k=i-1;k>=1;k--){ //向左 if(str[i][j]!=str[k][j]){ nxt[i][j][2]=nex(k,j); break; } } for(int k=i+1;k<=n;k++){ if(str[i][j]!=str[k][j]){ nxt[i][j][3]=nex(k,j); break; } } } } bfs(len); return 0; }
1453:移动玩具
和棋盘游戏一样的,注意二进制的使用方法
如果只是单纯从起点开始搜索:注意操作temp^x^y,其实就是把x和y两个位置的数字交换了
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; //BFS和位运算 //其实位运算更难 struct node{ int num; //当前的状态:压缩 int step; //到这里的步数 }; queue<node> q; int aa,bb; //分别是起始和终点状态 int vis[100000]; void bfs(){ q.push((node){aa,0}); while(!q.empty()){ node head=q.front(); q.pop(); if(head.num==bb){ cout<<head.step<<endl; return; } int tem=head.num; for(int i=15;i>=0;i--){ int x=(15-i)/4;//行 int y=(15-i)%4; //列 int w=1<<i; if(y<3&&(tem&(1<<i))!=(tem&(1<<i-1))){ //左右两边不同,可以交换 int z=1<<i-1; if(!vis[tem^z^w]){ //其实这样就算两个位值交换了 vis[tem^z^w]=1; q.push((node){tem^z^w,head.step+1}); } } if(x<3&&(tem&(1<<i))!=(tem&(1<<i-4))){ int z=1<<i-4; if(!vis[tem^w^z]){ vis[tem^w^z]=1; q.push((node){tem^w^z,head.step+1}); } } } } } int main(){ char c; //我还以为这个格式很难控制。。。。学的不够 for(int i=15;i>=0;i--){ cin>>c; if(c!='0') aa+=1<<i; } for(int i=15;i>=0;i--){ cin>>c; if(c!='0') bb+=1<<i; } if(aa==bb) cout<<"0"<<endl; else bfs(); return 0; }
如果用双向广搜,记得标记方向
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=(1<<16)+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //这种题和八数码也蛮像的 //重点是状态表达---状态转移(bfs //而且这种01表达的就自然很能想到二进制,所以二进制的操作要记得 //可以用双向广搜 int vis[2][maxn]; //不仅可以用来判重,还可以用来计算步数 int que[2][maxn],s,e; void inti(){ char x; for(int i=1;i<=16;i++){ cin>>x; //可以直接这样用,也可以用getchar s=(s<<1)+(x-'0'); } for(int i=1;i<=16;i++){ cin>>x; e=(e<<1)+(x-'0'); } } int flag,now,u,head[2],tail[2]; void expand(int des,int b){ //b是哪个方向的移动,des是目标 if(now&(1<<des)) return; //如果当前位置有玩具了 int k=now+(1<<des); if(vis[b][k]) return; if(vis[b^1][k]) { printf("%d ",vis[b^1][k]+vis[b][u]-1); //如果已经走到了这里来了,就需要相加-1 flag=1; return; } vis[b][k]=vis[b][u]+1; que[b][++tail[b]]=k; //入队 } void bfs(){ head[0]=head[1]=tail[0]=tail[1]=1; que[0][head[0]]=s; que[1][head[1]]=e; vis[0][s]=1; vis[1][e]=1; if(s==e){ printf("0 "); return; } while(head[0]<=tail[0]&&head[1]<=tail[1]){ u=que[0][head[0]++]; for(int i=15;i>=0;i--){ if(!u&&(1<<i)) continue; //这里没有玩具 now=u^(1<<i); //让他这里变得没有玩具 if(i/4<3) expand(i+4,0); if(flag) return; if(i/4>0) expand(i-4,0); if(flag) return; if(i%4!=3) expand(i+1,0); if(flag) return; if(i%4!=0) expand(i-1,0); if(flag) return; } u=que[1][head[1]++]; for(int i=15;i>=0;i--){ if(!u&&(1<<i)) continue; //这里没有玩具 now=u^(1<<i); //让他这里变得没有玩具 if(i/4<3) expand(i+4,1); if(flag) return; if(i/4>0) expand(i-4,1); if(flag) return; if(i%4!=3) expand(i+1,1); if(flag) return; if(i%4!=0) expand(i-1,1); if(flag) return; } } } int main(){ inti(); bfs(); return 0; } #include<bits/stdc++.h> using namespace std; #define maxn 20 #define maxm (1 << 16) + 5 #define file(a) freopen(#a".in","r",stdin); freopen(#a".out","w",stdout); typedef long long ll; int v[2][maxm], q[2][maxm]; int s, e, h[2], t[2], now, u, k, f; char c; int read(){ char orz; int tql = 0, qwq = 1; orz = getchar(); while(orz > '9' || orz < '0'){ if(orz == '-') qwq = -1; orz = getchar(); } while(orz >= '0' && orz <= '9'){ tql = (tql << 3) + (tql << 1) + orz - '0'; orz = getchar(); } return tql * qwq; } void init(){ for(int i = 1; i <= 16; i++){ cin >> c;//getchar会更快 s = (s << 1) + c - '0'; } for(int i = 1; i <= 16; i++){ cin >> c; e = (e << 1) + c - '0'; } } void expand(int m, int b){ if(now & (1 << m)) return;//要换的位置有玩具 int k = now + (1 << m);//换后的棋盘 if(v[b][k]) return;//判重 if(v[b ^ 1][k]){//对方搜过则直接输出 printf("%d", v[b ^ 1][k] + v[b][u] - 1); f = 1; return; } v[b][k] = v[b][u] + 1;//步数+1 q[b][++t[b]] = k;//入队 } void bfs(){ h[0] = t[0] = h[1] = t[1] = 1; q[0][h[0]] = s; v[0][s] = true; q[1][h[1]] = e; v[1][e] = true; if(s == e){ printf("0"); return; }//特判一下 while(h[0] <= t[0] && h[1] <= t[1]){ u = q[0][h[0]++]; for(int i = 15; i >= 0; i--){ if(! (u & (1 << i))) continue;//当前位木有玩具 now = u ^ (1 << i);//当前位有玩具变没玩具,没玩具变有玩具 if(i / 4 < 3) expand(i + 4, 0); if(f) return;//最后一行不可以向下移 if(i / 4 > 0) expand(i - 4, 0); if(f) return;//第一行不可以向上移 if(i % 4 != 3) expand(i + 1, 0); if(f) return;//最后一列不可向右移 if(i % 4 != 0) expand(i - 1, 0); if(f) return;//第一列不可以左移 } u = q[1][h[1]++]; for(int i = 15; i >= 0; i--){ if(! (u & (1 << i))) continue; now = u ^ (1 << i); if(i / 4 < 3) expand(i + 4, 1); if(f) return; if(i / 4 > 0) expand(i - 4, 1); if(f) return; if(i % 4 != 3) expand(i + 1, 1); if(f) return; if(i % 4 != 0) expand(i - 1, 1); if(f) return; } } } int main(){ //file(date); init(); bfs(); return 0; }
1454:山峰和山谷
广搜的剪枝 ,每个点进行广搜,每搜完一个点,把与它相连接的点标记,不重复的搜。每个点之搜索依次,
特别注意,如果整个地图方格的高度均相同,则整个地图既是一个山谷,也是一个山峰。
tips:判断是山峰还是山谷(flag1,flag2),这个连通块里面有多少个点(判断整张图)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //广搜的剪枝 ,每个点进行广搜,每搜完一个点,把与它相连接的点标记,不重复的搜。 //特别注意,如果整个地图方格的高度均相同,则整个地图既是一个山谷,也是一个山峰。 int mp[maxn][maxn]; int n,high,low,cnt,vis[maxn][maxn]; int dis[10][2]={0,1,0,-1,1,0,-1,0,1,-1,-1,-1,-1,1,1,1}; //md这个写错了!! struct node{ int x,y; //node(int xx,int yy) x(xx),y(yy){}; }; void BFS(int x1,int y1){ bool flag1=0,flag2=0; int cnt=1; vis[x1][y1]=1; queue<node> q; node now,pre; pre.x=x1; pre.y=y1; q.push(pre); while(!q.empty()){ now=q.front(); q.pop(); for(int i=0;i<8;i++){ pre.x=now.x+dis[i][0]; pre.y=now.y+dis[i][1]; if(pre.x>n||pre.y>n||pre.x<=0||pre.y<=0||(vis[pre.x][pre.y]&&mp[pre.x][pre.y]==mp[now.x][now.y])) //标记已走过的坐标,且改坐标对应的值与之前的值相同 continue; else if(mp[pre.x][pre.y]==mp[now.x][now.y]){ q.push(pre); vis[pre.x][pre.y]=true; cnt++; //cout<<cnt<<endl; } else{ if(mp[now.x][now.y]>mp[pre.x][pre.y]) flag1=true; else flag2=true; } } } if(flag1&&flag2) return; else if(flag1) high++; else if(flag2) low++; if(cnt==n*n) { low++;high++; } return; } int main(){ scanf("%d",&n); low=0;high=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ cin>>mp[i][j]; } } memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(!vis[i][j]) { // cout<<"heihei "<<i<<j<<endl; BFS(i,j); } } } printf("%d %d",high,low); return 0; }