搜索专题
搜索
球队“食物链” (搜索+剪枝)
题目链接
解题思路:
对于PAT的题目首先要先理解好题目。这里说了起点是1终点是N的一个哈密顿回路,因此我们在建好图之后,进行搜索即可,对于剪枝,因为最后一个点必须与1相连,因此对于该搜索到该节点,如果剩余的节点没有一个连向1,那么就可以返回。建图就是如果iWj,那么i有一条指向j的边,如果为L,则j指向i。
注意点:
要四舍五入后不是0,则范围在:大于等于0.05(四舍五入0.1),或小于等于-0.05(四舍五入-0.1)。即绝对值要大于0.05
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 24;
int n ;
int g[N][N];
char str[N];
int p[N] ;
bool flag, st[N];
void dfs(int u, int t) {
if (flag)
return ;
if (t == n + 1 && g[u][1] ) {
flag = 1;
return ;
}
int k ;
for (k = 1; k <= n ; ++k)
if (!st[k] && g[k][1])
break;
if (k == n + 1)
return ;
for (int i = 1 ; i <= n; ++i) {
if (!st[i] && g[u][i] && !flag ) {
p[t] = i;
st[i] = 1;
dfs(i, t + 1);
st[i] = 0;
}
}
}
int main() {
cin >> n;
for (int i = 1 ; i <= n ; ++i) {
scanf("%s", str);
for (int j = 1 ; j <= n ; ++j) {
char ch = str[j - 1];
if (ch == 'W')
g[i][j] = 1;
else if (ch == 'L')
g[j][i] = 1;
}
}
p[1] = 1, st[1] = 1;
dfs(1, 2);
if (flag)
for (int i = 1 ; i <= n ; ++i)
if (i != n)
cout << p[i] << ' ';
else
cout << p[i] << endl;
else
cout << "No Solution\n";
return 0;
}
愿天下有情人都是失散多年的兄妹(搜索+公共祖先)
题目链接
解题思路
我们将关系进行建树(用邻接表来存)后,只要在搜索的时候,出现当后面搜索的那个人,有一个存在的关系节点,被访问过了就说明它们两个之间在五代之内存在关系。
注意事项
需要将父母的性别也记录下来,不然有几个点过不去。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
vector<int>g[N];
int n, m;
char sex[N];
bool st[N], key;
void dfs(int u, int h) {
st[u] = 1;
if (h >= 4)
return ;
for (int i = 0 ; i < g[u].size() ; ++i) {
int x = g[u][i];
if (!st[x])
dfs(x, h + 1);
else
key = 1;
}
}
int main() {
cin >> n ;
for (int i = 1; i <= n ; ++i) {
int a, b, c;
char x;
cin >> a >> x >> b >> c;
sex[a] = x;
if (b != -1)
g[a].push_back(b) ,sex[b] = 'M';
if (c != -1)
g[a].push_back(c) ,sex[c] = 'F';
}
cin >> m;
while (m--) {
int a, b;
cin >> a >> b;
if (sex[a] == sex[b])
cout << "Never Mind\n";
else {
key = false;
memset(st, 0, sizeof st);
dfs(a, 0);
dfs(b, 0);
if (key)
cout << "No\n";
else
cout << "Yes\n";
}
}
return 0;
}
功夫传人
题目链接
解题思路
这题就是依据关系建好图之后在图上跑一遍dfs。注意的是只是求得道者的功力总和。
代码:
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
vector<int>g[N]; //邻接表存图
int n ;
double Z, r, power[N] , ans;
bool st[N] ;
void dfs(int u, double val) {
if (power[u]) {
ans += val * power[u];
st[u] = 1;
return ;
}
st[u] = 1;
for (int i = 0 ; i < (int)g[u].size() ; ++i) {
int v = g[u][i];
if (!st[v])
dfs(v, val * r);
}
}
int main() {
cin >> n >> Z >> r;
r = (100.0 - r) / 100.0;
for (int i = 0 ; i < n ; ++i) {
int k ;
cin >> k;
if (k == 0) // 得道者
cin >> power[i];
while (k--) {
int x;
cin >> x;
g[i].push_back(x);
}
}
dfs(0, Z);
cout << (LL)ans << endl;
return 0;
}
凑零钱(搜索 + 两种做法)
题目链接
注意事项:
本题指的顺序最小是排完序后搜索到的第一个解。
方法一:
对于每一个数字我们有选与不选,因此我们用一个bool 数组记录该路径下的选择情况,但到达某一个节点满足条件后,将该路径被标记的点输出。如果不选择,我们回溯的方式就是将这个位置置为FALSE。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], f ;
bool flag, st[N];
void dfs(int s, int sum) {
if ( s > n || flag || sum > m)
return ;
if (sum == m) {
flag = true;
for (int i = 0 ; i < n ; ++i)
if (st[i]) {
if (f)
cout << " ";
cout << a[i], f++;
}
return ;
}
st[s] = 1;
dfs(s + 1, sum + a[s]);
st[s] = 0 ;
dfs(s + 1, sum);
return ;
}
int main() {
cin >> n >> m;
int sum = 0;
for (int i = 0 ; i < n ; ++i) {
cin >> a[i];
sum += a[i];
}
sort(a, a + n);
if (sum < m)
cout << "No Solution\n";
else {
dfs(0, 0);
if (!flag)
cout << "No Solution\n";
}
return 0;
}
方法二
这里我们采用规定搜索顺序的方式,起始的位置是呈现单调的,这样可以做到不重不漏。对于路径上用的点,我们也可以想上面用bool数组记录,也可以用数组直接存值,回溯的时候就将位置减1。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt=0,f=0;
int a[10005];
int ans[10005];
void dfs(int pos,int sum)
{
if(sum>m) return;
if(sum==m){
f=1;
for(int i=0;i<cnt;i++)
printf(i==cnt-1?"%d":"%d ",ans[i]);
return;
}
for(int i=pos;i<n;i++){
ans[cnt++]=a[i];
dfs(i+1,sum+a[i]);
if(f) break;
cnt--;
}
}
int main()
{
scanf("%d%d",&n,&m);
int sum=0;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
if(sum<m) puts("No Solution");
else{
sort(a,a+n);
dfs(0,0);
if(!f) puts("No Solution");
}
return 0;
}
图论
紧急救援
题目链接
解题思路:
想知道最短路有几条,要先求出最短路长度(dij),然后用dfs判断:若dist[j]=dist[i]+g[i]j[j],则这条路也在最短路上,可以继续往下搜。
这里的边是无向边。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int>PII;
const int N = 510, INF = 0x3f3f3f3f;
int n, m, s, D;
int g[N][N], d[N], people[N];
bool st[N];
vector<int>ans, temp ; // 对于路径来说,用vector来求比较好
void dijkstra() {
d[s] = 0 ;
priority_queue<PII> heap;
heap.push({0, s}); // 距离需要放在第一位
while (heap.size()) {
auto u = heap.top();
heap.pop();
int x = u.second; // 下标在第二位
if (st[x])
continue;
st[x] = 1;
for (int i = 0 ; i < n ; ++i) {
if (g[x][i] < INF && !st[i]) {
if (d[i] > d[x] + g[x][i]) {
d[i] = d[x] + g[x][i];
heap.push({-d[i], i}); // 由于设置会大根堆,因此距离放负
}
}
}
}
}
int maxn, cnt;
void dfs(int u, int sum) {
if (u == D) {
if (sum > maxn) {
maxn = sum;
ans = temp;
}
cnt ++;
return ;
}
st[u] = 1;
for (int i = 0 ; i < n ; ++i)
if (!st[i] && d[i] == d[u] + g[u][i]) {
temp.push_back(i);
st[i] = 1;
dfs(i, sum + people[i]);
st[i] = 0;
temp.pop_back();
}
}
int main() {
memset(g, 0x3f, sizeof g);
memset(d, 0x3f, sizeof d);
cin >> n >> m >> s >> D;
for (int i = 0 ; i < n; ++i)
cin >> people[i];
while (m--) {
int a, b, c;
cin >> a >> b >> c;
g[a][b] = c, g[b][a] = c;
}
dijkstra();
cnt = 0;
temp.push_back(s);
memset(st, 0, sizeof st);
dfs(s, people[s]);
cout << cnt << " " << maxn << endl;
for (int i = 0 ; i < ans.size() - 1; ++i)
cout << ans[i] << " ";
cout << D << endl;
return 0;
}
红色警报(图论+搜索)
题目链接
解题思路:
这题就是说在将一个点分离出去后是否会影响原来图的连通性问题。对于图的连通块个数可以通过dfs来求。对于分离一个节点之后,如果使得连通性发生改变,那么连通块增加的数量会大于1,因为分离的节点也会算一个。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010, M = 5010 ;
int n, m, k;
bool st[N];
int g[N][N];
void dfs(int u ) {
st[u] = 1;
for (int i = 0 ; i < n ; ++i ) {
if (!st[i] && g[u][i])
dfs(i);
}
}
int solve() {
int ans = 0;
memset(st, 0, sizeof st);
for (int i = 0 ; i < n ; ++i)
if (!st[i]) {
ans++;
dfs(i);
}
return ans;
}
int main() {
cin >> n >> m;
while (m--) {
int a, b;
cin >> a >> b;
g[a][b] = g[b][a] = 1;
}
int now = solve();
cin >> k;
for(int i = 0 ; i < k ; ++i) {
int x;
cin >> x;
for (int j = 0 ; j < n ; ++j)
g[x][j] = g[j][x] = 0;
int ans = solve();
if (ans > now + 1 )
cout << "Red Alert: City " << x << " is lost!\n";
else
cout << "City " << x << " is lost.\n";
now = ans;
}
if (k == n)
cout << "Game Over.\n";
return 0;
}
分而治之(连通性+领接表)
题目链接
解题思路
这题与上面题目类似,这不过是问在将给定的点分离后,是否能将所以的点都独立起来。也就是连通块的数量等于节点数量。
- 由于节点太多,因此必须用领接表来存。同时需要用vector来模拟,不能用链式前向星。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include<vector>
using namespace std;
const int N = 10010, M = 10010 ;
int n, m, k;
vector<int>g[N];
int h[N], e[M], nx[M], cnt;
bool st[N] ;
void dfs(int u ) {
st[u] = 1;
for(auto v : g[u]) {
if (!st[v] )
dfs(v);
}
}
int solve() {
int ans = 0;
for (int i = 1 ; i <= n ; ++i)
if (!st[i]) {
ans++;
dfs(i);
}
return ans;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--) {
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
//add(a, b), add(b, a);
}
cin >> k;
while (k--) {
int x;
cin >> x;
memset(st, 0, sizeof st);
int res = 0;
for (int i = 1 ; i <= x ; ++i) {
int y ;
cin >> y;
st[y] = 1 , res ++;
}
res += solve();
if (res == n )
cout << "YES\n";
else
cout << "NO\n";
}
return 0;
}
图着色问题(邻接点检测)
解题思路
就是一个邻接点的检测,。
如果给定的配色方案颜色种类个数不等于k,输出No
那就直接输出No如果相邻两点之间有路,并且他们的颜色一样,输出No
如果不符合上述两种情况,输出Yes
- 对于种类数的求法,一个是利用map来判重,或者是用一个bool数组,另一种思路是利用set要自动去重的功能。
代码:
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <set>
#include <queue>
#include <map>
using namespace std;
const int N = 1010, M = 1E5 + 10;
int g[N][N], c[N], cloor[N];
int n, m, k;
set<int>s;
int solve() {
for (int i = 1 ; i <= n ; ++i)
for (int j = 1 ; j <= n ; ++j)
if (i != j )
if (g[i][j] == 1 && c[i] == c[j])
return false;
return true;
}
int main() {
cin >> n >> m >> k;
while (m--) {
int a, b;
cin >> a >> b;
g[a][b] = g[b][a] = 1;
}
int num;
cin >> num;
for (int j = 1 ; j <= num ; ++j) {
s.clear();
for (int i = 1 ; i <= n ; ++i) {
cin >> c[i];
s.insert(c[i]);
}
if ((int)s.size() != k)
cout << "No\n";
else {
if (solve())
cout << "Yes\n";
else
cout << "No\n";
}
}
return 0;
}
直捣黄龙(最短路 )
题目链接
题意:找到己方大本营到敌方大本营的一条最短路径,当这样的路径有多条时选择解放最多城镇的路径(就是经过的城市越多越好),当这样的路径也有多条时选择杀敌数最多的路。
解题思路:
Dijkstra (堆优化)+ 多重条件判断。
数据处理:用map容器对表示城市的字符串做数据映射,使得城市可以用0到N-1表示,方便建立邻接表,然后套上最短路径模板 + 三个条件判断。
path[i]
:表示i的前一个节点是path[i]
代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <string>
#include <cstring>
using namespace std;
typedef pair<int, int>PII;
const int N = 1010;
int n, m, start, End ;
map<string, int>mp;
map<int, string>name;
int h[N], w[N], ne[N], e[N], cnt;
int d[N], path[N], kill[N], node[N], army[N], road[N], em[N];
bool st[N];
//d存最短路径,path存路径,kill存杀敌数,node存经过城市数量
//em存各城市的士兵,road存最短路数量
void add(int u, int v, int val) {
e[++cnt] = v, ne[cnt] = h[u], w[cnt] = val, h[u] = cnt;
}
void dijstra() {
memset(path, -1, sizeof path);
memset(d, 0x3f, sizeof d);
d[start] = 0;
priority_queue<PII>heap;
heap.push({0, start});
road[start] = 1; // 初始化
while (heap.size()) {
int u = heap.top().second;
heap.pop();
if (st[u])
continue;
st[u] = 1;
for (int i = h[u] ; ~i ; i = ne[i]) {
int v = e[i];
if (!st[v] && d[v] > d[u] + w[i])
heap.push({-(d[u] + w[i]), v});
if (!st[v] && d[v] >= d[u] + w[i]) {
if (d[v] > d[u] + w[i]) { // 如果通过u 节点到v 更近,那么需要走这条
d[v] = d[u] + w[i];
node[v] = node[u] + 1; //前面经过的城市数 + 这个城市
kill[v] = kill[u] + em[v]; //前面所杀的敌人数 + 这个城市的士兵数
path[v] = u, road[v] = road[u];
} else { // 有别的路径到v 与通过u 距离一样
road[v] += road[u];
if (node[u] + 1 >= node[v]) { // 看经过的节点数量
if (node[u] + 1 > node[v]) { // 如果通过u的数量更多
kill[v] = kill[u] + em[v];
node[v] = node[u] + 1;
path[v] = u;
} else {
if (kill[u] + em[v] > kill[v]) { // 如果连经过的节点也一样看杀的敌人数量
kill[v] = kill[u] + em[v];
path[v] = u;
}
}
}
}
}
}
}
}
void Find(int x) { //递归输出路径
if (path[x] != -1) {
Find(path[x]);
cout << name[path[x]] << "->";
}
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
string s1, s, se, s2;
cin >> s1 >> se;
mp[s1] = 1, name[1] = s1;
for (int i = 2 ; i <= n ; ++i) {
int x, a;
cin >> s >> x;
mp[s] = i, em[i] = x;
name[i] = s;
}
start = mp[s1], End = mp[se];
while (m--) {
int a, b, c ;
cin >> s1 >> s2 >> c;
a = mp[s1], b = mp[s2];
add(a, b, c), add(b, a, c);
}
dijstra();
Find(End);
cout << se << endl;
cout << road[End] << ' ' << d[End] << ' ' << kill[End] << endl;
return 0;
}
网红点打卡攻略(建边 + 技巧)
题目链接
解题思路
- 对于是否能不重不漏的打卡完所有点,我们在读入方案的时候,如果一个重复出现或者点个数不等于 n ,或者有不连通的情况都不可以。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], d[N];
bool st[N];
int p[N][N];
int main() {
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while (m--) {
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = c;
}
cin >> m;
int mi, cnt = 0;
for (int j = 1 ; j <= m ; ++j) {
memset(st, 0, sizeof st);
bool flag = true;
int k, pre = 0, sum = 0, x;
cin >> k;
for (int i = 0 ; i < k ; ++i) {
cin >> x;
p[j][i] = x;
if (g[pre][x] >= INF / 200) // 不连通
flag = false;
sum += g[pre][x];
if (st[x])
flag = false;
st[x] = 1, pre = x;
}
sum += g[x][0];
// 不连通 或者 打卡点数不够
if (k != n || g[0][p[j][0]] >= INF / 200 || g[p[j][n - 1]][0] >= INF / 200 )
flag = false;
if (flag) {
d[j] = sum;
if (!cnt)
mi = j ;
else if (d[j] < d[mi])
mi = j;
cnt++;
}
}
cout << cnt << endl;
cout << mi << " " << d[mi] << endl;
return 0;
}