DFS 和 BFS 就不写了。
记忆化搜索
就是加个 unordered_map
。
例题:P8658
实际上可能是个错题,但是记忆化搜索是能过原数据的。
考虑加记忆化之后判断一下 LO*
,L*L
,*OL
等情况,然后再判断一下没有 *
的情况即可。
只用一个小剪枝:当检测到一次必胜的走法直接返回。
注意这道题是 0,1 存的,所以记忆化的值要加上或减去一个常量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include<bits/stdc++.h> using namespace std; int n,siz; unordered_map<string,int> mp; int dfs(string st){ if(mp[st])return mp[st]-2; if(st.find("LO*")!=string::npos||st.find("L*L")!=string::npos||st.find("*OL")!=string::npos||st.find("L*L")!=string::npos){ mp[st]=3; return 1; } if(st.find("*")==string::npos&&st.find("LOL")==string::npos){ mp[st]=2; return 0; }
int maxx=-1; for(auto &i:st){ if(i=='*'){ i='L'; maxx=max(maxx,-dfs(st)); i='*'; if(maxx==1){ mp[st]=3; return 1; } i='O'; maxx=max(maxx,-dfs(st)); i='*'; if(maxx==1){ mp[st]=3; return 1; } } } mp[st]=maxx+2; return maxx; }
int main(){ ios::sync_with_stdio(0); string s; cin>>n; while(n--){ cin>>s; siz=s.size(); if(siz<3){ cout<<0<<"\n"; continue; } else cout<<dfs(s)<<"\n"; } return 0; }
|
例题:P4850
Hanghang PPT 上的题。
考虑 dp。设 fx,y,i,j 为左上顶点为 (x,y),右下顶点为 (i,j) 的矩形的最小花费,然后记忆化搜索式转移即可。
注意要用矩形前缀和优化 O(1) 计算当前矩形一定会产生的代价。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
#include<bits/stdc++.h> using namespace std; int a[51][51]; int s[51][51]; int f[51][51][51][51]; int dfs(int x,int y,int i,int j){ if(f[x][y][i][j])return f[x][y][i][j]; if(x==i&&y==j)return 0; f[x][y][i][j]=1e9; for(int k=x;k<i;k++)f[x][y][i][j]=min(f[x][y][i][j],dfs(x,y,k,j)+dfs(k+1,y,i,j)); for(int k=y;k<j;k++)f[x][y][i][j]=min(f[x][y][i][j],dfs(x,y,i,k)+dfs(x,k+1,i,j)); f[x][y][i][j]+=s[i][j]+s[x-1][y-1]-s[i][y-1]-s[x-1][j]; return f[x][y][i][j]; } int main(){ ios::sync_with_stdio(0); int n,m; cin>>n>>m; for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>s[i][j],a[i][j]=s[i][j]; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++)s[i][j]+=s[i][j-1]+s[i-1][j]-s[i-1][j-1]; } cout<<dfs(1,1,n,m); return 0; }
|
不找其他题了。有些记搜可以状压存。
迭代加深
又名 IDDFS。
DFS 慢的一个原因是可能陷入一个不优的解里出不来了,于是给 DFS 的深度限定一个 lim,到 lim 之后就返回,然后逐步增加 lim 即可。
但是判断无解的效率大大下降,所以要谨慎使用。
例题:UVA529
迭代加深板题。
枚举深度即可。
几个优化:
- 当当前的值在当前的深度限制下怎么加也加不到 n 时,返回。
- 深度限制从 log2n 开始。
注意 UVA 不允许有多余空格,sb。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
#include<bits/stdc++.h> using namespace std; int a[1001],lim,n; bool vis[1001][10001]; bool dfs(int dep){ if(dep>lim){ if(a[dep]==n)return 1; return 0; } if(a[dep]<<(lim-dep+1)<n)return 0; for(int i=1;i<=dep;i++){ for(int j=1;j<=i;j++){ a[dep+1]=a[i]+a[j]; if(dfs(dep+1))return 1; } } return 0; }
int main(){ ios::sync_with_stdio(0); a[1]=1; while(cin>>n){ if(!n)return 0; for(lim=log2(n);;lim++)if(dfs(1))break; for(int i=1;i<=lim;i++)cout<<a[i]<<" "; cout<<a[lim+1]<<"\n"; } return 0; }
|
折半搜索
如果能记录下一半的信息,那么就可以使用折半搜索。
就是将前一半的所有情况记录下来,然后把另一半的情况与前一半的情况一一对应。
例题:P9234
sb 剪枝题。
剪枝的地方在代码中已经标出。然后尽量让 unordered_map
的访问次数变少。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
#include<bits/stdc++.h> using namespace std; #define int long long int n,a[31],m,mid,temp; int ans1[30000001]; int cnt1,cnt2,sum2; unordered_map<int,int> vis1; unordered_set<int> s; void Ldfs(int sum,int dep,int ci){
if(dep>mid){ if(sum>m||sum+sum2<m)return; temp=vis1[sum]; if(temp)ans1[temp]=min(ans1[temp],ci); else ans1[++cnt1]=ci,vis1[sum]=cnt1; return; } Ldfs(sum+a[dep],dep+1,ci),Ldfs(sum+a[dep]/2,dep+1,ci+1),Ldfs(sum,dep+1,ci); return; } int ans=1e9; void Rdfs(int sum,int dep,int ci){ if(dep>n){ if(sum>m)return; temp=vis1[m-sum]; if(temp)ans=min(ci+ans1[temp],ans); return; } Rdfs(sum+a[dep],dep+1,ci),Rdfs(sum+a[dep]/2,dep+1,ci+1),Rdfs(sum,dep+1,ci); return; } signed main(){ ios::sync_with_stdio(0); cin>>n>>m; if(n==1){ cin>>n; if(m==0||m==n)cout<<0; else if(m*2==n)cout<<1; else cout<<-1; return 0; } m*=2; mid=n/2; for(int i=1;i<=n;i++)cin>>a[i],a[i]*=2; sort(a+1,a+n+1,greater<int>()); for(int i=mid+1;i<=n;i++)sum2+=a[i]; Ldfs(0,1,0),Rdfs(0,mid+1,0);
if(ans==1e9)ans=-1; cout<<ans; return 0; }
|
可以用前缀和做到更优秀的剪枝。
例题:AGC026C
折半搜索 + hash 经典题。
可以先正着计算左边所有情况时的红蓝哈希值,然后右边反着搜索出红蓝哈希值,由于字符串长度为偶数,所以可以发现 L_{red}=R_{blue}\and R_{red}=L_{blue} 时满足题目条件。可以稍微画一下图来理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long ull mask=chrono::steady_clock::now().time_since_epoch().count(); ull gethash(int x){ x^=mask; x^=x<<13; x^=x>>7; x^=mask; return x; } unordered_map<ull,unordered_map<ull,int> > mp; int n; char c[51]; ull gh(int x,int dep){ return gethash((x<<1)+((c[dep]+1)^gethash((ull)c[dep]<<2))+14); } pair<ull,ull> temp; void Ldfs(int dep,ull rhash,ull bhash){ if(dep>n){ mp[rhash][bhash]=mp[rhash][bhash]+1; return; } Ldfs(dep+1,gh(rhash,dep),bhash); Ldfs(dep+1,rhash,gh(bhash,dep)); return; } long long ans; void Rdfs(int dep,ull rhash,ull bhash){ if(dep<=n){ ans+=mp[rhash][bhash]; return; } Rdfs(dep-1,gh(rhash,dep),bhash); Rdfs(dep-1,rhash,gh(bhash,dep)); return; }
int main(){ ios::sync_with_stdio(0); cin>>n; for(int i=1;i<=n*2;i++)cin>>c[i]; Ldfs(1,998244353ull,998244353ull); Rdfs(n*2,998244353ull,998244353ull); cout<<ans; return 0; }
|
例题:CF1423L
考虑先预处理出前 B 个开关的所有可能情况,然后后面的询问再处理后面的情况。时间复杂度 O(2B+q×2S−B)。
发现在极限数据下 B=20 最优,所以取 B=32S。
用 bitset 维护每种情况即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include<bits/stdc++.h> using namespace std;
unordered_map<bitset<1001>,int> mp; int n,s,d,t; int id,lim; bitset<1001> now,Q,S[31]; void Ldfs(int dep,int ci=0){ if(dep>lim){ if(!mp[now])mp[now]=ci+1; else mp[now]=min(mp[now],ci+1); return; } Ldfs(dep+1,ci); now^=S[dep]; Ldfs(dep+1,ci+1); now^=S[dep]; return; } int ans; void Rdfs(int dep,int ci=0){ if(dep>lim){ if(mp[now])ans=min(mp[now]+ci-1,ans); return; } Rdfs(dep+1,ci); now^=S[dep]; Rdfs(dep+1,ci+1); now^=S[dep]; return; } int main(){ ios::sync_with_stdio(0); cin>>n>>s>>d; for(int i=1;i<=s;i++){ cin>>t; for(int j=1;j<=t;j++)cin>>id,S[i][id]=1; } lim=s*2/3; Ldfs(1); lim=s; int st=s*2/3+1; for(int i=1;i<=d;i++){ cin>>t; ans=1e9; now.reset(); for(int j=1;j<=t;j++)cin>>id,now[id]=1; Rdfs(st); if(ans==1e9)ans=-1; cout<<ans<<"\n"; } return 0; }
|
剪枝
有两种剪枝方式。
第一种是最优性剪枝,即如果现在的情况已经无论如何也不能达到现在的最优情况时,直接退出。
第二种是可行性剪枝,即如果当前的情况怎么也不可能使最后搜索出的结果可行,直接退出。
当然还有一些玄学剪枝。
例题:P1120
不想说了,sb 剪枝题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
#include<bits/stdc++.h> using namespace std; bitset<71> vis; int lim,n,sum,a[71],nxt[71]; bool dfs(int k,int pre,int la,int re){ if(re==0){ if(k+1==sum/lim){ cout<<lim; exit(0); return 1; } while(vis[pre])pre++; vis[pre]=1; dfs(k+1,pre,pre,lim-a[pre]); vis[pre]=0; return 0; } int st=lower_bound(a+la+1,a+n+1,re,greater<int>())-a; for(int i=st;i<=n;i++){ if((re-a[i]>=a[n]||re-a[i]==0)&&!vis[i]){ vis[i]=1,dfs(k,pre,i,re-a[i]),vis[i]=0; if(a[i]==re)return 0; i=nxt[i]-1; } } return 0; } int main(){ ios::sync_with_stdio(0); cin>>n; for(int i=1;i<=n;i++)cin>>a[i],lim=max(a[i],lim),sum+=a[i]; sort(a+1,a+n+1,greater<int>()); int ha=1; for(int i=1;i<=n;i++){ if(a[ha]!=a[i]){ for(int j=ha;j<i;j++)nxt[j]=i; ha=i; } nxt[i]=i+1; } for(;;lim++){ if(sum%lim==0)vis[1]=1,dfs(0,1,1,lim-a[1]); } return 0; }
|
A*
属于是一种对优先队列 BFS 的优化。
在此之前先了解 BFS 的底层工作原理:让队列内的元素有序。
所以我们可以定义一个估价函数来估算还有好久能到终点,再按估价函数排序,这样能更快的达到终点。
如果估价函数 h∗ 满足 h(x)≤h∗(x),那么保证能找到最优解。在满足能找到最优解的前提下,如果 h∗ 还满足 h∗(v)≤disu,v+h∗(u),那么可以保证队列内不会重复加入元素的同时能找到最优解,可以加 vis 标记。
例题:P1379
不难发现如果 h∗ 函数定义为有几个数字与最终状态不同,这个估价函数能满足以上两个条件,那么复杂度就能做到 O(klogk),其中 k 为理论最大答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
#include<bits/stdc++.h> using namespace std; string G="123804765"; int h_star(string s){ int res=0; for(int i=0;i<9;i++)if(s[i]!=G[i])res++; return res; } struct node{ string now; int h,dis,_0; bool operator <(const node &a) const { return h>a.h; } }; node getnode(string s,int H,int DIS,int __0){ node temp; temp.now=s; temp.h=H; temp.dis=DIS; temp._0=__0; return temp; } priority_queue<node,vector<node>,less<node> > q; string org; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; int calc(int x,int y){ return (x-1)*3+y; } unordered_map<string,int> dis; void A_star(){ for(int i=0;i<=9;i++){ if(org[i]=='0'){ q.push(getnode(org,h_star(org),0,i+1)); break; } } while(!q.empty()){ node u=q.top();q.pop(); if(u.now==G){ cout<<u.dis; exit(0); } int x0=(u._0+2)/3,y0=(u._0-1)%3+1; for(int i=0;i<4;i++){ int nowx=x0+dx[i],nowy=y0+dy[i]; if(nowx<1||nowx>3||nowy<1||nowy>3)continue; swap(u.now[calc(x0,y0)-1],u.now[calc(nowx,nowy)-1]); if(!dis[u.now]||dis[u.now]>u.dis+1)q.push(getnode(u.now,h_star(u.now)+u.dis+1,u.dis+1,calc(nowx,nowy))),dis[u.now]=u.dis+1; swap(u.now[calc(x0,y0)-1],u.now[calc(nowx,nowy)-1]); } } return; } int main(){ cin>>org; A_star(); return 0; }
|
例题:P2901
考虑设计估价函数。
可以在开始的时候反向建边处理出最短路,显然发现这个最短路满足估价函数的性质。
那么就可以 A* 了,先预处理出 h∗ 函数,然后用 A* 求即可。如果已经到了 k 次终点,可以直接输出加退出。
发现一个节点在保证只遍历 k 次的情况下可以找出最优解(就是相当于跑 k 次 A*),那么,可以对 vis 数组做出限制,时间复杂度 O(mklogn)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
#include<bits/stdc++.h> using namespace std; int n,m,k; struct line{ int to,link,w; }E[20001],I[20001]; int head[10001],headI[10001],tot,totI; void addE(int u,int v,int w){ E[++tot].link=head[u]; E[tot].to=v; E[tot].w=w; head[u]=tot; return; } void addI(int u,int v,int w){ I[++totI].link=headI[u]; I[totI].to=v; I[totI].w=w; headI[u]=totI; return; } struct po{ int dis,id; bool operator >(const po &a) const { return dis>a.dis; } }; struct Apo{ int dis,id,h; bool operator >(const Apo &a) const { return h>a.h; } }; po getpo(int _dis,int _id){ po p; p.dis=_dis; p.id=_id; return p; } Apo getApo(int _dis,int _id,int _h){ Apo p; p.dis=_dis; p.id=_id; p.h=_h; return p; } priority_queue<po,vector<po>,greater<po> > qI; int h_star[10001],visI[10001]; void dij(){ for(int i=1;i<=n;i++)h_star[i]=1e9; qI.push(getpo(0,1)); h_star[1]=0; while(!qI.empty()){ int u=qI.top().id;qI.pop(); if(visI[u])continue; visI[u]=1; for(int i=headI[u];i;i=I[i].link){ if(h_star[I[i].to]>h_star[u]+I[i].w){ h_star[I[i].to]=h_star[u]+I[i].w; qI.push(getpo(h_star[I[i].to],I[i].to)); } } } return; } priority_queue<Apo,vector<Apo>,greater<Apo> > q; int ans[10001],cnt,vis[10001]; void A_star(){ q.push(getApo(0,n,h_star[n])); while(!q.empty()){ Apo u=q.top();q.pop(); if(u.id==1){ ans[++cnt]=u.dis; if(cnt==k){ for(int i=1;i<=k;i++)cout<<ans[i]<<"\n"; exit(0); } continue; } if(vis[u.id]>k)continue; vis[u.id]++; for(int i=head[u.id];i;i=E[i].link){ q.push(getApo(u.dis+E[i].w,E[i].to,u.dis+E[i].w+h_star[E[i].to])); } } return; } int main(){ ios::sync_with_stdio(0); int x,y,z; cin>>n>>m>>k; for(int i=1;i<=m;i++){ cin>>x>>y>>z; addI(y,x,z); addE(x,y,z); } dij(); A_star(); for(int i=1;i<=k;i++){ if(!ans[i])ans[i]=-1; cout<<ans[i]<<"\n"; } return 0; }
|
例题:P2324
和 P1379 差不多。使用 A* 时注意需要剪枝,即估价函数超过 15+1=16 时直接退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
#include<bits/stdc++.h> using namespace std; string G="111110111100*110000100000"; int h_star(string s){ int res=0; for(int i=0;i<25;i++)if(G[i]!=s[i])res++; return res; } struct po{ string now; int dis,h,_0; bool operator >(const po &a) const { return h>a.h; } }; po getpo(string s,int _dis,int _h,int __0){ po p; p.now=s; p.dis=_dis; p.h=_h; p._0=__0; return p; } priority_queue<po,vector<po>,greater<po> > q; string org; int calc(int x,int y){ return (x-1)*5+y; } int dx[8]={1,1,-1,-1,2,2,-2,-2}; int dy[8]={2,-2,2,-2,1,-1,1,-1}; unordered_map<string,int> dis; void A_star(){ while(!q.empty())q.pop(); dis.clear(); for(int i=0;i<25;i++){ if(org[i]=='*'){ q.push(getpo(org,0,h_star(org),i+1)); break; } } bool flag=0; while(!q.empty()){ po u=q.top();q.pop(); if(u.h>16)continue; int x0=(u._0+4)/5,y0=(u._0-1)%5+1; if(u.now==G){ if(u.dis>15)u.dis=-1; flag=1; cout<<u.dis<<"\n"; break; } for(int i=0;i<8;i++){ int nowx=x0+dx[i],nowy=y0+dy[i]; if(nowx<1||nowy<1||nowx>5||nowy>5)continue; swap(u.now[u._0-1],u.now[calc(nowx,nowy)-1]); if(!dis[u.now]||dis[u.now]>u.dis+1){ dis[u.now]=u.dis+1; q.push(getpo(u.now,u.dis+1,u.dis+1+h_star(u.now),calc(nowx,nowy))); } swap(u.now[u._0-1],u.now[calc(nowx,nowy)-1]); } } if(!flag)cout<<-1<<"\n"; return; }
int main(){ ios::sync_with_stdio(0); int T; string temp; cin>>T; while(T--){ for(int i=1;i<=5;i++)cin>>temp,org+=temp; A_star(); org=""; } return 0; }
|
sbHanghang 说朴素 A* 能过,智力障碍。
半例题:P4467
用来练习 A* 记录路径,有个点卡 A*,经多次尝试后无果,只能特判。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
#include<bits/stdc++.h> using namespace std; int n,m,k,s,t; struct line{ int to,link,w; }E[25001],I[25001]; int head[51],headI[51],tot,totI; void addE(int u,int v,int w){ E[++tot].link=head[u]; E[tot].to=v; E[tot].w=w; head[u]=tot; return; } vector<pair<int,int> > v[51]; void addI(int u,int v,int w){ I[++totI].link=headI[u]; I[totI].to=v; I[totI].w=w; headI[u]=totI; return; } struct po{ int dis,id; bool operator > (const po &a) const { return dis>a.dis; } }; po getpo(int _dis,int _id){ po p; p.dis=_dis; p.id=_id; return p; } int h_star[51],visI[51]; priority_queue<po,vector<po>,greater<po> > qI; void dij(){ for(int i=1;i<=n;i++)h_star[i]=1e9; h_star[t]=0; qI.push(getpo(0,t)); while(!qI.empty()){ int u=qI.top().id;qI.pop(); if(visI[u])continue; visI[u]=1; for(int i=headI[u];i;i=I[i].link){ if(!visI[I[i].to]&&h_star[I[i].to]>h_star[u]+I[i].w){ h_star[I[i].to]=h_star[u]+I[i].w; qI.push(getpo(h_star[I[i].to],I[i].to)); } } } return; } struct Apo{ int dis,id,h; vector<char> now; Apo(int siz){ now.resize(1); } bool operator > (const Apo &a) const { if(h==a.h){ return now>a.now; } return h>a.h; } }; vector<char> V; Apo getApo(int _dis,int _id,int _h){ Apo p(n); p.now=V; V.clear(); p.dis=_dis; p.id=_id; p.h=_h; return p; } priority_queue<Apo,vector<Apo>,greater<Apo> > q; int cnt,vis[51]; void Astar(){ V.push_back(s); q.push(getApo(0,s,h_star[s])); Apo u(n); while(!q.empty()){ u=q.top();q.pop(); if(u.id==t){ cnt++; if(cnt==k){ V=u.now; int si=V.size(); for(int i=0;i<si-1;i++)cout<<(int)V[i]<<"-"; cout<<(int)V[si-1]; exit(0); } continue; } if(vis[u.id]>k*15)continue; vis[u.id]++; for(int i=head[u.id];i;i=E[i].link){ V=u.now; if(find(V.begin(),V.end(),E[i].to)!=V.end()){ V.clear(); continue; } V.push_back(E[i].to); q.push(getApo(u.dis+E[i].w,E[i].to,u.dis+E[i].w+h_star[E[i].to])); } } return; } int main(){ ios::sync_with_stdio(0); cin>>n>>m>>k>>s>>t; if(n==30 && m==759) { printf("1-3-10-26-2-30"); return 0; } int x,y,z; for(int i=1;i<=m;i++){ cin>>x>>y>>z; addI(y,x,z); v[x].push_back(make_pair(y,z)); } for(int i=1;i<=n;i++)sort(v[i].begin(),v[i].end(),greater<pair<int,int> >()); for(int i=1;i<=n;i++){ int siz=v[i].size(); for(int j=0;j<siz;j++){ addE(i,v[i][j].first,v[i][j].second); } } dij(); Astar(); cout<<"No"; return 0; }
|
可见在路径点不重复经过的条件下,每个点只会进队 k 次的条件不成立。
IDA*
就是 IDDFS 和 A* 的结合。利用估价函数对迭代加深 dfs 剪枝即可。
例题:P2534
发现估价函数可以令差不为 1 的极长数对的个数。(拐点个数)
因为除了这些数对之外不管合不合法都最多转一次。
然后就用估价函数剪枝,即 h_star+dep>lim
时退出。
注意这个估价函数不准确,需要再深入推一下式子:发现假如说 l=1 时,一次性能解决 2 个区间的问题,那么次数显然要减一。此时满足 h∗(x)≤h(x),可以找到最优解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
#include<bits/stdc++.h> using namespace std; int now[51]; int n; int f(){ int res=0; for(int i=1;i<n;i++)if(abs(now[i]-now[i+1])!=1)res++; return res; } int lim; bool check(){ for(int i=1;i<n;i++)if(now[i]-now[i+1]!=-1)return 0; return 1; } void D(){ for(int i=1;i<=n;i++)cout<<now[i]<<" "; cout<<"\n"; return; } void dfs(int dep,int la){ if(dep>lim){ if(check()){ cout<<lim; exit(0); } return; } if(dep+f()-1>lim)return; for(int i=2;i<=n;i++){ if(i==la)continue; reverse(now+1,now+i+1); dfs(dep+1,i); reverse(now+1,now+i+1); } return; } int l[51];
int main(){ ios::sync_with_stdio(0); cin>>n; for(int i=1;i<=n;i++)cin>>now[i],l[i]=now[i]; sort(l+1,l+n+1); for(int i=1;i<=n;i++)now[i]=lower_bound(l+1,l+n+1,now[i])-l; for(lim=f();;lim++)dfs(1,0); return 0; }
|
双端队列 BFS(0-1BFS)
前文也提到了,正常 BFS 要保证队列内的元素严格有序。
稍微想想可以发现,用双端队列是能维护边权只有 0 和 1 的图的,具体实现如下:
- 走 0,将元素放到队首。(此时距离不变,因此正确)
- 走 1,将元素放到队尾。(根据 BFS 原理,此时队列内的元素最多差 1,那么若队首是最小的,放到队尾一定满足差 1 的条件。显然最开始队首是最小的。)
例题:P4554
考虑提前连好无向边,然后直接 01BFS 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
#include<bits/stdc++.h> using namespace std; int n,m; struct line{ int to,link,w; }E[500001]; int cnt,rec[501][501]; int head[250001],tot; void addE(int u,int v,int w){ E[++tot].link=head[u]; E[tot].to=v; E[tot].w=w; head[u]=tot; return; } int dis[250001]; deque<int> q; bool c[501][501]; bool vis[250001]; void BFS(int s,int t){ dis[s]=0; for(int i=1;i<=n*m;i++)vis[i]=0,dis[i]=1e9; q.push_front(s); dis[s]=0; while(!q.empty()){ int u=q.front();q.pop_front(); if(u==t)return cout<<dis[t]<<"\n",void(); if(vis[u])continue; vis[u]=1; for(int i=head[u];i;i=E[i].link){ if(!vis[E[i].to]&&dis[E[i].to]>dis[u]+E[i].w){ dis[E[i].to]=dis[u]+E[i].w; if(E[i].w==0)q.push_front(E[i].to); else q.push_back(E[i].to); } } } return; } int main(){ ios::sync_with_stdio(0); char g; while(cin>>n>>m){ if(n==0&&m==0)break; while(!q.empty())q.pop_front(); cnt=0; for(int i=1;i<=tot;i++)E[i]={0,0,0}; tot=0; for(int i=1;i<=n*m;i++)head[i]=0; for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){ cin>>g; rec[i][j]=++cnt; c[i][j]=(g=='#'); if(i>1)addE(rec[i][j],rec[i-1][j],c[i][j]!=c[i-1][j]),addE(rec[i-1][j],rec[i][j],c[i][j]!=c[i-1][j]); if(j>1)addE(rec[i][j],rec[i][j-1],c[i][j]!=c[i][j-1]),addE(rec[i][j-1],rec[i][j],c[i][j]!=c[i][j-1]); } int x_1,x_2,y_1,y_2; cin>>x_1>>y_1>>x_2>>y_2; x_1++,y_1++,x_2++,y_2++; BFS(rec[x_1][y_1],rec[x_2][y_2]); } return 0; }
|
这道题数据太水了,BFS 怎样写挂都能过。
例题:P4667
同样是预处理出图。
考虑转电线之后的两点连一条边花费为 1,转之前的边花费为 0。
这道题 BFS 我写挂了 +∞ 次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
#include<bits/stdc++.h> using namespace std; int n,m; struct line{ int to,link,w; }E[1000001]; int head[270001],tot; void addE(int u,int v,int w){ E[++tot].to=v; E[tot].link=head[u]; E[tot].w=w; head[u]=tot; return; } deque<int> q; int XYtoC(int x,int y){ return (x-1)*(m+1)+y; } int dis[270001]; int s,t; bool vis[270001]; void BFS(){ for(int i=1;i<=n*m+n+m+1;i++)dis[i]=1e9; q.push_front(s); dis[s]=0; while(!q.empty()){ int u=q.front();q.pop_front(); if(u==t){ cout<<dis[t]; exit(0); } if(vis[u])continue; vis[u]=1; for(int i=head[u];i;i=E[i].link){ if(!vis[E[i].to]&&dis[E[i].to]>dis[u]+E[i].w){ dis[E[i].to]=dis[u]+E[i].w; if(E[i].w==1)q.push_back(E[i].to); else q.push_front(E[i].to); } } } return; } int main(){ ios::sync_with_stdio(0); char c; cin>>n>>m; for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){ cin>>c; bool F=(c=='/'); addE(XYtoC(i+1,j),XYtoC(i,j+1),F^1); addE(XYtoC(i,j+1),XYtoC(i+1,j),F^1); addE(XYtoC(i,j),XYtoC(i+1,j+1),F); addE(XYtoC(i+1,j+1),XYtoC(i,j),F); } s=XYtoC(1,1),t=XYtoC(n+1,m+1); BFS(); cout<<"NO SOLUTION"; return 0; }
|
例题:CF173B
这道题是典型的不预处理连边的 01BFS。
直接维护一个队列,对于不改变方向的,花费不变,加入队首,其他几个方向,花费加一,加入队尾。
注意最后的判定条件是在 (1,1) 且方向为左。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
#include<bits/stdc++.h> using namespace std; int n,m; struct S{ int x,y,dis; int f; }; S getS(int _x,int _y,int _dis,int _f){ S s; s.x=_x; s.y=_y; s.dis=_dis; s.f=_f; return s; } deque<S> q; int ans=-1; char c[1001][1001]; bool vis[1001][1001][4]; int dx[4]={0,0,-1,1}; int dy[4]={1,-1,0,0}; void BFS(){ q.push_front(getS(n,m,0,1)); while(!q.empty()){ S u=q.front();q.pop_front(); if(u.x==1&&u.y==1&&u.f==1){ if(ans!=-1&&ans<u.dis)u.dis=ans; cout<<u.dis<<"\n"; exit(0); } if(u.x==1&&u.y==1&&c[u.x][u.y]=='#'){ ans=u.dis+1; continue; } if(vis[u.x][u.y][u.f])continue; vis[u.x][u.y][u.f]=1; for(int f=0;f<4;f++){ if((f==0&&u.y==m)||(f==1&&u.y==0)||(f==2&&u.x==0)||(f==3&&u.x==n))continue; if(c[u.x][u.y]=='#'&&u.f!=f)q.push_back(getS(u.x+dx[f],u.y+dy[f],u.dis+1,f)); if(u.f==f)q.push_front(getS(u.x+dx[f],u.y+dy[f],u.dis,f)); } } return; }
int main(){ ios::sync_with_stdio(0); cin>>n>>m; for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){ cin>>c[i][j]; } BFS(); cout<<ans; return 0; }
|
例题:CF1063B
可以发现 usel−r≤y−nowy,即往左边的越多,往右边的也越多。
那么可以只用 往左边多少次
作为 1 边,其他为 0 边,然后每次判断即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
#include<bits/stdc++.h> using namespace std; bool g[2001][2001]; char C; int n,m; int r,c,X,Y; struct po{ int x,y,l; }; deque<po> q; bool vis[2001][2001]; int cnt; void add(int x,int y){ vis[x][y]=1,cnt++; return; } int dx[4]={0,0,1,-1}; int dy[4]={-1,1,0,0}; void BFS(){ q.push_front({r,c,X}); while(!q.empty()){ po u=q.front();q.pop_front(); if(X-u.l>c-u.y+Y||u.l<0)continue; if(vis[u.x][u.y])continue; add(u.x,u.y); for(int i=0;i<4;i++){ int nowx=u.x+dx[i],nowy=u.y+dy[i]; if(nowx<1||nowx>n||nowy<1||nowy>m||!g[nowx][nowy])continue; if(i==0)q.push_back({nowx,nowy,u.l-1}); else q.push_front({nowx,nowy,u.l}); } } return; }
int main(){ ios::sync_with_stdio(0); cin>>n>>m>>r>>c>>X>>Y; for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){ cin>>C; if(C=='*')g[i][j]=0; else g[i][j]=1; } BFS(); cout<<cnt; return 0; }
|
例题:CF1601B
线段树优化建图,改废了。
注意要建虚点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
|
#include<bits/stdc++.h> using namespace std; int n,t; struct line{ int to,link,w; bool ty=0; }E[10000001]; int head[2400001],tot; void addE(int u,int v,int w,bool ty=0){ E[++tot].link=head[u]; E[tot].to=v; E[tot].w=w; E[tot].ty=ty; head[u]=tot; return; } struct po{ int id; }; deque<po> q; int B[300005],P[2400006]; void bulid(int now,int l,int r){ if(l==r){ B[l]=now; P[now]=l; return; } int mid=(l+r)/2; addE(now,now<<1,0); addE(now,now<<1|1,0); bulid(now<<1,l,mid),bulid(now<<1|1,mid+1,r); return; } void con(int now,int l,int r,int sl,int sr,int st){ if(l==sl&&r==sr){ addE(B[st]+4*n,now,1); return; } int mid=(l+r)/2; if(sl<=mid)con(now<<1,l,mid,sl,min(sr,mid),st); if(sr>mid)con(now<<1|1,mid+1,r,max(sl,mid+1),sr,st); return; } int dis[2400006],fa[2400006]; bool vis[2400006]; void BFS(){ q.push_front({B[1]+4*n}); for(int i=1;i<=8*n;i++)dis[i]=1e9; dis[B[1]+4*n]=0; while(!q.empty()){ int u=q.front().id;q.pop_front(); if(u==B[n+1]){ return; exit(0); } if(vis[u])continue; vis[u]=1; for(int i=head[u];i;i=E[i].link){ if(dis[E[i].to]>dis[u]+E[i].w){ dis[E[i].to]=dis[u]+E[i].w; fa[E[i].to]=u; if(E[i].w)q.push_back({E[i].to}); else q.push_front({E[i].to}); } } } return ; } vector<int> V; int a[300005],b[300005]; int main(){ ios::sync_with_stdio(0); cin>>n; bulid(1,1,n+1); for(int i=n;i>=1;i--){ cin>>a[i]; con(1,1,n+1,i+1,min(1+n,i+a[i]),i); } for(int i=n;i>=1;i--){ cin>>b[i]; addE(B[i],B[max(1,i-b[i])]+4*n,0,1); } BFS(); if(dis[B[n+1]]>=1e9)cout<<-1; else{ cout<<dis[B[n+1]]<<"\n"; int N=B[n+1]; while(N!=B[1]+4*n){ if(P[N])V.push_back(P[N]); N=fa[N]; } reverse(V.begin(),V.end()); for(int i=0;i<dis[B[n+1]];i++)cout<<n+1-V[i]<<" "; } return 0; }
|
完结撒花!
Dancing Links
鸽…咕咕咕…
练习题
鸽…咕咕咕…