题集转自以下链接:
https://blog.csdn.net/shahdza/article/details/7986044
胜利大逃亡(续) HDU - 1429
题意:就是二维地图,有障碍;重点有门,和钥匙(用于开门)。
理解:因为找钥匙,这个解答路径可能会走回头路。
思路:第一遍,因为有时间限制,并且20*20;所以简单bfs,然后MLimit了。感觉就是重复存储点在队列。
那么难点就在于判重了。因为走回头路,简单二维判重肯定不行。
回想和之前炮台那题,因为子弹的原因也是走回头路,所以判重数组多加了一个时间维度。
这题关键因素是钥匙,所以加一个钥匙拥有状态的判重,就ac了。(用2进制状态压缩,这里因为a-j,所以数组开个1024就够了。)
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; int n,m,t; char ditu[21][21]; bool vis[1025][21][21]; int Sx,Sy,Fx,Fy; int xx[] = {0,0,1,-1}; int yy[] = {1,-1,0,0}; struct node{ int x,y,step,k=0; node(int a,int b,int c,int d):x(a),y(b),step(c),k(d) {}; }; bool bfs(){ memset(vis,false,sizeof vis); queue<node> q;q.push(node(Sx,Sy,0,0));vis[0][Sx][Sy] = true; while(!q.empty()){ node p = q.front();q.pop(); for(int i=0;i<4;i++){ int x = p.x+xx[i],y = p.y+yy[i]; if(x<0||x>=n||y<0||y>=m||ditu[x][y]=='*'||p.step+1>=t) continue; int step = p.step + 1,k = p.k; if(ditu[x][y]>='A'&&ditu[x][y]<='Z'&&!(p.k&(1<<ditu[x][y]-'A'))) continue; if(ditu[x][y] == '^') {cout<<step<<endl;return true;} if(ditu[x][y]>='a'&&ditu[x][y]<='z') k |= 1<<(ditu[x][y]-'a'); if(vis[k][x][y]) continue; q.push(node(x,y,step,k));vis[k][x][y] = true; } } return false; } int main() { while(cin>>n>>m>>t){ for(int i=0;i<n;i++)for(int j=0;j<m;j++){ char ch = getchar();while(ch==' '||ch==' ') ch = getchar(); ditu[i][j] = ch; if(ch=='^') Fx = i,Fy = j; else if(ch=='@') Sx = i,Sy = j; } if(!bfs()) puts("-1"); } return 0; }
这题给我启发就是,判重作用在于剪枝,避免重复的入队操作。那么关键在于什么?关键在于点状态更新,并且取决于什么要素关键,炮台那题,因为子弹原因,时间是关键。这题因为门的原因,钥匙是关键。所以添加关键要素的判重数组是解题关键。
Key Task HDU - 1885
题意与上题一样。但是地图大小和钥匙,门出口数量不同。
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #include <map> using namespace std; int n,m; char ditu[101][101]; bool vis[17][101][101]; int Sx,Sy,Fx,Fy; int xx[] = {0,0,1,-1}; int yy[] = {1,-1,0,0}; struct node{ int x,y,step,k=0; node(int a,int b,int c,int d):x(a),y(b),step(c),k(d) {}; }; int mp[5]; bool bfs(){ memset(vis,false,sizeof vis); queue<node> q;q.push(node(Sx,Sy,0,0));vis[0][Sx][Sy] = true; while(!q.empty()){ node p = q.front();q.pop(); for(int i=0;i<4;i++){ int x = p.x+xx[i],y = p.y+yy[i]; if(x<0||x>=n||y<0||y>=m||ditu[x][y]=='#') continue; int step = p.step + 1,k = p.k; if(ditu[x][y] == 'X') {cout<<"Escape possible in "<<step<<" steps."<<endl;return true;} if(ditu[x][y]>='A'&&ditu[x][y]<='Z'&&!(p.k&(1<<mp[ditu[x][y]-'A']))) continue; if(ditu[x][y]>='a'&&ditu[x][y]<='z') k |= 1<<mp[ditu[x][y]-'a']; if(vis[k][x][y]) continue; q.push(node(x,y,step,k));vis[k][x][y] = true; } } return false; } int main() { mp['B'-'A'] = 0,mp['Y'-'A'] = 1,mp['R'-'A'] = 2,mp['G'-'A'] = 3; while(cin>>n>>m&&n&&m){ for(int i=0;i<n;i++)for(int j=0;j<m;j++){ char ch = getchar();while(ch==' '||ch==' ') ch = getchar(); ditu[i][j] = ch; if(ch=='X') Fx = i,Fy = j; else if(ch=='*') Sx = i,Sy = j; } if(!bfs()) puts("The poor student is trapped!"); } return 0; }
扩展思考:我这里想到如果钥匙并非永久产品而是一次性,那该怎么办?
我感觉难点可能在于不止走一次回头路。
我一开始能想到就是:像多重背包操作一样,将问题转化为0-1,多个相同门和钥匙,对应将他们分类成不同颜色。额,好像不行,因为你不清楚那个钥匙对应哪个门即为最优。。。。
那判重函数多加一维度,有什么颜色钥匙,并且多一个钥匙数量的维度。
超级密码HDU - 1226
算法:BFS
思路:BFS向下操作,*进制数+构成数 然后 对N求余 得到 的余数,因为是要正整数倍,所以当余数为0则为答案。
K1%N = K2%N;(K1*C+M)%N = (K2*C+M)%N ;所以余数可以作为判重点。相同余数不需要重复入队。
用数组记录答案长度,因为BFS是有层次性,所以超过500就可以判负了。
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <map> using namespace std; int N,C,M; int yS[17],ySlen; int vis[5005],pre[5005],ans[5005],cnt[5005]; void init(){ memset(vis,0,sizeof vis); memset(cnt,0,sizeof cnt); memset(pre,-1,sizeof pre); scanf("%d%d%d",&N,&C,&M); for(int i=0;i<M;i++){ char ch = getchar();while(ch == ' '|| ch == ' ') ch = getchar(); if(ch>='0'&&ch<='9') yS[i] = ch - '0'; else yS[i] = ch - 'A' + 10; } sort(yS,yS+M); ySlen = unique(yS,yS+M) - yS; } void showAns(int num){ if(num == -1) return ; showAns(pre[num]); if(ans[num]<10) printf("%d",ans[num]); else printf("%c",ans[num] - 10 + 'A'); } bool solve(){ queue<int> q; for(int i=0;i<ySlen;i++){ if(!yS[i]) continue; int tnt = yS[i] % N; if(vis[tnt]) continue; vis[tnt] = 1;cnt[tnt] = 1; ans[tnt] = yS[i]; if(!tnt) return true; q.push(tnt); } while(!q.empty()){ int now = q.front();q.pop(); if(cnt[now]>500) return false; for(int i=0;i<ySlen;i++){ int tnt = (now*C + yS[i]) % N; if(vis[tnt]) continue; vis[tnt] = 1;cnt[tnt] = cnt[now] + 1; ans[tnt] = yS[i];pre[tnt] = now; if(!tnt) return true; q.push(tnt); } } return false; } int main() { int _;scanf("%d",&_); while(_--){ init(); if(!N) {if(!yS[0]) puts("0");else puts("give me the bomb please");} else{ if(!solve()) puts("give me the bomb please"); else showAns(0),puts(""); } } return 0; }
Different DigitsHDU - 1664
算法:数论+BFS
思路:感觉这题集出得挺不错,每道题总与上一道题有关联。这一题和上一题一样,也是求N的正整数倍。所以也是余数下手。
但是这题巧妙在于,最优解是,答案需尽量少不同数字构成且最小。
这里牵扯的数论就是:
很好理解:求10进制的余数,(*10+1)MOD,因为只加一个数字的求余存在一个循环,存在0则为答案。余数相同,就互减得到答案,互减将余数减掉就是N的整数倍了。
所以一个整数一定存在一个正整数倍的数,且由0和1的数构成。
所以,两不同数字就构成本题答案;
那么这题剩下就是求一个最优最小解,即不同数字的数量相同求最小。
分两层,第一层只用一个数叠加求,得到答案就是最优。得不到去第二层,循环从不同两个数字组合求出答案。
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <map> using namespace std; const int inf = 2e9; int N,ansLen; bool vis[65536]; string ans,res; bool gO(int x){ memset(vis,false,sizeof vis); int tmp = 0;res = ""; while(1){ tmp = (tmp*10+x) % N; if(vis[tmp]) return false; vis[tmp] = true; res += '0' + x; if(!tmp){ if(ansLen == inf||ans.length()>res.length()){ ans = res;ansLen = res.length();return true; } return false; } } } int val[65536],pre[65536],cnt[65536]; void getAns(int x){ if(x==-1) return ; getAns(pre[x]); res += '0' + val[x]; } bool gT(int x,int y){ memset(vis,false,sizeof vis); memset(pre,-1,sizeof pre); memset(cnt,0,sizeof cnt); queue<int> q; int fk,num[2]={x,y}; if(x) { fk = x%N;vis[fk] = true,val[fk] = x,cnt[fk] = 1;q.push(fk); } fk = y%N; if(!vis[fk]) {vis[fk] = true,val[fk] = y,cnt[fk] = 1;q.push(fk);} while(!q.empty()){ int now = q.front();q.pop(); if(cnt[now]>ansLen) return false; for(int i=0;i<2;i++){ fk = (now*10 + num[i]) % N; if(vis[fk]) continue; vis[fk] = true,val[fk] = num[i],cnt[fk] = cnt[now] + 1,pre[fk] = now; if(!fk){ res = "";getAns(0); if(ansLen==inf||ans.length()>res.length()||(ans.length()==res.length()&&res<ans)){ ans = res;ansLen = res.length(); return true; } return false; } q.push(fk); } } return false; } int main() { while(cin>>N&&N){ int flag = 0; ansLen = inf;ans = ""; for(int i=1;i<10;i++) if(gO(i)) flag = 1; if(flag) {cout<<ans<<endl;continue;} for(int i=0;i<10;i++) for(int j=i+1;j<10;j++) gT(i,j); cout<<ans<<endl; } return 0; }
Pusher HDU - 2821
算法:枚举+dfs(简单模拟)
题意:类似手机游戏,一副25*25的地图,有多个障碍物,障碍物由字母表示,a为一方块,b为内嵌的2方块,以此类推。
球体,要选取一个地方起步(这里我用全地图枚举),进行弹射必须有一个空格当空隙。
起步后,方向不变,直到撞到方块才停下,且推方块向后一步,方块数减一,剩余方块积累到后一步。后一步如果是地图外,则起步方块消失。
如果球飞出地图,则失败。
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <map> using namespace std; int n,m; int ditu[26][26],sum; int xx[4] = {0,0,1,-1}; int yy[4] = {1,-1,0,0}; char dire[5] = "RLDU"; int len; char ans[2500]; void init(){ sum = 0,len=0; memset(ditu,0,sizeof ditu); for(int i=0;i<n;i++)for(int j=0;j<m;j++){ char ch = getchar(); while(ch==' '||ch==' ') ch = getchar(); if(ch>='a'&&ch<='z') ditu[i][j] += ch - 'a' + 1, sum+= ditu[i][j]; } } bool check(int a,int b,int c){ int x = a + xx[c] , y = b + yy[c]; if(x<0||x>=n||y<0||y>=m||ditu[x][y]!=0) return false; return true; } int check3(int x,int y){ if(x<0||x>=n||y<0||y>=m) return 0; if(ditu[x][y]!=0) return 1; return 2; } bool check2(int &a,int &b,int c){ while(1){ a += xx[c],b += yy[c]; int tnt = check3(a,b); if(tnt==0) return false; if(tnt==1) return true; } } bool dfs(int x,int y,int z){ if(z==0) return true; for(int i=0;i<4;i++){ if(!check(x,y,i)) continue; int a = x,b = y; if(!check2(a,b,i)) continue; //cout<<x<<" "<<y<<" "<<a<<" "<<b<<" "<<z<<endl; bool flag = false; if(check3(a+xx[i],b+yy[i])!=0) flag = true; int fk = ditu[a][b],fkk,c; if(flag){ fkk = ditu[a+xx[i]][b+yy[i]]; ditu[a+xx[i]][b+yy[i]] += fk - 1; c = z - 1; }else c = z - fk; ditu[a][b] = 0; ans[len++] = dire[i]; if(dfs(a,b,c)) return true; len--; ditu[a][b] = fk; if(flag) ditu[a+xx[i]][b+yy[i]] = fkk; } return false; } int main() { while(cin>>m>>n){ init(); //cout<<sum<<endl; //for(int i=0;i<n;i++){for(int j=0;j<m;j++) cout<<ditu[i][j]<<" ";cout<<endl;} int cp = 1; for(int i=0;i<n&&cp;i++)for(int j=0;j<m&&cp;j++) if(ditu[i][j]==0&&dfs(i,j,sum)) { printf("%d %d ",i,j); ans[len] = '