知識總結
搜尋演算法
剪枝
剪枝是指在搜尋樹的構造過程中,對某些分支不必繼續探索,從而減少搜尋樹的大小,提高搜尋效率。
啟發式搜尋
啟發式搜尋是指根據某種啟發式函式對搜尋樹進行排序,使得搜尋樹中優先擴充套件那些有可能產生最優解的分支。
迭代加深搜尋
迭代加深搜尋是指在搜尋樹的構造過程中,每一步都對當前的搜尋樹進行擴充套件,直到搜尋樹中所有可行解都被找到為止。
A*搜尋演算法
A*搜尋演算法是一種啟發式搜尋演算法,它在搜尋樹的構造過程中,同時考慮了啟發式函式和路徑長度。
IDA*搜尋演算法
IDA*搜尋演算法是一種迭代加深搜尋演算法,它在搜尋樹的構造過程中,使用啟發式函式來對搜尋樹進行排序,並對那些分支的擴充套件進行限制,從而減少搜尋樹的大小。
題目
這些題目基本都是模擬
T1 螞蟻
題目描述
在二維平面座標軸裡面,有 N 只螞蟻,第 i 只螞蟻所在的點的座標是(xi, yi),座標都是整數。所有螞蟻的移動速度都相等,都是每秒移動 1 個單位。每隻螞蟻都有一個固定的移動方向,是如下 4 種方向之一,都是平行於座標軸的:
? N 表示向北(即朝上), 則 y 座標正方向。
? E 表示向東(即朝右), 則 x 座標正方向。
? S 表示向南(即向下), 則 y 座標負方向。
? W 表示向西(即向左), 則 x 座標負方向。
當 2 只或多隻螞蟻在某個時刻碰(不一定是整數時刻)撞到一起,那麼這些螞蟻都會立即消失。 例如螞蟻 A 的初始位置是(0, 0)且方向是向東,螞蟻 B 的初始位置是(1, 0)且方向是向西,那麼 0.5 秒後,兩隻螞蟻會在點(0.5, 0)處碰撞,兩隻螞蟻瞬間都消失。當所有的碰撞結束後,還有多少隻螞蟻存在?不管螞蟻最終移動到哪裡,只要沒有消失,都算是存在。
思路解析
由於螞蟻碰撞的地點座標可能是小數,因此讀入資料把座標值乘以 2,那麼碰撞後的點的座標肯定是整數。螞蟻數量較少,N<= 50,而且座標值範圍是【-2000,2000】,因此螞蟻碰撞的時刻不會超過 8000,因此可以逐一時刻的模擬螞蟻爬到了什麼位置,然後判斷如果在某個特定時刻有兩隻或多隻螞蟻爬到同一點,那麼這些螞蟻都會消失。可以用 active 陣列表示目前還有哪些螞蟻是活的,然後按照上面的模擬,螞蟻消失就設 active 為 false。但 8000 個時刻過後,再統計一遍,看還有多少隻螞蟻是活的,輸出答案。用 C++的 STL 庫減少程式碼量,當時時間複雜度會差些。本題還有更高效率的演算法,請讀者思考。如下程式的時間複雜度是:O(800050Log50)。
程式碼實現
#include <bits/stdc++.h>
using namespace std;
ifstream fin("ant.in");
ofstream fout("ant.out");
const int maxn = 60;
int n, type[256], dx[] = {0, 0, -1, 1}, dy[] = {1, -1, 0, 0};
string dir;
struct Tant {
int x, y;
} ant[maxn];
map<pair<int, int>, vector> Map;
bool active[maxn];
int main()
{
type['N'] = 0;
type['S'] = 1;
type['W'] = 2;
type['E'] = 3;
fin >> n >> dir;
for (int i = 1; i <= n; i++)
{
fin >> ant[i].x >> ant[i].y;
ant[i].x *= 2;
ant[i].y *= 2;
}
for (int i = 1; i <= n; i++)
active[i] = true;
for (int t = 1; t <= 8001; t++)
{
Map.clear();
for (int j = 1; j <= n; j++)
if (active[j])
{
int x = ant[j].x + t * dx[type[dir[j - 1]]];
int y = ant[j].y + t * dy[type[dir[j - 1]]];
Map[make_pair(x, y)].push_back(j);
}
map<pair<int, int>, vector>::iterator it;
for (it = Map.begin(); it != Map.end(); it++)
{
vector vec = it->second;
int s = vec.size();
if (s <= 1)
continue;
for (int k = 0; k < s; k++)
active[vec[k]] = false;
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
if (active[i])
ans++;
fout << ans << endl;
return 0;
}
T2 士兵訓練
題目描述
N 個士兵排成一隊進行軍事訓練,每個士兵的等級用 1...K 範圍內的數來表示,長官每隔 1 小時就隨便說出 M 個等級 a1,a2…am(1≤ai≤K,M 個等級中允許有重複),如果這 M 個等級組成的序列是排成一隊的 N 個士兵等級序列的子序列,那麼訓練繼續;否則訓練結束。長官想知道,M 至少為多少時,訓練才有可能結束。
例:士兵等級序列為 1 5 3 2 5 1 3 4 4 2 5 1 2 3,長官說出的等級序列為 5 4,那麼訓練繼續,如果長官說出的等級序列為 4 4 4,那麼訓練結束。
思路解析
解題分析:方法很簡單,只要從 1 掃到 n,一旦 tot 個等級都至少有一個了,就清空統計陣列,同時把 tot + 1,最終答案是 tot + 1。下面我們來證明這為什麼是最少的。透過上面的掃描,把士兵分成了 tot 個區域 A1、A2、A3……An。無論長官怎麼報,第 i 個等級必然處於 A1~Ai 當中,故最終至少需要 tot + 1 個等級才能使訓練結束。
時間複雜度:O(n)
空間複雜度:O(n)
程式碼實現
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, k, a[N], ans, cnt;
bool vis[N];
signed main() {
scanf("%lld%lld", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++) {
if (vis[a[i]]) {
continue;
}
cnt++;
vis[a[i]] = true;
if (cnt == k) {
cnt = 0;
ans++;
memset(vis, 0, sizeof(vis));
}
}
printf("%lld", ans + 1);
return 0;
}
T3 JLOI2010 世界盃租房
題目描述
南非世界盃組委會指定了在此期間可提供的一些旅館供球迷租賃,名為阿凡達的即是其中一所。因為阿凡達旅館房子的數目不超過 26,所以它們可以用 26 個大寫字母表示。
有一天,劉經理的電話響了,他接到了一個租賃房屋的請求,要求從 6 月 12 日晚起租到 6 月 19 日中午。於是他察看了預定表,但是並沒有發現一間房屋能夠直接滿足要求。比如房主可能因為一些私人原因需要留在自己的房子中,所以這個遊客不得不在其中的一間先住上幾天再搬到另一間住上幾天。他詳細檢查了預定表後,對旅客說:“我將你先在 B 安置 3 天,再將你安排到 F 去度過剩餘的旅途。”
你的目標是使得遊客從一間房屋搬到另一間房屋的次數最少。
注意在旅館的計費中,總是將某一天的晚上到第二天的中午視作一天。
思路解析
狀壓 dp
程式碼實現
#include <bits/stdc++.h>
using namespace std;
int f[110][30], d[110][30];
char a[110][30];
int n, m, s, t, i, j, k, ans, T;
void print(int s, int i, int j) {
if (i == t)
return;
if (d[i][j] != j) {
printf("%c: %d-%d\n", 'A' + j - 1, s, i + 1);
print(i + 1, i + 1, d[i][j]);
} else {
print(s, i + 1, j);
}
return;
}
int main() {
while (cin >> m >> n && n) {
for (i = 1; i <= m; i++)
scanf("%s", a[i] + 1);
cin >> s >> t;
memset(f, 0x3f, sizeof(f));
f[t][0] = 0;
for (i = t - 1; i >= s; i--)
for (j = 1; j <= n; j++)
if (a[i][j] == 'O')
for (k = 0; k <= n; k++)
if (f[i + 1][k] + (j != k) < f[i][j]) {
f[i][j] = f[i + 1][k] + (j != k);
d[i][j] = k;
}
ans = 0x3f3f3f3f;
for (i = 1; i <= n; i++)
if (f[s][i] < ans)
ans = f[s][i], j = i;
printf("Case %d:\n\n", ++T);
if (ans == 0x3f3f3f3f) {
printf("Not available\n");
} else
print(s, s, j);
puts("");
}
return 0;
}
T4 ZJOI2002Reading 閱覽室
題目描述
一個閱覽室每天都要接待大批讀者。閱覽室開門時間是 0,關門時間是 T。每位讀者的到達時間都不一樣,並且想要閱讀的刊物不超過 5 本。每位讀者心裡對自己想看的刊物都有一個排位,到達之後他會先去找自己最想看的刊物,如果找不到則去找其次想看的刊物。如果找不到任何他想看的刊物,他會開始等待,直到有一本以上的他想看的刊物被人放回原處。當然,他會先去拿其中自己最想看的刊物。當他看完某一本刊物後,就把它放回原處,接著去找自己沒看過的最想看的刊物。如此下去,直到看完所有他想看的刊物為止。矛盾出現在兩個人同時想要拿同一本刊物的時候。閱覽室為了避免讀者之間出現爭執,作了一個規定,讀者每次在開始等待時先去服務檯做一次登記。如果兩個人都同時想要一本刊物,那麼先登記的讀者將得到這本刊物。如果兩個人同時登記,那麼先到達閱覽室的讀者將得到刊物。沒得到的人就只能去找其他的刊物看。閱覽室關門時,所有讀者都將被強迫離開閱覽室,不再允許繼續閱讀。
現在閱覽室想做一個統計調查,你被要求寫一個程式來模擬這個過程計算出所有刊物被閱讀的總次數。當某個讀者開始閱讀某本刊物時,該刊物的被閱讀次數就加 1,無論這本刊物最後有沒有被讀完。
思路解析
- 列舉當前時刻 $t$
- 對於每個人,標記該時刻想要拿到的書
- 根據題目的要求判斷衝突情況
- 對書進行分配
程式碼實現
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5, K = 6;
struct reader {
bool f[K];
int num, k, ls, s[K], t[K];
bool operator<(const reader& A) const {
if (ls != A.ls) {
return ls < A.ls;
}
return num < A.num;
}
} a[N];
int T, n, ans, bk[N], cnt;
priority_queue<reader> q;
void init() {
scanf("%d%d", &T, &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i].num, &a[i].k);
a[i].ls = ++a[i].num;
for (int j = 1; j <= a[i].k; j++) {
scanf("%d%d", &a[i].s[j], &a[i].t[j]);
}
}
return;
}
signed main() {
init();
for (int t = 1; t <= T; t++) {
cnt = 0;
for (int i = 1; i <= n; i++) {
q.push(a[i]);
}
while (!q.empty()) {
reader cur = q.top();
a[n-cnt] = cur;
q.pop();
cnt++;
}
for (int i = 1; i <= n; i++) {
if (a[i].ls <= t && a[i].num <= t) {
for (int w = 1; w <= a[i].k; w++) {
if ((bk[a[i].s[w]] <= t) && (!a[i].f[w])) {
ans++;
a[i].f[w] = true;
a[i].ls = bk[a[i].s[w]] = t + a[i].t[w];
break;
}
}
}
}
}
printf("%d", ans);
return 0;
}
T5 NOIP2004-S-4-蟲食算
題目描述
https://www.luogu.com.cn/problem/P1092
思路解析
首先 我們倒序儲存 以方便判斷是否可行時做的 A+B=C
先想一個暴力:列舉這 n 個字母分別對應哪個數字 最後來個判斷 這樣應該是過三個點 30
接著 進行剪枝最佳化
- 首先 定義 flag 若有可行解(一定有且只有一個)時 flag=1
dfs 時 先看 flag 是否為 1 若 flag 為 1 則有找到可行解 其後的 dfs 完全不需要。(記憶化) - 我們不必等全列舉完這 n 個字母再進行判斷 可以從低位往高位列舉 每完成一位後進行判斷 設上一位的進位為 t 判斷是否 (A[i]+B[i]+t)%n==c 若不是 return 0 若是 接著 i++
- 當列舉當前字母要對應哪一個數字時 不要正著列舉 要倒著列舉 這一步可以從 80 分到 AC!!!!!!!(為了儘早用掉大數,防止最高位發生進位
程式碼實現
#include <bits/stdc++.h>
#pragma GCC optimize(3) // 手開O3最佳化
#define N 30
using namespace std;
bool flag, used[N];
int n, a[5][N], num[N];
char s[5][N];
bool check() {
int add = 0;
for (int i = n; i >= 1; i--) {
int A, B, C;
A = num[a[1][i]], B = num[a[2][i]], C = num[a[3][i]];
if ((A + B + add) % n != C)
return false;
add = (A + B + add) / n;
}
return true;
}
bool Prune() {
if (num[a[1][1]] + num[a[2][1]] >= n)
return true;
for (int i = n - 1; i >= 0; i--) {
int A = num[a[1][i]], B = num[a[2][i]], C = num[a[3][i]];
if (A == -1 || B == -1 || C == -1)
continue;
if ((A + B) % n != C && (A + B + 1) % n != C)
return true;
}
return false;
}
void Print() {
for (int i = 1; i <= n; i++)
printf("%d ", num[i]);
return;
}
bool Check() {
for (int i = 1; i <= n; i++)
if (num[i] == -1)
return false;
return true;
}
void dfs(int x, int y, int t) {
if (flag)
return;
if (Prune())
return;
if (Check()) {
if (check()) {
Print();
flag = true;
}
return;
}
if (num[a[y][x]] == -1) {
for (int i = n - 1; i >= 0; i--) {
if (!used[i]) {
if (y != 3) {
num[a[y][x]] = i;
used[i] = true;
dfs(x, y + 1, t);
used[i] = false;
num[a[y][x]] = -1;
} else {
int z = num[a[1][x]] + num[a[2][x]] + t;
if (z % n != i)
continue;
used[i] = true;
num[a[y][x]] = i;
dfs(x - 1, 1, z / n);
used[i] = false;
num[a[y][x]] = -1;
}
}
}
} else {
if (y != 3)
dfs(x, y + 1, t);
else {
int z = num[a[1][x]] + num[a[2][x]] + t;
if (Prune())
return;
dfs(x - 1, 1, z / n);
}
}
return;
}
void Getid() {
for (int i = 1; i <= 3; i++)
for (int j = 0; j < n; j++)
a[i][j + 1] = s[i][j] - 'A' + 1;
return;
}
int main() {
scanf("%d", &n);
scanf("%s%s%s", s[1], s[2], s[3]);
Getid();
memset(num, -1, sizeof(num));
dfs(n, 1, 0);
return 0;
}
T6 CQOI2012 模擬工廠
題目描述
https://www.luogu.com.cn/problem/P3161
思路解析
n 只有 15,完全可以 2^n 暴力列舉打算完成哪些訂單
問題轉化為 如何安排每天的策略才能滿足列舉的所有訂單?
每天的決策有兩種:增加生產力、生產商品
顯然我們希望第一種操作儘可能排在第二種操作之前
但由於前期的訂單要求
可能存在先大量生產產品以滿足較近的訂單需求 然後再提高生產力生產的情況
簡單的貪心似乎都是錯的
考慮單個訂單的情況
顯然所有提高生產力的操作排在生產操作之前
現在要求滿足全部訂單需求,於是可以從當前時刻開始,帶上當前剩餘的產品數量
逐個考慮 ii+1,i+1i+2,i+2~i+3 等過程中是在什麼時候開始生產的。
但這樣的問題在於,可選的生產時間是一個範圍, 我們並不能確定哪個值對後續的訂單最合適。
所以列舉後續所有訂單,考慮在滿足後續所有訂單的基礎上,最多可以花多少時間提高生產力。
取 min 即為 在保證後續訂單可以滿足的前提下 目前能提升的生產力最大值
總複雜度 O(2^n * n^2)
程式碼實現
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 20;
struct P {
int t, c, p;
friend bool operator<(P a, P b) {
return a.t < b.t;
}
} S[N], a[N];
int n;
int tot;
ll res;
ll js(ll Make, ll Time, ll Need) {
ll a = 1;
ll b = Make - Time;
ll c = Need - Make * Time;
ll derta = b * b - 4 * a * c;
if (derta < 0)
return -1;
return floor((-b + sqrt(derta)) / 2 / a);
}
void solve(int Now) {
tot = 0;
ll num = 0;
for (int i = 1; i <= n; i++)
if (Now & (1 << i - 1))
S[++tot] = a[i], num += a[i].p;
ll Make = 1;
ll Have = 0;
for (int i = 1; i <= tot; i++) {
ll t = S[i].t - S[i - 1].t;
ll sum = 0;
for (int j = i; j <= tot; j++) {
sum += S[j].c;
if (sum > Have)
t = min(t, js(Make, S[j].t - S[i - 1].t, sum - Have));
}
if (t < 0)
return;
Make += t;
Have += Make * (S[i].t - S[i - 1].t - t) - S[i].c;
}
res = max(res, num);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d%d%d", &a[i].t, &a[i].c, &a[i].p);
sort(a + 1, a + 1 + n);
for (int i = 0; i < (1 << n); i++)
solve(i);
printf("%lld\n", res);
return 0;
}
T7 NOIP2011-S-DAY1-3-Mayan 遊戲
題目描述
https://www.luogu.com.cn/problem/P1312
思路解析
首先,這題一看肯定是個暴力搜尋,因為題目中的變換/操作略複雜,所以我們儘量分開寫函式解決每塊小操作
注:本文中的地圖變數 [ i ][ j ] 均代表自下而上第 i 行,自左而右第 j 列
首先把圖用二維陣列表示出來,結構體,圖方便
struct node{int pad[10][10];}
題目中總共滿足三個操作: 1.掉落
當一個方塊下方沒有方塊時,會往下掉,直到到底或下方有方塊
實現[x][y]方塊的掉落
void drop(int x, int y, node& u) { // 同步修改掉,用地址,下同
int r = x; // 用來記錄當前掉落塊,接下來有用
if (u.pad[x][y] == 0)
return; // 如果是個“空塊”,不做了
bool flag = 0; // 用來判定這個塊有沒有掉落
while (u.pad[x - 1][y] == 0 && x > 0) // 下方為空且不是最底層
{
flag = 1;
swap(u.pad[x - 1][y], u.pad[x][y]); // 掉落即交換
x--; // 自己的位置降低
}
if (flag)
drop(r + 1, y, u); // 如果自己掉了,自己上方也掉
}
2.橫向拖動/交換
每個方塊可以向左/右拖動,即與左/右交換方塊,於是我們可以用簡單的 swap 搞定
void move(int x, int y, int d, node& u) {
swap(u.pad[x][y], u.pad[x][y + d]); // d 是方向即 1 或-1,直接拿來用
drop(x + 1, y, u);
drop(x, y + d, u); // 換來的可能是 0 ,換出去的一定不是零,會在寬搜時候保證
// 我的上方方塊可能會掉落,我過去的地方可能會使我掉落
}
- 消除
橫向或縱向的三個連續相同塊可以消掉,且可以共用塊
因為有了共用塊的性質,所以肯定不能無腦找到就消除,不妨打標記
void del(node& u) {
bool need = 0;
memset(vis, 0, sizeof(vis)); // vis陣列用來打標記
for (int i = 0; i < 7; i++) // 從左下往右上搜
for (int j = 0; j < 5; j++) // 只要考慮向右和向上的連續塊即可
if (u.pad[i][j] != 0) { // 存在方塊
if (u.pad[i + 1][j] == u.pad[i][j] && u.pad[i + 2][j] == u.pad[i][j]) // 縱向三個相連
vis[i][j] = vis[i + 1][j] = vis[i + 2][j] = 1;
if (u.pad[i][j + 1] == u.pad[i][j] && u.pad[i][j + 2] == u.pad[i][j]) // 橫向三個相連
vis[i][j] = vis[i][j + 1] = vis[i][j + 2] = 1;
}
for (int i = 6; i >= 0; i--) // 消除的時候從上往下,可以同步drop操作
for (int j = 0; j < 5; j++)
if (vis[i][j]) { // 被標記了要消除
u.pad[i][j] = 0; // 消掉
drop(i + 1, j, u); // 上方的方塊下落
need = 1;
}
if (need)
del(u);
}
所以我們用一個 need 來判定有沒有消除,如果有消除,那麼再掃一遍,看在掉落後有沒有新的可以消除的塊
程式碼實現
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
struct G {
int maps[10][10];
};
int n;
G now;
G last[10];
int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
struct Info {
int x, y;
int flag;
Info(int dx, int dy, int flag) {
x = dx;
y = dy;
this->flag = flag;
}
Info() {
}
};
Info steps[10];
int cnt = 0;
bool visited[10][10];
bool checkp(int x, int y) {
if (x >= 0 && y >= 0 && x < 5 && y < 7 && !visited[x][y] && now.maps[x][y])
return true;
return false;
}
bool marks[10][10];
bool updates[10][10];
int maxx, maxy;
int minx, miny;
void dfsupdate(int x, int y, int dire, int d) {
visited[x][y] = true;
maxx = max(maxx, x);
minx = min(minx, x);
maxy = max(maxy, y);
miny = min(miny, y);
if (dire == -1) {
for (int i = 0; i < 4; i++) {
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if (checkp(dx, dy))
if (now.maps[x][y] == now.maps[dx][dy])
dfsupdate(dx, dy, i, d + 1);
}
} else {
int dx = x + dir[dire][0];
int dy = y + dir[dire][1];
if (checkp(dx, dy))
if (now.maps[x][y] == now.maps[dx][dy])
dfsupdate(dx, dy, dire, d + 1);
}
visited[x][y] = false;
}
bool update(int x, int y) {
maxx = maxy = 0;
minx = miny = 100;
dfsupdate(x, y, -1, 1);
bool flag = false;
if (maxx - minx + 1 >= 3) {
for (int i = minx; i <= maxx; i++) {
marks[i][y] = true;
updates[i][y] = true;
}
flag = true;
}
if (maxy - miny + 1 >= 3) {
for (int i = miny; i <= maxy; i++) {
marks[x][i] = true;
updates[x][i] = true;
}
flag = true;
}
return flag;
}
void updateall() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 7; j++)
if (marks[i][j])
now.maps[i][j] = 0, marks[i][j] = false;
for (int j = 0; j < 7; j++)
if (!now.maps[i][j]) {
int ind = 0;
for (int k = j + 1; k < 7; k++)
if (now.maps[i][k])
now.maps[i][j + ind] = now.maps[i][k], ind++, now.maps[i][k] = 0;
break;
}
}
}
bool check() {
for (int i = 0; i < 5; i++)
if (now.maps[i][0] != 0)
return false;
return true;
}
bool move(int x, int y, int k) {
int targetx, targety;
if (k) {
if (x == 4)
return false;
else {
int t = now.maps[x][y];
now.maps[x][y] = now.maps[x + 1][y];
now.maps[x + 1][y] = t;
steps[cnt++] = Info(x, y, 1);
targetx = x + 1;
targety = y;
if (!now.maps[x][y]) {
for (int i = x; i <= x + 1; i++)
for (int j = 0; j < 7; j++)
if (!now.maps[i][j]) {
int ind = 0;
for (int k = j + 1; k < 7; k++)
if (now.maps[i][k]) {
if (i == x + 1 && y == k)
targety = j + ind;
now.maps[i][j + ind] = now.maps[i][k], ind++, now.maps[i][k] = 0;
}
break;
}
}
}
} else {
if (x == 0)
return false;
else {
int t = now.maps[x][y];
now.maps[x][y] = now.maps[x - 1][y];
now.maps[x - 1][y] = t;
steps[cnt++] = Info(x, y, -1);
targetx = x - 1;
targety = y;
if (!now.maps[x][y]) {
for (int i = x - 1; i <= x; i++)
for (int j = 0; j < 7; j++)
if (!now.maps[i][j]) {
int ind = 0;
for (int k = j + 1; k < 7; k++)
if (now.maps[i][k]) {
if (i == x - 1 && y == k)
targety = j + ind;
now.maps[i][j + ind] = now.maps[i][k], ind++, now.maps[i][k] = 0;
}
break;
}
}
}
}
bool flag = false;
if (update(x, y) | update(targetx, targety))
flag = true;
while (flag) {
updateall();
flag = false;
for (int i = 0; i < 5; i++)
for (int j = 0; j < 7 && now.maps[i][j] != 0; j++)
if (updates[i][j]) {
updates[i][j] = false;
if (update(i, j))
flag = true;
}
}
return true;
}
void dfs(int d, int x, int y, int k) {
if (d > n)
return;
last[d] = now;
if (move(x, y, k)) {
if (check()) {
if (d == n) {
for (int i = 0; i < cnt; i++)
cout << steps[i].x << " " << steps[i].y << " " << steps[i].flag << endl;
exit(0);
} else {
bool flag = false;
if ((d - n) % 2 == 0) {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 7; j++)
if (last[1].maps[i][j] && last[1].maps[i][j + 1]) {
for (int t = 0; t < (d - n); t++)
cout << i << " " << j << " 1" << endl;
break;
flag = true;
}
if (flag)
break;
}
for (int i = 0; i < cnt; i++)
cout << steps[i].x << " " << steps[i].y << " " << steps[i].flag << endl;
exit(0);
}
}
}
for (int i = 0; i < 5; i++)
for (int j = 0; j < 7 && now.maps[i][j] != 0; j++)
if (i != 0 && !now.maps[i - 1][j])
for (int l = 1; l >= 0; l--)
dfs(d + 1, i, j, l);
else
dfs(d + 1, i, j, 1);
cnt--;
}
now = last[d];
}
int main() {
cin >> n;
for (int i = 0; i < 5; i++) {
int x;
int ind = 0;
while (cin >> x) {
if (x == 0)
break;
now.maps[i][ind] = x;
ind++;
}
}
for (int i = 0; i < 5; i++)
for (int j = 0; j < 7 && now.maps[i][j] != 0; j++)
if (i != 0 && !now.maps[i - 1][j])
for (int k = 1; k >= 0; k--)
dfs(1, i, j, k);
else
dfs(1, i, j, 1);
cout << -1 << endl;
}
T8 NOIP2009-S-4-靶形數獨
題目描述
https://www.luogu.com.cn/problem/P1074
思路解析
下面主要講填數獨:
將數獨劃分為 9 個宮,如何知道一個座標在哪個宮?
1。確定宮號
我們需要這個函式解決:
inline int get_id(int i, int j) {
return (i - 1) / 3 * 3 + 1 + (j - 1) / 3;
}
i,j 是座標,返回的就是所在的宮號。我們當然可以打表預先儲存好宮號,這樣還可以最佳化時間複雜度。所以上面這個函式可以廢掉了
定義二維陣列:
const int gong[10][10] =
{
{1, 1, 1, 2, 2, 2, 3, 3, 3},
{1, 1, 1, 2, 2, 2, 3, 3, 3},
{1, 1, 1, 2, 2, 2, 3, 3, 3},
{4, 4, 4, 5, 5, 5, 6, 6, 6},
{4, 4, 4, 5, 5, 5, 6, 6, 6},
{4, 4, 4, 5, 5, 5, 6, 6, 6},
{7, 7, 7, 8, 8, 8, 9, 9, 9},
{7, 7, 7, 8, 8, 8, 9, 9, 9},
{7, 7, 7, 8, 8, 8, 9, 9, 9},
{7, 7, 7, 8, 8, 8, 9, 9, 9},
};
這樣也是可以的。
2。設計資料結構
sdk 陣列,用來儲存數獨
area 陣列,用來標記宮
row 陣列,用來標記行
col 陣列,用來標記列
row_cnt 陣列,用來記錄行元素數
col_cnt 陣列,用來記錄列元素數
score 陣列,用來表示分值
const int score[10][10] =
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 6, 6, 6, 6, 6, 6, 6, 6, 6},
{0, 6, 7, 7, 7, 7, 7, 7, 7, 6},
{0, 6, 7, 8, 8, 8, 8, 8, 7, 6},
{0, 6, 7, 8, 9, 9, 9, 8, 7, 6},
{0, 6, 7, 8, 9, 10, 9, 8, 7, 6},
{0, 6, 7, 8, 9, 9, 9, 8, 7, 6},
{0, 6, 7, 8, 8, 8, 8, 8, 7, 6},
{0, 6, 7, 7, 7, 7, 7, 7, 7, 6},
{0, 6, 6, 6, 6, 6, 6, 6, 6, 6},
}; // score list
我們還需要設計這樣一個函式
inline int get_value() {
int ret = 0;
rep(i, 1, 9, 1)
rep(j, 1, 9, 1)
ret += score[i][j] * sdk[i][j];
return ret;
}
用來每次計算總價值
返回總價值,跟 ans 作比較,選最大值
3。求解數獨
可以用舞蹈鏈解決,但是這道題 dfs 足以
void dfs(int r, int c, int cpl) // r row, c column
{
if (cpl == 81) // 81個代表填滿了,填滿了一定保證是正確的填法
{
ans = max(ans, get_value()); // 填滿了之後,比較一次最大值
return;
}
rep(k, 1, 9, 1) // 這裡改成for(register int k = 1; k <= 9; k++)之後同理
{
if (row[r][k] || col[c][k] || area[get_id(r, c)][k])
continue; // 判斷列不能重複,行不能重複,宮不能重複
row[r][k] = true; // 既然不重複,那現在就重複了
col[c][k] = true; // 同上
area[get_id(r, c)][k] = true; // 同上
row_cnt[r]++; // 當前行中的元素數++
col_cnt[c]++; // 當前列中的元素數++
sdk[r][c] = k; // 填進去
// 尋找下一個dfs的點
int tmpr = -1, nxt_r = 0, tmpc = -1, nxt_c = 0;
// nxt_r是下一個行,nxt_c是下一個列
rep(i, 1, 9, 1) if (row_cnt[i] > tmpr && row_cnt[i] < 9)
tmpr = row_cnt[i],
nxt_r = i;
rep(j, 1, 9, 1) if (col_cnt[j] > tmpc && (!sdk[nxt_r][j]))
tmpc = col_cnt[j],
nxt_c = j;
dfs(nxt_r, nxt_c, cpl + 1);
// 回溯解除標記↓
row[r][k] = false;
col[c][k] = false;
area[get_id(r, c)][k] = false;
row_cnt[r]--;
col_cnt[c]--;
sdk[r][c] = 0;
}
}
程式碼實現
#include <bits/stdc++.h>
using namespace std;
const int N = 10, inf = 0x3f3f3f3f;
struct f {
int rank, sum;
} cou[N];
bool operator<(const f& a, const f& b) {
return a.sum < b.sum;
}
bool r[10][10], c[10][10], g[10][10];
int a[10][10], s[100][4], u, ok, ans = -1, filled;
int palace(int y) {
if (y <= 3) {
return 1;
} else if (y <= 6) {
return 2;
} else {
return 3;
}
}
int which(int x, int y) {
if (x <= 3) {
return palace(y);
} else if (x <= 6) {
return 3 + palace(y);
} else {
return 6 + palace(y);
}
}
int point(int x, int y) {
if (x == 1 || y == 1 || x == 9 || y == 9) {
return 6;
}
if (x == 2 || y == 2 || x == 8 || y == 8) {
return 7;
}
if (x == 3 || y == 3 || x == 7 || y == 7) {
return 8;
}
if (x == 4 || y == 4 || x == 6 || y == 6) {
return 9;
}
if (x == 5 && y == 5) {
return 10;
}
}
inline void dfs(int p, int score) {
if (p == u) {
ans = max(ans, score);
return;
}
for (int i = 1; i <= 9; i++) {
if (r[s[p][0]][i] || c[s[p][1]][i] || g[s[p][3]][i]) {
continue;
}
r[s[p][0]][i] = c[s[p][1]][i] = g[s[p][3]][i] = true;
dfs(p + 1, score + (s[p][2] * i));
r[s[p][0]][i] = c[s[p][1]][i] = g[s[p][3]][i] = false;
}
return;
}
inline void init() {
for (int i = 1; i <= 9; i++) {
cou[i].rank = i;
}
return;
}
signed main() {
init();
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
scanf("%d", &a[i][j]);
if (a[i][j] > 0) {
r[i][a[i][j]] = c[j][a[i][j]] = g[which(i, j)][a[i][j]] = true;
filled += a[i][j] * point(i, j);
} else {
cou[i].sum++;
}
}
}
sort(cou + 1, cou + 10);
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (a[cou[i].rank][j] == 0) {
s[u][0] = cou[i].rank;
s[u][1] = j;
s[u][2] = point(cou[i].rank, j);
s[u++][3] = which(cou[i].rank, j);
}
}
}
dfs(0, filled);
printf("%d\n", ans);
return 0;
}