搜索专题总结
第七章的例题做得差不多了,还有一道枚举二叉树和一道比较难的搜方块的没过,另外有一道火柴的用IDA*水过,并没有过大数据,由于这道可以用dancing links过,所以留着dancing links一坑。接下来总结下这章的收获,首先最重要的当然是不需要判重的高效率的IDA*以及估价函数的设计技巧;然后是bfs+hash写得更熟练了,如果hash需要erase那么就只能用指针版的,但是效率会很慢,否则就用数组版的。
做搜索题的几个要点:
1,估算最坏复杂度。
2,寻找合适的剪枝策略,估计剪枝的效果,根据情况选择IDA*还是bfs,最坏复杂度高但剪枝效果明显的优先IDA*。
3,想清楚代码的大致框架。
4,想清楚代码实现困难的细节以及可能出错需要注意的地方。
5,大胆快速的写吧。
下面是第七章的例题:
A题:
输出所有的xxxxx / xxxxx =N
只要枚举上面的数字就行了,复杂度<10^5,直接循环枚举。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); const double EPS=0.0000001; const double Pi=acos(-1.0); int n; int a,b; bool vis[maxn]; bool has[20]; bool check(int b) { MS0(has); REP(i,1,5){ int x=b%10;b/=10; if(vis[x]||has[x]) return 0; has[x]=1; } return 1; } int main() { //freopen("in.txt","r",stdin); int st=1; while(cin>>n,n){ if(!st) puts(""); st=0; MS0(vis); a=0; bool flag=0; REP(i,0,9){ vis[i]=1; a=a*10+i; REP(j,0,9){ if(vis[j]) continue; vis[j]=1; a=a*10+j; REP(k,0,9){ if(vis[k]) continue; vis[k]=1; a=a*10+k; REP(l,0,9){ if(vis[l]) continue; vis[l]=1; a=a*10+l; REP(x,0,9){ if(vis[x]) continue; vis[x]=1; a=a*10+x; if(a%n==0&&check(a/n)){ flag=1; printf("%05d / %05d = %d\n",a,a/n,n); } vis[x]=0; a-=x;a/=10; } vis[l]=0; a-=l;a/=10; } vis[k]=0; a-=k;a/=10; } vis[j]=0; a-=j;a/=10; } vis[i]=0; a-=i;a/=10; } if(!flag) printf("There are no solutions for %d.\n",n); } return 0; }
B题:
水题,暴力。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); const double EPS=0.0000001; const double Pi=acos(-1.0); int n; ll a[maxn]; ll cal(int l,int r) { ll res=1; REP(i,l,r) res*=a[i]; return res; } int main() { int casen=1; while(cin>>n){ REP(i,1,n) scanf("%lld",&a[i]); ll ans=0; REP(l,1,n){ REP(r,l,n){ ll tmp=cal(l,r); ans=max(ans,tmp); } } printf("Case #%d: The maximum product is %lld.\n\n",casen++,ans); } return 0; }
C题:
水题,先确定下范围再枚举。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); const double EPS=0.0000001; const double Pi=acos(-1.0); ll k,x,y; struct St { ll x,y; }; vector<St> ans; int main() { while(cin>>k){ ans.clear(); for(y=k+1;;y++){ x=k*y/(y-k); if(x<y) break; if((k*y)%(y-k)==0){ x=k*y/(y-k); if(x<y) break; ans.push_back({x,y}); } } printf("%d\n",(int)ans.size()); for(int i=0;i<ans.size();i++){ printf("1/%lld = 1/%lld + 1/%lld\n",k,ans[i].x,ans[i].y); } } return 0; }
D题:
水题,dfs就行了。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); const double EPS=0.0000001; const double Pi=acos(-1.0); int n,a[maxn]; bool vis[maxn],isprime[maxn]; void getPrime() { memset(isprime,1,sizeof(isprime)); isprime[1]=0; REP(i,2,maxn-1){ if(!isprime[i]) continue; for(int j=i+i;j<maxn;j+=i) isprime[j]=0; } } void dfs(int cur) { if(cur>n){ if(!isprime[a[n]+a[1]]) return; REP(i,1,n) printf("%d%c",a[i],i==n?‘\n‘:‘ ‘); return; } REP(i,1,n){ if(!vis[i]&&isprime[a[cur-1]+i]){ a[cur]=i; vis[i]=1; dfs(cur+1); vis[i]=0; } } } int main() { int casen=1; getPrime(); int st=1; while(cin>>n){ MS0(vis); if(!st) puts(""); st=0; printf("Case %d:\n",casen++); a[1]=1; vis[1]=1; dfs(2); } return 0; }
E题:困难的串。
很经典的题,只要不断往后添加然后判断就行了,关键在于判断的复杂度,由于substr(0,l-1)是已经判断过的,所以只要判断后缀就行了。
/// 19:43 2015/11/16 #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); int n,L; int cur; bool flag; string ans; bool judge(string s) { int len=s.size(); for(int i=1;i<=len/2;i++){ //out<<s.substr(len-2*i,i)<<"_"<<s.substr(len-i,i)<<endl; if(s.substr(len-2*i,i)==s.substr(len-i,i)) return false; } return true; } void dfs(string s) { if(flag) return; if(cur==n){ ans=s;flag=1;return; } cur++; REP(i,0,L-1){ string t=s+char(‘A‘+i); if(judge(t)) dfs(t); } } int main() { //freopen("in.txt","r",stdin); while(cin>>n>>L,(n||L)){ cur=0;flag=0; dfs(""); REP(i,0,(int)ans.size()-1){ printf("%c",ans[i]); if(i%64==63&&i!=(int)ans.size()-1) printf("\n"); else if(i%4==3&&i!=(int)ans.size()-1) printf(" "); } printf("\n%d\n",(int)ans.size()); } return 0; }
F题:
枚举排列就行了,复杂度能过,注意输入的处理。
/// 20:34~ 2015/11/16 #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); char s[maxn]; vector<int> G[maxn]; int a[maxn],pos[maxn]; int b[maxn],ans; int id[maxn],fid[maxn]; int n; bool vis[maxn]; void build() { REP(i,1,8) G[i].clear(); memset(id,-1,sizeof(id)); n=0; int len=strlen(s); for(int i=0;i<len;){ int u=id[s[i]]; if(u==-1) u=id[s[i]]=++n,fid[n]=s[i]; int j; for(j=i+2;j<len;j++){ if(s[j]==‘;‘) break; int v=id[s[j]]; if(v==-1) v=id[s[j]]=++n,fid[n]=s[j]; G[u].push_back(v); G[v].push_back(u); } i=j+1; } } void update() { REP(i,1,n) pos[a[i]]=i; int res=0; REP(u,1,n){ for(int i=0;i<G[u].size();i++){ int v=G[u][i]; res=max(res,abs(pos[u]-pos[v])); } } if(res<ans){ REP(i,1,n) b[i]=a[i]; ans=res; } else if(res==ans){ bool flag=0; REP(i,1,n){ if(fid[a[i]]<fid[b[i]]){ flag=1;break; } else if(fid[a[i]]>fid[b[i]]) break; } if(flag){ REP(i,1,n) b[i]=a[i]; } } } void dfs(int cur,int now) { a[cur]=now; vis[now]=1; if(cur==n) update(); REP(i,1,n) if(!vis[i]) dfs(cur+1,i); vis[now]=0; } void solve() { ans=INF; REP(i,1,n) dfs(1,i); REP(i,1,n) printf("%c ",fid[b[i]]); printf("-> %d\n",ans); } int main() { //freopen("in.txt","r",stdin); while(~scanf("%s",s)){ if(strcmp(s,"#")==0) break; build(); solve(); } return 0; }
H题:
倒水。bfs都可以,注意由于求的是倒水量最小而不是次数最少,所以bfs要用优先队列,IDA*似乎不太适合,因为倒水量的范围比较大。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=8000100; const int INF=(1<<29); int s[3],d; int ans,d_; bool vis[maxn]; struct Node { int s[3]; int step; friend bool operator<(Node A,Node B) { return A.step>B.step; } void debug() { printf("x=%2d y=%2d z=%2d step=%2d\n",s[0],s[1],s[2],step); } }; int st(Node u) { int res=0; REP(i,0,2) res=res*201+u.s[i]; return res; } Node pop(Node u,int x,int y) { if(u.s[x]<=s[y]-u.s[y]){ u.step+=u.s[x]; u.s[y]+=u.s[x]; u.s[x]=0; return u; } else{ u.step+=s[y]-u.s[y]; u.s[x]-=s[y]-u.s[y]; u.s[y]=s[y]; return u; } } void bfs() { MS0(vis); priority_queue<Node> q; Node ss={0,0,s[2],0}; q.push(ss); vis[st(ss)]=1; ans=0;d_=0; while(!q.empty()){ Node u=q.top();q.pop(); ///u.debug(); REP(i,0,2){ if(u.s[i]>d_&&u.s[i]<=d){ d_=u.s[i];ans=u.step; } } if(d_==d) break; Node v; /// 1->2 v=pop(u,0,1); if(!vis[st(v)]) q.push(v),vis[st(v)]=1; /// 1->3 v=pop(u,0,2); if(!vis[st(v)]) q.push(v),vis[st(v)]=1; /// 2->1 v=pop(u,1,0); if(!vis[st(v)]) q.push(v),vis[st(v)]=1; /// 2->3 v=pop(u,1,2); if(!vis[st(v)]) q.push(v),vis[st(v)]=1; /// 3->1 v=pop(u,2,0);//cout<<"01"<<" ";v.debug(); if(!vis[st(v)]) q.push(v),vis[st(v)]=1; /// 3->2 v=pop(u,2,1); if(!vis[st(v)]) q.push(v),vis[st(v)]=1; } } int main() { //freopen("in.txt","r",stdin); int T;cin>>T; while(T--){ scanf("%d%d%d%d",&s[0],&s[1],&s[2],&d); MS0(vis); bfs(); printf("%d %d\n",ans,d_); } return 0; }
I题:
很经典的状态空间搜索问题,由于不可走的区域很多,所以重新建图以减小复杂度,这里判重很容易,直接bfs,当然IDA*也可以。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); int w,h,m; char ch[20][20]; vector<int> G[maxn];int n; struct Node { char c; int x; friend bool operator<(Node A,Node B) { return A.c<B.c; } }; Node S[maxn],T[maxn]; int id[20][20]; int dx[]={-1,1,0,0}; int dy[]={0,0,-1,1}; bool vis[300][300][300]; struct qNode { int x,y,z; int d; }; bool jud(int x,int y,int z,int nx,int ny,int nz) { if(m==1) return 1; if(m==2){ if(nx==ny) return 0; if(x==ny&&y==nx) return 0; return 1; } if(m==3){ if(nx==ny||ny==nz||nz==nx) return 0; if(x==ny&&y==nx) return 0; if(y==nz&&z==ny) return 0; if(z==nx&&x==nz) return 0; return 1; } } int bfs() { MS0(vis); queue<qNode> q; qNode s={S[1].x,S[2].x,S[3].x,0}; qNode t={T[1].x,T[2].x,T[3].x,0}; q.push(s); vis[s.x][s.y][s.z]=1; while(!q.empty()){ qNode u=q.front();q.pop(); int x=u.x,y=u.y,z=u.z,d=u.d; if(x==t.x&&y==t.y&&z==t.z) return d; for(int i=0;i<G[x].size();i++){ int nx=G[x][i]; for(int j=0;j<G[y].size();j++){ int ny=G[y][j]; for(int k=0;k<G[z].size();k++){ int nz=G[z][k]; if(!vis[nx][ny][nz]&&jud(x,y,z,nx,ny,nz)){ q.push({nx,ny,nz,d+1}); vis[nx][ny][nz]=1; } } } } } return -1; } int main() { //freopen("in.txt","r",stdin); while(cin>>w>>h>>m){ if(w==0&&h==0&&m==0) break; gets(ch[0]); REP(i,1,h) gets(ch[i]+1); n=0; MS0(id); int ks=0,kt=0; MS0(S);MS0(T); REP(i,1,h){ REP(j,1,w){ if(ch[i][j]!=‘#‘){ id[i][j]=++n; if(islower(ch[i][j])) S[++ks]={ch[i][j],n}; if(isupper(ch[i][j])) T[++kt]={ch[i][j],n}; } } } REP(i,0,maxn-1) G[i].clear(); G[0].push_back(0); REP(i,1,h){ REP(j,1,w){ int u=id[i][j]; if(!u) continue; G[u].push_back(u); REP(k,0,3){ int ni=i+dx[k],nj=j+dy[k]; int v=id[ni][nj]; if(!v) continue; G[u].push_back(v); } } } sort(S+1,S+m+1);sort(T+1,T+m+1); printf("%d\n",bfs()); } return 0; }
J题:剪纸问题。
又是一道很经典的状态空间搜索问题。这里以每个元素的相对位置来设计的估价函数剪枝效果明显,所以用IDA*,效率非常高。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) #define PB push_back using namespace std; typedef long long ll; const int maxn=2000100; const int INF=(1<<29); int n; struct Node { int a[11]; void read() { REP(i,1,n) scanf("%d",&a[i]); } };Node s; bool judge(Node s) { REP(i,1,n){ if(s.a[i]!=i) return 0; } return 1; } Node cut(Node s,int l,int r,int L,int R) { Node res=s; REP(i,0,R-L) res.a[l+i]=s.a[L+i]; int lt=l+R-L+1; REP(i,0,r-l) res.a[lt+i]=s.a[l+i]; return res; } int Not(Node s) { int res=0; REP(i,1,n-1) res+=(s.a[i]+1!=s.a[i+1]); return res; } int dfs(Node s,int cur,int maxd) { if(cur==maxd){ if(judge(s)) return 1; return 0; } if(cur*3+Not(s)>maxd*3) return 0; int res=0; REP(l,1,n-1){ REP(r,l,n-1){ REP(R,r+1,n){ Node ns=cut(s,l,r,r+1,R); res|=dfs(ns,cur+1,maxd); if(res) return 1; } } } return res; } int main() { //freopen("in.txt","r",stdin); int casen=1; while(cin>>n,n){ s.read(); int ans=INF; REP(i,0,n){ if(dfs(s,0,i)){ ans=i;break; } } printf("Case %d: %d\n",casen++,ans); } return 0; }
K题:
又是一道经典的状态空间搜索问题。这里如果用bfs的话要分三次,而IDA*依旧优势明显,效率高,代码短,注意每一步只能改变一个,所以估价函数为cur+不一样的个数>maxd,这里不一样的个数取最大就可以了,就不用分三次了。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); struct State { int a[24]; };State s; string str; int ans,x; int A[]={0,2,6,11,15,20,22}; int B[]={1,3,8,12,17,21,23}; int C[]={4,5,6,7,8,9,10}; int D[]={13,14,15,16,17,18,19}; int Y[]={6,7,8,11,12,15,16,17}; /* 0 1 2 3 4 5 6 7 8 9 10 11 12 3 4 5 6 7 8 19 20 21 22 23 */ void debug(State s) { printf(" ");printf("%2d %2d\n",s.a[0],s.a[1]); printf(" ");printf("%2d %2d\n",s.a[2],s.a[3]); REP(i,4,10) printf("%2d ",s.a[i]);printf("\n"); printf(" ");printf("%2d %2d\n",s.a[11],s.a[12]); REP(i,13,19) printf("%2d ",s.a[i]);printf("\n"); printf(" ");printf("%2d %2d\n",s.a[20],s.a[21]); printf(" ");printf("%2d %2d\n",s.a[22],s.a[23]); cout<<endl; } State mov(State s,int *A,bool tag) { if(tag){ int tmp=s.a[A[0]]; REP(i,1,6) s.a[A[i-1]]=s.a[A[i]]; s.a[A[6]]=tmp; return s; } else{ int tmp=s.a[A[6]]; for(int i=6;i>=1;i--) s.a[A[i]]=s.a[A[i-1]]; s.a[A[0]]=tmp; return s; } } State go(State s,char ch) { State ns=s; if(ch==‘A‘) ns=mov(s,A,1); if(ch==‘B‘) ns=mov(s,B,1); if(ch==‘C‘) ns=mov(s,C,0); if(ch==‘D‘) ns=mov(s,D,0); if(ch==‘E‘) ns=mov(s,B,0); if(ch==‘F‘) ns=mov(s,A,0); if(ch==‘G‘) ns=mov(s,D,1); if(ch==‘H‘) ns=mov(s,C,1); return ns; } int judge(State s) { REP(i,1,7) if(s.a[Y[i]]!=s.a[Y[0]]) return 0; return s.a[Y[0]]; } int Not(State s) { int cnt[4]={0}; REP(i,0,7){ cnt[s.a[Y[i]]]++; } return max(cnt[1],max(cnt[2],cnt[3])); } bool dfs(State s,int cur,int maxd,string road) { if(cur==maxd){ int xx=judge(s); if(xx){ str=road; x=xx; //debug(s); return 1; } return 0; } if(cur+8-Not(s)>maxd) return 0; for(char ch=‘A‘;ch<=‘H‘;ch++){ State ns=go(s,ch); if(dfs(ns,cur+1,maxd,road+ch)) return 1; } return 0; } int main() { //freopen("in.txt","r",stdin); int st; while(cin>>st){ if(st==0) return 0; s.a[0]=st; REP(i,1,23) scanf("%d",&s.a[i]); //debug(s);debug(mov(s,C,0)); for(int i=0;;i++){ if(dfs(s,0,i,"")){ ans=i;break; } } if(ans==0) puts("No moves needed"); else cout<<str<<endl; printf("%d\n",x); } }
L题:
这应该是很经典的贪心。。。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); ll N,s1,v1,s2,v2; ll solve1(ll N,ll s1,ll v1,ll s2,ll v2) { ll res=0; REP(i,0,N/s1){ ll tmp=i*v1+((N-i*s1)/s2)*v2; res=max(tmp,res); } return res; } ll solve2(ll N,ll s1,ll v1,ll s2,ll v2) { // a*s1=b*s2->v1>v2 v1/s1>v2/s2 -> v1*s2>v2*s1 if(v1*s2<v2*s1){ swap(s1,s2);swap(v1,v2); } ll d=__gcd(s1,s2); ll a=s2/d,b=s1/d; ll res=0; REP(i,0,b-1){ ll tmp=i*v2+((N-i*s2)/s1)*v1; res=max(res,tmp); } return res; } int main() { //freopen("in.txt","r",stdin); int T;cin>>T; int casen=1; while(T--){ scanf("%lld%lld%lld%lld%lld",&N,&s1,&v1,&s2,&v2); ll ans=0; if(N/s1<maxn) ans=solve1(N,s1,v1,s2,v2); else if(N/s2<maxn) ans=solve1(N,s2,v2,s1,v1); else ans=solve2(N,s1,v1,s2,v2); printf("Case #%d: %lld\n",casen++,ans); } return 0; }
O题:
这题大数据没过却莫名其妙地AC了。。。用的是IDA*,看来如果拼人品的话就IDA*吧。。。由于这题的另一种解法是dancing links,有时间学dancing links的时候一定回来看这题。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=(1<<29); int n,k; struct State { bool has[61]; int cnt; int c[61]; int sum_c,max_c; int Cnt() { int res=0; REP(d,1,n){ REP(r,0,n-d){ REP(c,1,n-d+1){ int x=1; int st=r*(2*n+1)+c; REP(j,0,d-1) x&=(has[st+j]&has[st+n+j*(2*n+1)]&has[st+n+d+j*(2*n+1)]&has[st+d*(2*n+1)+j]); res+=x; } } } return res; } void cal_c() { MS0(c);max_c=0;sum_c=0; REP(i,1,2*n*(n+1)){ if(has[i]){ has[i]=0; int tmp=cnt; cnt=Cnt(); c[i]=tmp-cnt; max_c=max(c[i],max_c); sum_c+=c[i]; has[i]=1; cnt=tmp; } } } };State s; bool dfs(State s,int cur,int maxd) { s.cal_c(); if(cur==maxd) return s.cnt==0; if(cur+s.cnt-s.sum_c>maxd) return 0; REP(i,1,2*n*(n+1)){ if(s.has[i]){ if(s.c[i]==s.max_c){ State ns=s; ns.has[i]=0; ns.cnt=s.cnt-s.c[i]; if(dfs(ns,cur+1,maxd)) return 1; } } } return 0; } int main() { //freopen("in.txt","r",stdin); int T;cin>>T; while(T--){ scanf("%d%d",&n,&k); REP(i,1,2*n*(n+1)) s.has[i]=1; REP(i,1,k){ int x;scanf("%d",&x); s.has[x]=0; } s.cnt=s.Cnt(); s.cal_c(); int ans=INF; REP(i,1,60){ if(dfs(s,0,i)){ ans=i;break; } } printf("%d\n",ans); } return 0; }