shzr和搜索的故事:
沉迷于搜索无法自拔。。。
记得一开始学搜索的时候,不知道是听了什么谣言,以为搜索只是用来骗分的,不是正经算法,于是正直的我决定不学搜索,似乎noip2017前我的搜索水平止于输出全排列。。。NOIP 2017 PJ T3 ---- 一道搜索可A的题目。。。写了很长很长的dp,只得了10分。当时好naive啊,一个是搜索并不只是用来骗分的,另一个是,骗分又怎么样?难道所有题都写正解吗...后来练了一段时间的搜索,当时的状态就是被young_全方位吊打,尤其是搜索,好多搜索题都是她帮我重构的。最近又在练搜索,不求达到多么高的水平,别被吊打就行(好像立了一个不可能实现的flag)。不多说啦,下面是一些简单的搜索。
搜索---1
不需要很多思维和剪枝的搜索,高端搜索请见“搜索-2”。
BFS的技巧:
如果边权都相等,可以用于求最短路,$O(N)$,比其他的最短路都快。
0-1bfs如果搜到边权为0的边,就把新的点插入到队首,如果是边权不为0的边,就把新的点插入到队尾,如果终点出现在队首才能说明找到了最优解。这种思路的运用是非常重要的,下面来看道题。
开关灯:https://www.luogu.org/problemnew/show/P2845
题意概述:在一个网格图里走,只能走亮的格子,一开始只有起点是亮的,走到新的格子就可以打开这个格子所控制的所有灯,求一共能打开多少灯。
如果考虑朴素的BFS会发现不行,因为有的格子走完之后又开了新的灯,此时走回去是可以走到新格子的,但是我们已经把这些可能性排除掉了。这样还有一个方法:BFS 1000 次。然而不仅会超时还不能保证对。这时候我们就想到了刚刚学到的技巧。把可以走到但是因为没开灯所以没走的格子记录下来,如果某一次把这个格子的灯打开了,就把这个格子作为新的BFS起点。这样是对的吗?其实还是不对,因为这里的路早就该走,不应该再排很久的队列道路了,所以把这样的节点插入到BFS的队首,其他扩展出来的节点插入到BFS的队尾,进行BFS。
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # define xx (x+dx[i]) 6 # define yy (y+dy[i]) 7 8 using namespace std; 9 const int dx[]={-1,0,0,1}; 10 const int dy[]={0,1,-1,0}; 11 int tim,cnt,ans,h,n,m,x_1,y_1,x_2,y_2,firs[20009]; 12 deque <int> q; 13 struct nod 14 { 15 int nex,too; 16 }g[500009]; 17 bool ope[100][100],can_vis[100][100],vis[100][100]; 18 19 void add (int x,int y) 20 { 21 g[++h].too=y; 22 g[h].nex=firs[x]; 23 firs[x]=h; 24 } 25 26 void bfs () 27 { 28 int x,y,beg; 29 int j; 30 q.push_back(n+2); 31 while (q.size()) 32 { 33 beg=q.front(); 34 q.pop_front(); 35 x=beg/(n+1); 36 y=beg%(n+1); 37 if(vis[x][y]) continue; 38 vis[x][y]=true; 39 for (int i=firs[beg];i;i=g[i].nex) 40 { 41 j=g[i].too; 42 ope[j/(n+1)][j%(n+1)]=true; 43 if (can_vis[j/(n+1)][j%(n+1)]&&vis[j/(n+1)][j%(n+1)]==false) 44 q.push_front((j/(n+1))*(n+1)+j%(n+1)); 45 } 46 for (int i=0;i<4;++i) 47 { 48 if(xx<1||xx>n||yy<1||yy>n) continue; 49 can_vis[xx][yy]=true; 50 if(!ope[xx][yy]) continue; 51 q.push_back(xx*(n+1)+yy); 52 } 53 } 54 } 55 56 int main() 57 { 58 scanf("%d%d",&n,&m); 59 for (int i=1;i<=m;++i) 60 { 61 scanf("%d%d%d%d",&x_1,&y_1,&x_2,&y_2); 62 add(x_1*(n+1)+y_1,x_2*(n+1)+y_2); 63 } 64 ope[1][1]=true; 65 bfs(); 66 for (int i=1;i<=n;++i) 67 for (int j=1;j<=n;++j) 68 if(ope[i][j]) ans++; 69 printf("%d",ans); 70 return 0; 71 }
排列组合类:
组合的输出:https://www.luogu.org/problemnew/show/P1157
题意概述:输出n个数的组合。
与排列不同,不考虑顺序,所以只好人为的去钦定一些顺序,比如每一个都比上一个大。
# include <cstdio> # include <iostream> # define R register using namespace std; int n,r; bool vis[25]={false}; int ans[30]={0}; void write() { for (R int i=1;i<=r;i++) printf("%3d",ans[i]); printf(" "); } void dfs(int x) { if(x==r+1) write(); else { for (R int i=1;i<=n;i++) if(!vis[i]&&i>ans[x-1]) { ans[x]=i; vis[i]=true; dfs(x+1); ans[x]=0; vis[i]=false; } } } int main() { scanf("%d%d",&n,&r); dfs(1); return 0; }
有重复元素的排列问题:https://www.luogu.org/problemnew/show/P1691
题意概述:输出n个字母的全排列,相同的字母不进行区分。
思想很巧妙,直接看代码吧。
# include <cstdio> # include <iostream> using namespace std; int n,S; int vis[30]; char c,ans[501]; void write() { for (int i=1;i<=n;i++) printf("%c",ans[i]); printf(" "); S++; } void dfs(int x) { if(x==n+1) { write(); return ; } for (int i=0;i<26;i++) if(vis[i]) { ans[x]=i+'a'; vis[i]--; dfs(x+1); vis[i]++; } } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) { c=getchar(); while (c<'a'||c>'z') c=getchar(); vis[c-'a']++; } dfs(1); printf("%d",S); return 0; }
全排列问题:https://www.luogu.org/problemnew/show/P1706
题意概述:输出1-n的全排列。
。。。只是为了凑个整。
#include <cstdio> #include <iostream> using namespace std; int n; int a[10]={0},vi[10]={0}; void write() { for (int i=1;i<=n;i++) printf("%5d",a[i]); printf(" "); } void search(int i) { if (i!=n+1) { for (int j=1;j<=n;j++) if (vi[j]==0) { a[i]=j; vi[j]=1; search(i+1); vi[j]=0; } } else write(); } int main() { scanf("%d",&n); search(1); return 0; }
填数类:
八皇后:https://www.luogu.org/problemnew/show/P1219
题意概述:非常经典的题目,就不说了。
#include <cstdio> #include <iostream> #include <cmath> #include <cstring> #include <algorithm> using namespace std; int n,sum=0; int heng[15]={0},zxtys[30]={0},zstyx[30]={0},v[15]={0}; void write() { printf("%d",heng[1]); for (int i=2;i<=n;i++) printf(" %d",heng[i]); printf(" "); } void search(int x) { if (x!=n+1) for (int i=1;i<=n;i++) if (v[i]==0&&zxtys[i+x]==0&&(zstyx[i-x+15])==0) { heng[x]=i; v[i]=1; zxtys[i+x]=zstyx[i-x+15]=1; search(x+1); heng[x]=0; v[i]=0; zxtys[i+x]=zstyx[i-x+15]=0; } else ; else { if (sum<=2) write(); sum++; } } int main() { scanf("%d",&n); search(1); printf("%d",sum); return 0; }
数独:https://www.luogu.org/problemnew/show/P1784
题意概述:。。。填数独。
读入后保存每个0位的位置,搜索即可,因为题目保证了不会无解或多解,所以搜到一个解就直接输出,退出。
# include <cstdio> # include <iostream> # define R register int using namespace std; int a[10][10],q; int ans=0; int h=0,x[100],y[100]; int r[10],c[10],s[3][3]; void write() { for (R i=1;i<=9;i++) { for (R j=1;j<=9;j++) printf("%d ",a[i][j]); printf(" "); } return ; } void dfs(int ra) { if(ra==0) { write(); } else { for (R i=1;i<=9;i++) { if(r[ x[ra] ]&(1<<i)) continue; if(c[ y[ra] ]&(1<<i)) continue; if(s[ (x[ra]-1)/3 ][ (y[ra]-1)/3 ]&(1<<i)) continue; r[ x[ra] ]+=(1<<i); c[ y[ra] ]+=(1<<i); s[ (x[ra]-1)/3 ][ (y[ra]-1)/3 ]+=(1<<i); a[x[ra]][y[ra]]=i; dfs(ra-1); r[ x[ra] ]-=(1<<i); c[ y[ra] ]-=(1<<i); s[ (x[ra]-1)/3 ][ (y[ra]-1)/3 ]-=(1<<i); a[x[ra]][y[ra]]=0; } } } int main() { for (R i=1;i<=9;i++) for (R j=1;j<=9;j++) { scanf("%d",&q); a[i][j]=q; if(q==0) x[++h]=i,y[h]=j; else { r[i]|=(1<<q); c[j]|=(1<<q); s[(i-1)/3][(j-1)/3]|=(1<<q); } } dfs(h); return 0; }
靶形数独:https://www.luogu.org/problemnew/show/P1074
题意概述:填数独且每个位置有一个权值,数独的得分为权值*填的数,最大化得分。
纯搜索就没什么好说的了,主要看看剪枝:1.如果剩下位置全填9且全赋9的权值还是比最优解小,退出。(这个剪枝可以写的很强,比如每个位置按照真实权值来算,同一个行不能都按9来做等,但是这样写很麻烦,消耗的时间也不一定划算,所以就弱弱的只加了一句(h-ra+1)*90+Sum<ans,效果也的确不怎么样。对于最难的数据,加不加这句剪枝效率只差了100-200ms。看到这你一定想,交上去看看吧!于是就交了上去,竟然得了80分。采取了一贯很有效的优化:顺着搜超时那就倒着搜。。。神奇的是得了95分。。。
卡时!过了。。。
这就很神奇了,然而卡时毕竟不是很光明正大的方法,想一想怎么优化。想一下自己玩数独的时候是怎么玩的,是不是如果某一行只有一两个数没填就先去填它们,所以预处理出每一行还没填的格子数,排序后重构搜索顺序,快到飞起。
事实上也应该处理每一列,每一宫的格子数,动态更新之后选择填哪一个,之前还听说过从中心往边缘搜这样的优化,不过有的优化其实是有负作用的。。。
// luogu-judger-enable-o2 # include <cstdio> # include <iostream> # define R register int using namespace std; int a[10][10],q; int ans=0; int h=0,x[100],y[100]; int f[10][10]; int r[10],c[10],s[3][3]; void dfs(int ra,int Sum) { if(ra==h+1) ans=max(ans,Sum); else for (R i=1;i<=9;i++) { if(r[ x[ra] ]&(1<<i)) continue; if(c[ y[ra] ]&(1<<i)) continue; if(s[ (x[ra]-1)/3 ][ (y[ra]-1)/3 ]&(1<<i)) continue; if((h-ra+1)*100+Sum<ans) continue; r[ x[ra] ]+=(1<<i); c[ y[ra] ]+=(1<<i); s[ (x[ra]-1)/3 ][ (y[ra]-1)/3 ]+=(1<<i); a[x[ra]][y[ra]]=i; dfs(ra+1,Sum+f[ x[ra] ][ y[ra] ]*i); r[ x[ra] ]-=(1<<i); c[ y[ra] ]-=(1<<i); s[ (x[ra]-1)/3 ][ (y[ra]-1)/3 ]-=(1<<i); a[x[ra]][y[ra]]=0; } } int main() { int ss[10]={0},su=0; for (R i=1;i<=9;i++) for (R j=1;j<=9;j++) { if(i==1||i==9||j==1||j==9) f[i][j]=6; if(((i==2||i==8)&&(j>=2&&j<=8))||((i>=2&&i<=8)&&(j==2||j==8))) f[i][j]=7; if(((i==3||i==7)&&(j>=3&&j<=7))||((i>=3&&i<=7)&&(j==3||j==7))) f[i][j]=8; if(((i==4||i==6)&&(j>=4&&j<=6))||((i>=4&&i<=6)&&(j==4||j==6))) f[i][j]=9; if(i==5&&j==5) f[i][j]=10; } for (R i=1;i<=9;i++) for (R j=1;j<=9;j++) { scanf("%d",&q); a[i][j]=q; if(q!=0) { if(r[i]&(1<<q)) { printf("-1"); return 0; } if(c[j]&(1<<q)) { printf("-1"); return 0; } if(s[(i-1)/3][(j-1)/3]&(1<<q)) { printf("-1"); return 0; } r[i]|=(1<<q); c[j]|=(1<<q); s[(i-1)/3][(j-1)/3]|=(1<<q); su+=f[i][j]*q; } } for (int i=1;i<=9;i++) for (int j=1;j<=9;j++) if(a[i][j]==0) ss[i]++; int mx=0,my=10000; for (int i=1;i<=9;i++) { for (int j=1;j<=9;j++) if(ss[j]<my) { my=ss[j]; mx=j; } ss[mx]=100000000; my=10000000; for (int z=1;z<=9;z++) if(a[mx][z]==0) x[++h]=mx,y[h]=z; } dfs(1,su); if(ans==0) printf("-1"); else printf("%d",ans); return 0; }
八数码难题:https://www.luogu.org/problemnew/show/P1379
题意概述:九个格子中有8个数字,一个空格,每次将空格向上下左右移动,求达到目标状态的最小步数。
这里写的是简单版,跑的挺慢的,后来又写了双向bfs,扔到“搜索-2”里了。
没有用康拓展开,而是暴力展成链以后扔进set里面。
# include <iostream> # include <set> # include <queue> # include <cstdio> using namespace std; const int dx[]={-1,0,0,1}; const int dy[]={0,1,-1,0}; int x,dream=123804765; int ans=-1; int q[10000000]={0}; int ql[10000000]={0}; set<int> s; int A() { int head=0,tail=1; while (1) { int il=0,jl=0,n[3][3],now; now=q[++head]; if(now==dream) return ql[head]; for (register int i=2;i>=0;i--) for (register int j=2;j>=0;j--) { if(now%10==0) il=i,jl=j; n[i][j]=now%10; now=now/10; } for (register int i=0;i<4;i++) { int xx=il+dx[i]; int yy=jl+dy[i]; if(xx>=0&&xx<=2&&yy>=0&&yy<=2) { int nn[3][3]; for (register int ii=0;ii<3;ii++) for (register int jj=0;jj<3;jj++) nn[ii][jj]=n[ii][jj]; swap(nn[il][jl],nn[xx][yy]); now=nn[0][0]*100000000+nn[0][1]*10000000+nn[0][2]*1000000+nn[1][0]*100000+nn[1][1]*10000+nn[1][2]*1000+nn[2][0]*100+nn[2][1]*10+nn[2][2]; if(s.find(now)!=s.end()) { continue; } else { s.insert(now); q[++tail]=now; ql[tail]=ql[head]+1; } } } } } int main() { scanf("%d",&x); q[1]=x; ql[1]=0; s.insert(x); ans=A(); printf("%d",ans); return 0; }
是的。。。什么优化也没有,用了set还没开O2,就这样都能过。。。
聪明的打字员:https://www.luogu.org/problemnew/show/P1949
题意概述:不想概述$qwq$.
可以考虑双向广搜,不过记忆化一下就足够了.
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # define R register int 5 6 using namespace std; 7 8 int y,m,step,n,a[10],po,neww,Min=9,Max; 9 bool vis[10000006]; 10 queue <int> q,b; 11 12 int bfs () 13 { 14 int bef; 15 q.push(y * 10 + 1); 16 b.push(0); 17 vis[y*10+1]=true; 18 while (q.size()) 19 { 20 n = q.front(); 21 step = b.front(); 22 q.pop(); 23 b.pop(); 24 po = n % 10; 25 n /= 10; 26 if(n==m) return step; 27 28 bef=n; 29 for (R i = 6; i >= 1; --i) 30 a[i] = n % 10, n /= 10; 31 32 swap(a[1], a[po]); 33 neww = 0; 34 for (R i = 1; i <= 6; ++i) 35 neww = neww * 10 + a[i]; 36 if (!vis[neww * 10 + po]) 37 vis[neww * 10 + po] = true, q.push(neww * 10 + po), b.push(step + 1); 38 swap(a[1], a[po]); 39 40 swap(a[6], a[po]); 41 neww = 0; 42 for (R i = 1; i <= 6; ++i) 43 neww = neww * 10 + a[i]; 44 if (!vis[neww * 10 + po]) 45 vis[neww * 10 + po] = true, q.push(neww * 10 + po), b.push(step + 1); 46 swap(a[6], a[po]); 47 48 if(po!=6) 49 { 50 po++; 51 neww = bef; 52 if (po <= 6 && vis[neww * 10 + po] == false) 53 vis[neww * 10 + po] = true, q.push(neww * 10 + po), b.push(step + 1); 54 po--; 55 } 56 57 if(po!=1) 58 { 59 po--; 60 neww = bef; 61 if (po >= 1 && vis[neww * 10 + po] == false) 62 vis[neww*10 + po] = true, q.push(neww * 10 + po), b.push(step + 1); 63 po++; 64 } 65 66 if(a[po]+1<=Max) 67 { 68 a[po]++; 69 neww = 0; 70 for (R i = 1; i <= 6; ++i) 71 neww = neww * 10 + a[i]; 72 if (!vis[neww * 10 + po]) 73 vis[neww * 10 + po] = true, q.push(neww * 10 + po), b.push(step + 1); 74 a[po]--; 75 } 76 77 if(a[po]-1>=Min) 78 { 79 a[po]--; 80 neww = 0; 81 for (R i = 1; i <= 6; ++i) 82 neww = neww * 10 + a[i]; 83 if (!vis[neww * 10 + po]) 84 vis[neww * 10 + po] = true, q.push(neww * 10 + po), b.push(step + 1); 85 a[po]++; 86 } 87 } 88 return -1; 89 } 90 91 int main() 92 { 93 scanf("%d%d",&y,&m); 94 int t=m; 95 for (R i=1;i<=6;++i) 96 Min=min(Min,t%10),Max=max(Max,t%10),t/=10; 97 printf("%d",bfs()); 98 return 0; 99 }
魔板:https://www.luogu.org/problemnew/show/P2730
题意概述:一块$2*4$的板子,三种操作,求开始状态到目标状态的最小步数.
做多了发现这种题都是套路,状压之后模拟每个操作就好了.
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <map> 5 # include <algorithm> 6 # define R register int 7 8 using namespace std; 9 10 int cs,beg,ne,q[41000],h,t,b[41000],c[41000]; 11 int ch[3][5],a[3][5],x,sta[1000],gol,pre[41000],vis[41000]; 12 map <int,int> m1; //p->id 13 map <int,int> m2; //id->p 14 15 void init() 16 { 17 int a[10],x,h=1; 18 for (R i=1;i<=8;++i) 19 a[i]=i; 20 m1[12345678]=1; 21 m2[1]=12345678; 22 while (next_permutation(a+1,a+9)) 23 { 24 x=0; 25 for (R i=1;i<=8;++i) 26 x=x*10+a[i]; 27 m1[x]=++h; 28 m2[h]=x; 29 } 30 } 31 32 void giv() 33 { 34 for (R i=1;i<=2;++i) 35 for (R j=1;j<=4;++j) 36 ch[i][j]=a[i][j]; 37 } 38 39 int pul() 40 { 41 int x=0; 42 for (R i=1;i<=4;++i) 43 x=x*10+ch[1][i]; 44 for (R i=4;i>=1;--i) 45 x=x*10+ch[2][i]; 46 return x; 47 } 48 49 void in_que (int x,int y) 50 { 51 vis[x]=true; 52 q[++t]=x; 53 b[t]=b[h]+1; 54 c[x]=y; 55 pre[x]=q[h]; 56 } 57 58 int bfs() 59 { 60 beg=m1[12345678]; 61 q[1]=beg; 62 b[1]=0; 63 h=t=1; 64 vis[1]=true; 65 while (h<=t) 66 { 67 beg=m2[ q[h] ]; 68 if(q[h]==gol) 69 return b[h]; 70 for (R j=1;j<=4;++j) 71 a[2][j]=beg%10,beg/=10; 72 for (R j=4;j>=1;--j) 73 a[1][j]=beg%10,beg/=10; 74 giv(); 75 for (R i=1;i<=4;++i) 76 swap(ch[1][i],ch[2][i]); 77 ne=m1[pul()]; 78 if(!vis[ne]) in_que(ne,1); 79 giv(); 80 for (R i=4;i>=2;--i) 81 for (R j=1;j<=2;++j) 82 swap(ch[j][i-1],ch[j][i]); 83 ne=m1[pul()]; 84 if(!vis[ne]) in_que(ne,2); 85 giv(); 86 swap(ch[1][2],ch[1][3]); 87 swap(ch[2][2],ch[2][3]); 88 swap(ch[1][2],ch[2][3]); 89 ne=m1[pul()]; 90 if(!vis[ne]) in_que(ne,3); 91 h++; 92 } 93 return -1; 94 } 95 96 int main() 97 { 98 init(); 99 for (R i=1;i<=8;++i) 100 scanf("%d",&x),gol=gol*10+x; 101 gol=m1[gol]; 102 printf("%d ",bfs()); 103 int Top=0,tot=0; 104 for (R i=gol;i!=1;i=pre[i]) 105 sta[++Top]=c[i]; 106 for (R i=Top;i>=1;--i) 107 { 108 tot++; 109 printf("%c",sta[i]+'A'-1); 110 if(tot%60==0) printf(" "); 111 } 112 return 0; 113 }
网格图类:
棋盘:https://www.luogu.org/problemnew/show/P3956
题目概述:这道题永远不会忘。
考场上为什么写不出来啊啊啊啊。
# include <cstdio> # include <iostream> # include <cstring> # include <string> using namespace std; int m,n,c,x,y,ans=-1; bool F=false; short G[105][105]; int Min[105][105][2]; bool vis[105][105]; const int dx[]={-1,0,0,1},dy[]={0,-1,1,0}; void dfs(int x,int y,bool can,int sc) { if(x==m&&y==m) { if(ans==-1) ans=sc; ans=min(sc,ans); return ; } for (int i=0;i<4;i++) { int ax=x+dx[i]; int ay=y+dy[i]; if (vis[ax][ay]) continue; if (ax<1||ax>m||ay<1||ay>m) continue; if (can==false&&G[ax][ay]==0) continue; if(G[ax][ay]==0) { G[ax][ay]=G[x][y]; vis[ax][ay]=true; if(sc+2<Min[ax][ay][1]) { Min[ax][ay][1]=sc+2; dfs(ax,ay,false,sc+2); } vis[ax][ay]=false; G[ax][ay]=0; continue; } vis[ax][ay]=true; if(G[x][y]==G[ax][ay]) { if(Min[ax][ay][0]>sc) { Min[ax][ay][0]=sc; dfs(ax,ay,true,sc); } } else { if(Min[ax][ay][0]>sc+1) { Min[ax][ay][0]=sc+1; dfs(ax,ay,true,sc+1); } } vis[ax][ay]=false; } } int main() { memset(Min,1,sizeof(Min)); scanf("%d%d",&m,&n); if(m==1) { printf("0"); return 0; } for (int i=1;i<=n;i++) { scanf("%d%d%d",&x,&y,&c); if (c==0) G[x][y]=1; if (c==1) G[x][y]=2; } vis[1][1]=true; dfs(1,1,true,0);//1,1点;可变色,金币花费0 printf("%d",ans); return 0; }
不知道该怎么归类类:
数字三角形:https://www.luogu.org/problemnew/show/P1118
题意概述:1-n的排列中两两相加得到n-1个数,不断进行这个过程直到剩下一个数,给出这个数,求原排列。
朴素搜索大概会T,所以要加可行性剪枝,但是在全部算完后怎么知道这个数的贡献是多少呢?很巧妙,事实上每个数的权值正是当行的杨辉三角。
# include <cstdio> # include <iostream> using namespace std; int n,sum; bool f=false; int ans[15]={0}; int pascal[14][14]; bool vis[14]={0}; void writ() { for (int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d",ans[n]); f=true; } void dfs(int x,int s) { if(s==sum&&x==n+1) writ(); if(f) return ; if(s>=sum) return ; for (int i=1;i<=n;i++) { if(vis[i]) continue; vis[i]=true; ans[x]=i; dfs(x+1,s+i*pascal[n][x]); vis[i]=false; ans[x]=0; } return ; } int main() { pascal[1][1]=1; scanf("%d%d",&n,&sum); for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) if(i==1&&j==1) continue; else pascal[i][j]=pascal[i-1][j]+pascal[i-1][j-1]; dfs(1,0); return 0; }
砝码称重:https://www.luogu.org/problemnew/show/P1441
题意概述:从n个数中去掉m个数,求剩下的数能拼成的最大连续价值。(1——max都可以拼出来)
看起来是道蓝题挺吓人的,其实简单到不可思议。。。
先写一个朴素搜索,爆搜取哪些数,然后做01背包,得了60;
又卡了好久,不知道怎么优化,后来加了一句迷之剪枝竟然过了。。。因为是求数的组合,所以按照组合数那道题的思路优化。有的题目看似简单,其中的思想化用后却可以解决更难的问题。
总结出一个不算经验的搜索经验来:如果是选出一些元素且元素的顺序不影响结果时,应在dfs中加入上一层搜到的数在数组中的排名,直接从这里往后搜就好了,防止搜出重复的状态。
# include <cstdio> # include <iostream> # include <cstring> # define R register int using namespace std; int n,m,S; int ans=0,a[25]; bool vis[25]; bool dp[20009]; void check() { memset(dp,0,sizeof(dp)); dp[0]=true; for (R i=1;i<=n;i++) { if(vis[i]) continue; for (R j=S;j>=a[i];j--) dp[j]|=dp[j-a[i]]; } int anss=0; for (R i=1;i<=S;i++) if(dp[i]) anss++; ans=max(ans,anss); } void dfs(int x,int las) { if(x==m) { check(); return ; } for (R i=las;i<=n;i++) { if(vis[i]) continue; vis[i]=true; dfs(x+1,i+1); vis[i]=false; } } int main() { scanf("%d%d",&n,&m); for (R i=1;i<=n;i++) { scanf("%d",&a[i]); S+=a[i]; } dfs(0,1); printf("%d",ans); return 0; }
邮票面值设计:https://www.luogu.org/problemnew/show/P1021
题意概述:找出k个数,从中取出n个(可以重复),求最大连续价值。
一开始写了搜索of搜索,先搜取哪些数,再搜可以拼成的所有数。。。数据很水,竟然得了75分。
后来改成了搜索of dp。用到了一个性质:如果用一些小的数不能拼出X这个数字,加上一些比X大的数自然更拼不出来。所以在dfs时保存当前能拼出的连续的最大的数,选下一个数时maxn+1就是上界。
怎么判断maxn呢?这里其实挺暴力的,而且也不是很快,但是相比无尽的搜索还是快了很多,即每加入一个新的数就重新做一次dp,dp[i][j]表示从前i个数中拼出j所需数的最小个数,当然可以减掉i这一维啦。
# include <cstdio> # include <iostream> # include <cstring> # define R register int using namespace std; int n,k,ans; int a[20]; int shzr[20]; int dp(int x,int maxn) { int f[50009]; memset(f,1,sizeof(f)); f[0]=0; for (R i=1;i<=x;i++) for (R j=a[i];j<=a[i]*n;j++) f[j]=min(f[j],f[j-a[i]]+1); int Tot=1; while (f[Tot]<=n) Tot++; return Tot-1; } void dfs1(int x,int las,int maxn) { if(x==k+1) { if(maxn>ans) { ans=maxn; for (R i=1;i<=k;i++) shzr[i]=a[i]; } return ; } for (R i=las;i<=maxn+1;i++) { a[x]=i; dfs1(x+1,i+1,dp(x,maxn)); a[x]=0; } } int main() { scanf("%d%d",&n,&k); dfs1(1,1,0); for (int i=1;i<=k;i++) printf("%d ",shzr[i]); printf(" MAX=%d",ans); return 0; }
计算几何类:其实也不能算是计算几何,可是也不知道该怎么归类。
油滴扩展:https://www.luogu.org/problemnew/show/P1378
题意概述:在一个方框中滴一些油滴,合理安排滴的顺序,使得占据的总面积最大。(油滴数量<=6)
搜索判断顺序即可,对于每一滴油,判断是否被框或者是已有的油滴拦住来判断半径,就做完啦!注意一些细节:有可能旧的油滴已经覆盖了新油滴的起点,这时候要将新油滴的半径设置为0;其实这道题真正的难点在于背圆周率?。如果设置为$3.14159$,就只有70分,如果是$3.1415926$,就可以拿到满分。
1 # include <cstdio> 2 # include <iostream> 3 # include <cmath> 4 5 using namespace std; 6 7 const double pi=3.1415926; 8 int n,x_1,x_2,y_1,y_2,S; 9 double ans,r[7]; 10 int x[7],y[7]; 11 bool vis[7]; 12 13 void dfs(int a,double Su) 14 { 15 if(a==n+1) 16 { 17 ans=max(ans,Su); 18 return ; 19 } 20 for (int i=1;i<=n;++i) 21 { 22 if(vis[i]) continue; 23 vis[i]=true; 24 r[i]=10001; 25 r[i]=min(r[i],(double)x[i]-x_1); 26 r[i]=min(r[i],(double)x_2-x[i]); 27 r[i]=min(r[i],(double)y_2-y[i]); 28 r[i]=min(r[i],(double)y[i]-y_1); 29 for (int j=1;j<=n;++j) 30 { 31 if(i==j) continue; 32 if(vis[j]==false) continue; 33 r[i]=min(r[i],max((double)sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))-r[j],0.0)); 34 } 35 dfs(a+1,Su+pi*r[i]*r[i]); 36 vis[i]=false; 37 } 38 } 39 40 int main() 41 { 42 scanf("%d",&n); 43 scanf("%d%d%d%d",&x_1,&y_1,&x_2,&y_2); 44 if(x_1>x_2) swap(x_1,x_2); 45 if(y_1>y_2) swap(y_1,y_2); 46 S=(x_2-x_1)*(y_2-y_1); 47 for (int i=1;i<=n;i++) 48 scanf("%d%d",&x[i],&y[i]); 49 dfs(1,0); 50 printf("%.0lf",(double)S-ans); 51 return 0; 52 }
字符串类:
最近发现字符串类的搜索考的还是比较多的。
还有一件事就是很多题会忘记写总结,但是看总结还是比较频繁的,所以把想写却还没写的题目链接摆在这里。
统计单词个数:https://www.luogu.org/problemnew/show/P1026
字串变换:https://www.luogu.org/problemnew/show/P1032
单词接龙:https://www.luogu.org/problemnew/show/P1019
好像也就这些了吧...看到这里如果您想到了什么别的字符串搜索题,欢迎告诉我。