状压搜索、迭代加深搜索、剪枝、记忆化搜索
前言
继续填搜索的坑,不过估计是永远也填不完的 QAQ
- 埃及分数问题 与 迭代加深搜索
https://www.cnblogs.com/hchlqlz-oj-mrj/p/5389223.html - 剪枝
https://hrbust-acm-team.gitbooks.io/acm-book/content/search/search_opt.html
胜利大逃亡(续) - HDU - 1429
思路 —— 状压搜索
这题与普通的搜索题的区别在于有钥匙和锁的门,借助状压DP的思想以及门只有从A到J十个,我们的used数组可以开多一维表示目前在点(x,y)拿到了哪些钥匙,而搜索时使用BFS,也多加一个变量用于表示当前拿到哪些钥匙,再对应搜索即可
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 20 + 3;
const int inf = 0x3f3f3f3f;
struct Node{
pii dor;
int step, st;
};
bool used[N][N][1 << 10];
char G[N][N];
const int dx[] = {1, 0, -1, 0};
const int dy[] = {0, 1, 0, -1};
int bfs(pii src, pii des, int n, int m, int k){
memset(used, false, sizeof(used));
queue<Node> que;
que.push(Node{src, 0, 0});
while(!que.empty()){
Node u = que.front();
que.pop();
if(u.step >= k) return inf; //加上这个剪枝会快很多
int x = u.dor.first, y = u.dor.second;
for(int k = 0; k < 4; k++){
int nx = x + dx[k], ny = y + dy[k];
int nst = u.st;
if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if(G[nx][ny] == '*') continue;
//还没拿到这个门的钥匙则不能往里走
if(G[nx][ny] >= 'A' && G[nx][ny] <= 'J' && (u.st & (1 << (G[nx][ny] - 'A'))) == 0) continue;
//拿到新的钥匙则更新状态
if(G[nx][ny] >= 'a' && G[nx][ny] <= 'j') nst |= (1 << (G[nx][ny] - 'a'));
if(used[nx][ny][nst]) continue;
used[nx][ny][nst] = true;
if(make_pair(nx, ny) == des) return u.step + 1;
que.push(Node{make_pair(nx, ny), u.step + 1, nst});
}
}
return inf;
}
int main(){
int n, m, k;
while(~scanf("%d%d%d", &n, &m, &k)){
for(int i = 0; i < n; i++) scanf("%s", G[i]);
pii src, des;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(G[i][j] == '@') src = make_pair(i, j);
if(G[i][j] == '^') des = make_pair(i, j);
}
}
int ans = bfs(src, des, n, m, k);
printf("%d\n", ans < k ? ans : -1);
}
}
Magical Girl Haze - ACM-ICPC 2018 南京赛区网络预赛
题意
给定一幅图,求将K条边置为0后,从点1到点n的最短路的值
思路 —— 最短路 + 状压思想
借助状压DP的思想,在跑Dijkstra的时候在d数组上多开一维记录下在删除k条边时到达这一点的最短路的值,队列中也是多开一维记录下当前已经删除了k条边,再进入进入下一个点时,可以不删边进入,在当前还能继续删边时,还要再加入一个删边进入了的点
另外,在队列中让删边多的点先出队列能提高运行效率(不确定不这样做会不会TLE)
最后,答案是d[n][k]
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 3;
const int M = 2e5 + 3;
const ll inf = 1LL << 60;
struct edge{
int v, w, nxt;
};
struct Node{
int u;
ll w;
int k;
bool operator < (const Node& b) const{
if(w != b.w) return w > b.w;
else return k < b.k;
}
};
priority_queue<Node> que;
ll d[N][12];
edge e[M];
int head[N];
int tot;
inline int read(){
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while('0' <= ch && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
inline void init(){
memset(head, -1, sizeof(head));
tot = 0;
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
ll dijkstra(int n, int lim){
while(!que.empty()) que.pop();
fill(d[0], d[N - 1] + 12, inf);
d[1][0] = 0;
que.push(Node{1, 0, 0});
while(!que.empty()){
Node nd = que.top();
que.pop();
int u = nd.u, w = nd.w, k = nd.k;
if(w > d[u][k]) continue;
if(u == n) return w;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(d[v][k] > d[u][k] + e[i].w){
d[v][k] = d[u][k] + e[i].w;
que.push(Node{v, d[v][k], k});
}
if(k + 1 <= lim && d[v][k + 1] > d[u][k]){
d[v][k + 1] = d[u][k];
que.push(Node{v, d[v][k + 1], k + 1});
}
}
}
return inf;
}
int main(){
int t = read();
while(t--){
int n = read(), m = read(), k = read();
init();
while(m--){
int u = read(), v = read(), w = read();
addEdge(u, v, w);
}
printf("%lld\n", dijkstra(n, k));
}
}
埃及分数 - Vijos 1308
思路 —— 迭代加深搜索
具体见上面的博客链接,蒟蒻觉得那个写得蛮好的
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 20;
const double eps = 1e-6;
ll ans[N], tmp[N] = {0};
bool dfs(int i, int lim, ll a, ll b){
if(i > lim){
if(a == 0 && ans[lim] > tmp[lim]) memcpy(ans, tmp, sizeof(tmp));
return a == 0;
}
ll l = max(b/a, tmp[i - 1] + 1), r = b/a*(lim - i + 1);
bool flag = false;
for(ll j = l; j <= r; j++){
ll nb = b*j/__gcd(b, j);
ll na = a*nb/b - nb/j;
if(na < 0) continue;
tmp[i] = j;
if(dfs(i + 1, lim, na, nb)) flag = true;
}
return flag;
}
int main(){
ll a, b;
while(~scanf("%lld%lld", &a, &b) && (a | b)){
memset(ans, 0x3f, sizeof(ans));
int lim = 1;
while(!dfs(1, lim, a, b)) lim++;
for(int i = 1; i <= lim; i++){
printf("%lld", ans[i]);
if(i < lim) putchar(' ');
}
puts("");
}
}
Addition Chains - POJ - 2248
题意
给定一个数n,求一个严格递增的序列满足1, .., n,,且对于任意一个(1 <= k <= m)上的数ak,都存在ak = ai + aj(除了开头的1不需要满足),且序列元素个数需满足最小
思路 —— 迭代加深搜索
考虑使用迭代加深搜索,逐层递增进行搜索
每次算一个数,同时要用打表思维打出下一个数有哪些数是可以填的
最后就是普通的搜索
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 200 + 15;
int cnt[N] = {0, 1};
int tmp[N] = {1}, ans[N] = {1};
bool dfs(int i, int lim, int m){
if(i > lim){
memcpy(ans, tmp, sizeof(tmp));
return true;
}
int l, r;
if(i == 1) l = r = 1;
else if(i == lim) l = max(m, tmp[i - 1] + 1), r = m;
else l = tmp[i - 1] + 1, r = m - 1;
bool flag = false;
for(int x = l; x <= r; x++){
if(!cnt[x]) continue;
tmp[i] = x;
for(int j = i; j >= 1; j--){
cnt[x + tmp[j]]++;
}
if(dfs(i + 1, lim, m)) flag = true;
for(int j = i; j >= 1; j--){
cnt[x + tmp[j]]--;
}
if(flag) return true;
}
return flag;
}
int main() {
int m;
while(scanf("%d", &m) && m){
if(m == 1){
puts("1");
}else{
int n = 2;
while(!dfs(1, n, m)) n++;
for(int i = 1; i <= n; i++){
printf("%d", ans[i]);
if(i < n) putchar(' ');
}
puts("");
}
}
return 0;
}
N皇后问题 - HDU - 2553
思路 —— 状压搜索
其实用状压DP的思路迁移一下就可以了
具体见代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 20;
const double eps = 1e-6;
int a[N];
//ld:y=x的对角线不可放位置
//m:x=0的线不可放的位置
//rd:y=-x的对角线不可放的位置
int dfs(int dpt, int n, int ld, int m, int rd){
if(dpt > n) return 1;
int st = ~(ld | m | rd); //取反,得到可放的位置
if(!st) return 0;
int ans = 0;
for(int cur = (1 << (n - 1)), j = 1; cur >= 1; cur >>= 1, j++){
if((st & cur) == 0) continue; //当前位置不可放,则continue
//更新ld,m,rd的信息
ans += dfs(dpt + 1, n, (ld | cur) << 1, m | cur, (rd | cur) >> 1);
}
return ans;
}
int main(){
for(int i = 1; i <= 10; i++) a[i] = dfs(1, i, 0, 0, 0);
int n;
while(scanf("%d", &n) && n){
printf("%d\n", a[n]);
}
}
Tempter of the Bone - ZOJ - 2110
题意
给定一个迷宫,走过的路不能再走,问是否能在t时刻到达出口
思路 —— 奇偶剪枝
实际上只是普通的DFS加上奇偶剪枝
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 10;
const int dx[] = {1, 0, -1, 0};
const int dy[] = {0, 1, 0, -1};
char G[N][N];
bool used[N][N];
inline int mabs(int x){ return x > 0 ? x : -x; }
inline int getDistance(const pii& u, const pii& v){
return mabs(u.first - v.first) + mabs(u.second - v.second);
}
bool dfs(pii u, pii des, int step, int lim, int n, int m){
if(u == des) return step == lim;
int x = u.first, y = u.second, d = getDistance(u, des);
//当前步数 + 到des的最短距离还比lim大,那么肯定到不了,剪枝
if(step + d > lim) return false;
//lim - step与d的奇偶性不同,即剩下要走的步数和最短步数奇偶性不同
//根据奇偶剪枝,肯定走不到,减掉
if((lim - step - d)&1) return false;
for(int k = 0; k < 4; k++){
int nx = x + dx[k], ny = y + dy[k];
if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if(G[nx][ny] == 'X' || used[nx][ny]) continue;
used[nx][ny] = true;
if(dfs(make_pair(nx, ny), des, step + 1, lim, n, m)) return true;
used[nx][ny] = false;
}
return false;
}
int main(){
int n, m, k;
while(~scanf("%d%d%d", &n, &m, &k) && (n | m | k)){
for(int i = 0; i < n; i++) scanf("%s", G[i]);
pii src, des;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(G[i][j] == 'S'){
src = make_pair(i, j);
}else if(G[i][j] == 'D'){
des = make_pair(i, j);
}
}
}
memset(used, false, sizeof(used));
used[src.first][src.second] = true;
puts(dfs(src, des, 0, k, n, m) ? "YES" : "NO");
}
}
Free Candies - UVA - 10118
题意
给定4堆糖果,每堆n个,每次能从某一堆的顶部拿一个糖果放入容量为5的背包中,若背包中有两个糖果种类相同,则可消除那两个糖果并加1分,问总最大得分
思路 —— 记忆化搜索
4堆糖果各自剩余的数量若固定,则在此状态下能拿到的最大分数也是固定的
因为假设已达到这样一个状态,则不管如何达到这样一个状态,背包中的糖果一定是相同的(顺序可能不同),毕竟能消除的已经消除了,才能达到这样一个状态
那么就记录这个状态的值,做记忆化搜索即可
#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 40 + 1;
int arr[4][N], ptr[4] = {0};
int dp[N][N][N][N];
int cnt[N];
inline void init(){
memset(dp, -1, sizeof(dp));
memset(cnt, 0, sizeof(cnt));
}
int dfs(int p, int n){
if(p >= 5) return 0;
if(dp[ptr[0]][ptr[1]][ptr[2]][ptr[3]] != -1) return dp[ptr[0]][ptr[1]][ptr[2]][ptr[3]];
int ans = 0;
for(int i = 0; i < 4; i++){
if(ptr[i] >= n) continue;
int val = arr[i][ptr[i]];
ptr[i]++;
cnt[val]++;
if(cnt[val] >= 2){
cnt[val] = 0;
ans = max(ans, dfs(p - 1, n) + 1);
cnt[val] = 2;
}else{
ans = max(ans, dfs(p + 1, n));
}
cnt[val]--;
ptr[i]--;
}
dp[ptr[0]][ptr[1]][ptr[2]][ptr[3]] = ans;
return ans;
}
int main(){
int n;
while(~scanf("%d", &n) && n){
init();
for(int j = 0; j < n; j++){
for(int i = 0; i < 4; i++){
scanf("%d", &arr[i][j]);
}
}
printf("%d\n", dfs(0, n));
}
}