Problem - D - Codeforces
用記憶化搜尋過的,然而DP能快300ms
記憶化搜尋 | \(\texttt{set}\)模擬
核心思路一致,都是透過定義一個狀態,即在第t次到達第now點來去重剪枝
記憶化搜尋
int n, m, x;
std::vector<std::pair<int, char>> step;
std::set<int> S;
int getClock(int x, int dis) {
dis %= n;
if (x + dis > n) return (x + dis) % n;
else return x + dis;
}
int getAntiClock(int x, int dis) {
dis %= n;
if (x - dis < 1) return n - (dis - (x - 1)) + 1;
else return x - dis;
}
bool vis[1010][1010];
void dfs(int now, int t) {
if (t == m) {S.insert(now); return ;}
if (vis[now][t]) {return ;}
vis[now][t] = true;
auto[dis, opt] = step[t];
if (opt == '?' or opt == '0') {
dfs(getClock(now, dis), t + 1);
}
if (opt == '?' or opt == '1') {
dfs(getAntiClock(now, dis), t + 1);
}
}
\(\texttt{set}\)模擬
這裡STD還利用了幾個取模trick
首先是把 \(x \mod{n}\) 轉化成 \((x - 1)\mod {n} + 1\) ,防止 \(x = n\) 時想要得到 \(n\) 卻得到 \(0\)。
首先是逆時針可能是負數,所以最後再加上 \(n\) 防止取模失效。
//本質上也是記憶化搜尋
//透過set自動把當前位置且次數都相同的狀態去重了
void solve() {
int n, m, x;
std::cin >> n >> m >> x;
std::set<int> S[2];
bool cur(false);
S[cur].insert(x);
while (m--) {
int d; char opt;
std::cin >> d >> opt;
while (!S[cur].empty()) {
int now(*S[cur].begin());
S[cur].erase(now);
if (opt == '?' or opt == '0') {
S[cur ^ 1].insert((now + d - 1) % n + 1);
}
if (opt == '?' or opt == '1') {
S[cur ^ 1].insert((now - d + n - 1) % n + 1);
}
}
cur ^= 1;
}
std::cout << sz(S[cur]) << '\n';
for (auto& x : S[cur]) std::cout << x << ' ';
std::cout << '\n';
}
可達性DP
即定義狀態 \(dp_{i, j}\) 為第 \(i\) 次操作第 \(j\) 點的可達性。
轉移方程很直接
\[dp_{i, j} = dp_{i - 1, (j + d - 1)\mod n + 1}(opt = \text{?} \| opt = \text{0})
\]
\[dp_{i, j} = dp_{i - 1, (j - d + n - 1)\mod n + 1}(opt = \text{?} \| opt = \text{1})
\]
原始寫法
因為該題沒有卡空間,所以能過
void solve() {
int n, m, x;
std::cin >> n >> m >> x;
std::vector dp(m + 1, std::vector<short>(n + 1));
dp[0][x] = 1;
for (int i = 1; i <= m; i++) {
int d; char opt;
std::cin >> d >> opt;
if (opt == '?' or opt == '0') {
for (int j = 1; j <= n; j++) {
dp[i][j] |= dp[i - 1][(j - d + n - 1) % n + 1];
}
}
if (opt == '?' or opt == '1') {
for (int j = 1; j <= n; j++) {
dp[i][j] |= dp[i - 1][(j + d - 1) % n + 1];
}
}
}
std::cout << std::count(all(dp[m]), 1) << '\n';
for (int i = 0; i <= n; i++) if (dp[m][i] == 1) {
std::cout << i << ' ';
}
std::cout << '\n';
}
最佳化空間
t寶和jls都是進一步最佳化了空間,因為該狀態只會從上一步轉移而來,所以完全可以省去第一維
void solve() {
int n, m, x;
std::cin >> n >> m >> x;
x--;
std::vector<bool> dp(n);
dp[x] = true;
for (int i = 0; i < m; i++) {
int d; char opt;
std::cin >> d >> opt;
std::vector<bool> newDp(n);
for (int j = 0; j < n; j++) if(dp[j]) {//從0開始,直接避免了取模會為0的問題
if (opt != '1') {
newDp[(j + d) % n] = true;
}
if (opt != '0') {
newDp[(j - d + n) % n] = true;
}
}
dp = newDp;
}
std::cout << std::count(all(dp), 1) << '\n';
for (int i = 0; i < n; i++) if (dp[i]) {
std::cout << i + 1 << ' ';
}
std::cout << '\n';
}
另一道題
Problem - E - Codeforces
依然可以用可達性DP。
定義 \(dp_i\) 為 \([1, i]\) 區間是否合法
假設 \(a_i\) 就是表示區間長度的值,則透過之前合法的情況遞推
這裡取 \(i - 1\) 是因為 \(i\) 時表示長度的那個數的下標,不用算進去
如果他在他表示區間的左邊,則 \([1, i + a_i]\) 的合法條件是 \([1, i - 1]\) 合法
如果他在他表示區間的右邊,則 \([1, i]\) 的合法條件是 \([1, i - 1 - a_i]\) 合法
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++) std::cin >> a[i];
std::vector<bool> dp(n + 1);
dp[0] = true;
for (int i = 1; i <= n; i++) {
if (i + a[i] <= n and dp[i - 1]) {
dp[i + a[i]] = true;
}
if (i - 1 - a[i] >= 0 and dp[i - 1 - a[i]]) {
dp[i] = true;
}
}
dp[n] ? YES : NO;
}