T1001 超維攻堅(HDU 7469)
三維凸包,不會。
T1002 黑白邊遊戲(HDU 7470)
顯然這道題沒有一個固定的最優策略,所以只能 \(\text{dp}\) 決策。
可以倒著做,設 \(f_{i, S}\) 表示從後往前進行了第 \(i \sim m\) 輪,第 \(i\) 輪結束後(在原始意義下是開始前)黑邊集合為 \(S\),雙方使用最優策略時先手與後手的分差,\(g_{S, a, b}\) 表示黑邊集合為 \(S\),一條黑邊/白邊長度分別為 \(a, b\) 時,可以獲得的收益(就是兩兩最短路之和)。
轉移考慮列舉 \(T\),需要滿足 \(S\) 可翻轉一條邊的顏色到達 \(T\),那麼 \(f_{i, S} = \max \{ g_{a_i, b_i, T} - f_{i + 1, T} \}\)。注意到 \(S\) 是 \(O(2^{\frac{n(n - 1)}{2}})\) 的,無法接受。
注意到這題點與點之間是不區分的,我們可以將本質不同的圖全部搜出來,建立狀態自動機,在自動機上跑上述 \(\text{dp}\)(只需將 \(S\) 換成自動機上的結點即可),我們猜測自動機上的結點不會太多,實際上,在 \(n = 8\) 時只有 \(12346\) 個結點。
現在考慮如何建出自動機,也就是找出所有本質不同的圖(現在我們預設刪掉所有白邊,那麼我們只考慮有邊和無邊),並在恰好相差一條邊的圖之間連邊。
對於一張圖 \(G\),我們欽定它的最小表示為,對於所有結點,計算它的度數 \(d\),和所有鄰居度陣列成的可重集 \(d'\),對於所有點按二元組 \((d, d')\) 從小到大排序,然後按照 \((d, d')\) 劃分等價類,對於同一個等價類中的點,我們直接階乘列舉全排列,最後考慮重標號後的鄰接矩陣,我們取字典序最小的鄰接矩陣作為圖的最小表示。然後我們認為兩張圖 \(G_0, G_1\) 同構,當且僅當他們的最小表示相等。
知道如何判斷圖的同構後,我們對於所有圖按照 \(|E|\) 分層,容易發現只有相鄰層之間有連邊,所以考慮從當前層 \(|E| = e\) 轉移到下一層 \(|E| = e + 1\),列舉當前層的一張圖,在它上面任加一條邊,然後判斷一下這張新圖是否已經有了即可。
複雜度可能不太好分析,但是我們有自動機上點數最大為 \(12346\),轉移數最大為 \(172844\)。
Code
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <map>
#include <unordered_set>
#include <chrono>
#include <array>
using namespace std;
using uLL = unsigned long long;
uLL Get_time () {
return chrono::steady_clock::now().time_since_epoch().count();
}
namespace State_automaton {
int n;
struct G {
struct State {
uLL es;
bool Get (int i, int j) {
return ((es >> (i << 3)) >> j) & 1;
}
void Add (int i, int j) {
es |= (1ull << (((i << 3) | j)));
es |= (1ull << (((j << 3) | i)));
}
State () : es(0) {}
State (uLL _es) : es(_es) {}
void Rebuild () {
vector<int> deg(n);
vector<uLL> cdeg(n);
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (Get(i, j)) ++deg[i], ++deg[j];
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (Get(i, j)) {
cdeg[i] += 1ull << (deg[j] * 3);
}
}
}
auto cmp = [&](int i, int j) -> bool {
return deg[i] < deg[j] || deg[i] == deg[j] && cdeg[i] < cdeg[j];
};
vector<int> p(n);
iota(p.begin(), p.end(), 0);
sort(p.begin(), p.end(), cmp);
vector<int> rk(n);
rk[0] = 0;
for (int i = 1; i < n; ++i) {
rk[p[i]] = rk[p[i - 1]] + cmp(p[i - 1], p[i]);
}
int tot = rk[p[n - 1]] + 1;
vector<vector<int>> vec(tot);
for (int i = 0; i < n; ++i) {
vec[rk[i]].push_back(i);
}
uLL mi = -1;
auto Dfs = [&](auto &self, int x) -> void {
if (x == tot) {
vector<int> per;
for (int i = 0; i < tot; ++i) {
for (auto j : vec[i]) {
per.push_back(j);
}
}
uLL news = 0;
for (int i = n - 1; ~i; --i) {
for (int j = n - 1; j > i; --j) {
if (Get(per[i], per[j])) {
news |= (1ull << (((i << 3) | j)));
news |= (1ull << (((j << 3) | i)));
}
}
}
mi = min(mi, news);
return;
}
sort(vec[x].begin(), vec[x].end());
do {
self(self, x + 1);
} while (next_permutation(vec[x].begin(), vec[x].end()));
};
Dfs(Dfs, 0);
es = mi;
}
};
int tot;
vector<unordered_set<int>> e;
vector<State> s;
vector<array<array<int, 5>, 5>> g;
int New_Node (State a) {
e.push_back(unordered_set<int>());
s.push_back(a);
g.push_back({});
vector<vector<int>> dis(n, vector<int>(n));
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
g.back()[i][j] = 0;
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
dis[x][y] = (x == y ? 0 : (a.Get(x, y) ? i + 1 : j + 1));
}
}
for (int z = 0; z < n; ++z) {
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
dis[x][y] = min(dis[x][y], dis[x][z] + dis[z][y]);
}
}
}
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
g.back()[i][j] += dis[x][y];
}
}
}
}
return tot++;
}
void Add_edge (int u, int v) {
e[u].insert(v);
e[v].insert(u);
}
void Init () {
vector<pair<State, int>> now{{State(), New_Node(State())}};
for (int k = 1; k <= n * (n - 1) / 2; ++k) {
map<uLL, int> p;
for (auto i : now) {
State s = i.first;
int id = i.second;
for (int x = 0; x < n; ++x) {
for (int y = x + 1; y < n; ++y) {
if (!s.Get(x, y)) {
State t = s;
t.Add(x, y);
t.Rebuild();
auto it = p.find(t.es);
if (it != p.end()) {
Add_edge(id, it->second);
}
else {
it = p.insert({t.es, New_Node(t)}).first;
Add_edge(id, it->second);
}
}
}
}
}
now.clear();
for (auto i : p) {
now.push_back(pair<State, int>({State({i.first}), i.second}));
}
}
}
} g[7];
void Init () {
for (int i = 0; i < 7; ++i) {
n = i + 2, g[i].Init();
}
}
}
const int M = 305, S = 12346;
const int Inf = 1e9;
int n, m;
int f[M][S];
int a[M], b[M];
int main () {
cin.tie(0)->sync_with_stdio(0);
uLL st0 = Get_time();
State_automaton::Init();
int T;
cin >> T;
while (T--) {
cin >> n >> m;
auto &mg = State_automaton::g[n - 2];
for (int i = 1; i <= m; ++i) {
cin >> a[i] >> b[i];
--a[i], --b[i];
}
for (int i = 0; i < mg.tot; ++i) {
f[m + 1][i] = 0;
}
for (int i = m; i; --i) {
for (int s = 0; s < mg.tot; ++s) {
f[i][s] = -Inf;
for (auto t : mg.e[s]) {
f[i][s] = max(f[i][s], mg.g[t][a[i]][b[i]] - f[i + 1][t]);
}
}
}
cout << f[1][mg.tot - 1] << '\n';
}
uLL ed0 = Get_time();
// cerr << "Time0 = " << (ed0 - st0) / int(1e6) << '\n';
return 0;
}
T1003 最優 K 子段(HDU 7471)
首先可以二分,轉化成判斷是否存在不相交的 \(k\) 個長度為質數的子段,滿足這 \(k\) 段的和都 \(\ge x\)。
考慮貪心,每次我們在合法的子段中選一個最靠左的肯定不劣。所以從左往右掃一遍,相當於每次將 \(r \gets r + 1\),你需要判斷是否存在 \(l \in [minl, r]\) 使得子段 \([l, r]\) 合法。有 \(minl\) 的限制是因為不能和之前的子段重合。
做字首和 \(s_i = \sum\limits_{k = 1}^{i} a_k\),那麼一個子段中所有數的和可以表示為 \(s_r - s_{l - 1}\),容易發現如果沒有子段長度為質數的限制是好做的,因為我們固定右端點 \(r\) 後,必定會選使得 \(s_{l - 1}\) 最小的 \(l\)。但加入質數的限制後這樣可能會使 \([l, r]\) 的長度不為質數,所以只記一個決策點肯定是不行的。
考慮用 \(\text{set}\) 按照 \(s_{l - 1}\) 從小到大維護所有決策點 \(l\),查詢一個 \(r\) 時在 \(\text{set}\) 上暴力跳,由於素數密度,期望跳 \(O(\log n)\) 次就可以跳到一個質數。注意這個 \(\log\) 是素數密度,而不是 \(s\) 的單調棧期望長度,後者是假的。
時間複雜度 \(O(n \log n \log V)\)。
Code
#include <iostream>
#include <vector>
#include <set>
using namespace std;
using Pii = pair<int, int>;
const int N = 2e5 + 5;
const int Inf = 1e9;
int n, k;
int a[N], pre[N];
bool is_prime[N];
vector<int> prime;
bool check (int x) {
set<Pii> s;
int cnt = 0;
for (int i = 1; i <= n; ++i) {
int maxv = [&]() -> int {
for (auto j : s) {
if (is_prime[i - j.second]) {
return pre[i] - j.first;
}
}
return -Inf;
}();
s.insert({pre[i - 1], i - 1});
if (maxv >= x) {
++cnt;
s.clear();
}
}
return cnt >= k;
}
int main () {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
fill(is_prime + 2, is_prime + N, 1);
for (int i = 2; i < N; ++i) {
if (is_prime[i]) {
for (int j = i * 2; j < N; j += i) {
is_prime[j] = 0;
}
}
}
int T;
cin >> T;
while (T--) {
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> a[i], pre[i] = pre[i - 1] + a[i];
}
const int low = -2e3, hig = 2e8;
int ansl = low, ansr = hig;
while (ansl <= ansr) {
int ansm = (ansl + ansr) >> 1;
if (check(ansm)) {
ansl = ansm + 1;
}
else {
ansr = ansm - 1;
}
}
if (ansr == low - 1) {
cout << "impossible" << '\n';
}
else {
cout << ansr << '\n';
}
}
return 0;
}
T1004 分組(HDU 7472)
考慮把四個集合分成兩部分,每部分兩個集合,分別算一個答案,然後用一些最佳化技巧拼起來,大概相當於 Meet in the middle。
設 \(f_{S, v}\) 表示我們劃分出一個部分 \(S\),是否能在內部選出一個異或和為 \(v\) 的集合,直接轉移即可。
列舉 \(L \cup R = [1, n]\),表示劃分的兩個部分,對於部分 \(L\),透過 \(f\) 陣列可以知道是否可以進一步劃分出兩個集合,使它們的異或和分別為 \(A, B\),對於 \(R\) 類似,有許多 \(C, D\) 可供選擇,直接這樣暴力做是 \(O(2^{n + 2m})\) 的。
不妨欽定 \(w_A \ge w_B, w_C \ge w_D\),這樣一種方案的代價就是 \(\max(w_A, w_C) - \min(w_B, w_D)\),列舉 \(A\) 並分類討論:
- 對於 \(w_A \ge w_C\),答案為 \(w_A - \min(w_B, w_D)\) 顯然我們只需知道最大的 \(w_D\),設 \(s_i\) 表示 \(w_C = i\) 時的最大 \(w_D\),我們所查詢的相當於 \(s\) 的一段字首 \(\max\)。
- 對於 \(w_A < w_C\) 類似。
我們可以對 \(w\) 排序,然後字首最佳化一下即可。
時間複雜度 \(O(2^{n + m})\)。
Code
#include <iostream>
#include <algorithm>
#include <bitset>
using namespace std;
const int N = 18, NS = (1 << N);
const int M = 10, MS = (1 << M);
const int Inf = 1e9;
int n, m;
int a[N], w[MS], p[MS], rk[MS];
int s[MS], pres[MS], t[MS], pret[MS];
bitset<MS> f[NS];
int main () {
cin.tie(0)->sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
for (int i = 0; i < (1 << m); ++i) {
cin >> w[i], p[i] = i;
}
sort(p, p + (1 << m), [&](int i, int j) -> bool {
return w[i] < w[j];
});
for (int i = 0; i < (1 << m); ++i) {
rk[p[i]] = i;
}
for (int s = 0; s < (1 << n); ++s) {
f[s].reset();
}
f[0][0] = 1;
for (int s = 1; s < (1 << n); ++s) {
int x = -1;
for (int i = 0; i < n; ++i) {
if ((s >> i) & 1) {
x = i;
break;
}
}
int t = s ^ (1 << x);
for (int i = 0; i < (1 << m); ++i) {
if (f[t][i]) {
f[s][i] = 1, f[s][i ^ a[x]] = 1;
}
}
}
int ans = Inf;
for (int L = 0; L < (1 << n); L += 2) {
int R = ((1 << n) - 1) ^ L, Ls = 0, Rs = 0;
for (int i = 0; i < n; ++i) {
((L >> i) & 1 ? Ls : Rs) ^= a[i];
}
fill(s, s + (1 << m), -1);
fill(t, t + (1 << m), -1);
fill(pres, pres + (1 << m), -1);
fill(pret, pret + (1 << m), -1);
for (int wa = 0; wa < (1 << m); ++wa) {
if (!f[L][wa]) continue;
int wb = Ls ^ wa;
if (rk[wb] > rk[wa]) continue;
s[rk[wa]] = max(s[rk[wa]], rk[wb]);
}
for (int wc = 0; wc < (1 << m); ++wc) {
if (!f[R][wc]) continue;
int wd = Rs ^ wc;
if (rk[wd] > rk[wc]) continue;
t[rk[wc]] = max(t[rk[wc]], rk[wd]);
}
pres[0] = s[0], pret[0] = t[0];
for (int i = 1; i < (1 << m); ++i) {
pres[i] = max(s[i], pres[i - 1]);
pret[i] = max(t[i], pret[i - 1]);
}
for (int i = 0; i < (1 << m); ++i) {
if (s[i] != -1 && pret[i] != -1) {
ans = min(ans, w[p[i]] - w[p[min(s[i], pret[i])]]);
}
if (t[i] != -1 && pres[i] != -1) {
ans = min(ans, w[p[i]] - w[p[min(t[i], pres[i])]]);
}
}
}
cout << ans << '\n';
}
return 0;
}
T1005 多層血條(HDU 7473)
注意到只有在 \([hp - m + 1, hp]\) 範圍內的生命值是有用的,其餘都會被覆蓋掉,暴力處理這一段即可。
Code
#include <iostream>
#include <cassert>
using namespace std;
int main () {
// freopen("1005.in", "r", stdin);
// freopen("1005.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
int n, m, hp, dmg;
cin >> n >> m >> hp >> dmg;
cout << '+' << string(m, '-') << '+' << '\n';
string ans = string(m, ' ');
for (int i = max(1, hp - m + 1); i <= hp; ++i) {
int pos = (i - 1) % m;
char &res = ans[pos];
int cur = (i - 1) / m + 1;
char ch = 'A' + ((cur - 1) % 5);
res = (i >= hp - dmg + 1 ? '.' : ch);
}
assert(int(ans.size()) == m);
for (int i = 1; i <= n; ++i) {
cout << '|' << ans << '|' << '\n';
}
cout << '+' << string(m, '-') << '+' << '\n';
}
return 0;
}
T1006 延時操控(HDU 7474)
首先延時移動顯然不好做,考慮將敵方的操作提前 \(k\) 回合,不影響答案,相當於我們要在 \(m - k\) 回合打敗敵方,然後接著隨機遊走 \(k\) 步,我們將這兩部分分開計算。
注意到敵方會和己方在同一個方向移動,除非它受到了傷害。由於它受到的傷害不超過 \(hp\),所以它與己方相對位置的變化量(較初始的相對位置而言)不超過 \(hp\),所以設 \(f_{i, x_1, y_1, tx, ty, d}\) 表示經過 \(i\) 個回合,我方位於 \((x_1, y_1)\),我方與敵方相對位置變化量為 \(tx, ty\),敵方受到了 \(d\) 點傷害的操作序列數。
設 \(g_{x_1, y_1, k}\) 表示從 \((x_1, y_1)\) 開始遊走 \(k\) 步且不能碰到障礙的方案數,計算答案 \(f, g\) 拼一下即可。
時間複雜度 \(O(mn^2hp^3)\)。
Code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 53;
const int Mod = 1e9 + 7;
const int Dx[4] = {0, 0, 1, -1};
const int Dy[4] = {1, -1, 0, 0};
int n, m, t, hp, px, py, ex, ey, dx, dy;
char s[N][N];
int f[N][N][N][11][11][6], g[N][N][N];
int main () {
cin.tie(0)->sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m >> t >> hp, m -= t;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> s[i][j];
if (s[i][j] == 'P')
px = i, py = j;
if (s[i][j] == 'E')
ex = i, ey = j;
}
}
for (int i = 0; i <= n + 1; ++i) {
for (int j = 0; j <= n + 1; ++j) {
if (!i || !j || i == n + 1 || j == n + 1)
s[i][j] = '#';
}
}
dx = px - ex, dy = py - ey;
fill(f[0][0][0][0][0], f[m][n][n][hp * 2 + 1][hp * 2 + 1] + hp + 1, 0);
f[0][px][py][hp][hp][0] = 1;
for (int i = 1; i <= m; ++i) {
for (int x1 = 1; x1 <= n; ++x1) {
for (int y1 = 1; y1 <= n; ++y1) {
for (int d = 0; d < hp; ++d) {
for (int tx = -d + hp; tx <= d + hp; ++tx) {
for (int ty = -d + hp; ty <= d + hp; ++ty) {
int x2 = x1 - dx - (tx - hp), y2 = y1 - dy - (ty - hp);
if (x2 <= 0 || x2 > n || y2 <= 0 || y2 > n) continue;
int lstv = f[i - 1][x1][y1][tx][ty][d];
if (!lstv) continue;
for (int k = 0; k < 4; ++k) {
int _x1 = x1 + Dx[k];
int _y1 = y1 + Dy[k];
if (s[_x1][_y1] == '#') continue;
int _x2 = x2 + Dx[k];
int _y2 = y2 + Dy[k];
int _d = d;
if (s[_x2][_y2] == '#')
_x2 = x2, _y2 = y2, ++_d;
int _tx = (_x1 - _x2) - dx + hp, _ty = (_y1 - _y2) - dy + hp;
f[i][_x1][_y1][_tx][_ty][_d] = (f[i][_x1][_y1][_tx][_ty][_d] + lstv) % Mod;
}
}
}
}
}
}
}
memset(g, 0, sizeof(g));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (s[i][j] != '#')
g[i][j][0] = 1;
}
}
for (int k = 1; k <= t; ++k) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
for (int d = 0; d < 4; ++d) {
int x = i + Dx[d];
int y = j + Dy[d];
if (s[x][y] != '#')
g[i][j][k] = (g[i][j][k] + g[x][y][k - 1]) % Mod;
}
}
}
}
int ans = 0;
for (int i = 1; i <= m; ++i) {
for (int x1 = 1; x1 <= n; ++x1) {
for (int y1 = 1; y1 <= n; ++y1) {
for (int tx = 0; tx <= hp * 2; ++tx) {
for (int ty = 0; ty <= hp * 2; ++ty) {
ans = (ans + 1ll * f[i][x1][y1][tx][ty][hp] * g[x1][y1][t]) % Mod;
}
}
}
}
}
cout << ans << '\n';
}
return 0;
}
T1007 序列更新(HDU 7475)
考慮對於每個位置獨立算答案。每個位置暴力做 \(\sqrt n\) 次操作,做完這些操作後期望只有 \(O(\sqrt n)\) 種數字大於當前最大值,算這些數字最早什麼時候對這個位置有貢獻即可。
時間複雜度 \(O(n \sqrt n)\)。
Code
#include <iostream>
#include <numeric>
#include <algorithm>
#include <cmath>
using namespace std;
using LL = long long;
const int N = 2.5e5 + 5;
int n, m;
int a[N], b[N], ok[N], fs[N], p[N];
LL ans[N];
int main () {
// freopen("1007.in", "r", stdin);
// freopen("1007.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
cin >> b[i];
}
iota(p + 1, p + n + 1, 1);
auto cmp = [&](int i, int j) -> bool {
return b[i] < b[j];
};
sort(p + 1, p + n + 1, cmp);
fill(fs, fs + n, n + 1);
for (int i = 1; i <= m; ++i) {
cin >> ok[i], fs[ok[i]] = min(fs[ok[i]], i);
}
fill(ans, ans + m + 1, 0);
auto add = [&](int l, int r, int x) -> void {
ans[l] += x, ans[r + 1] -= x;
};
int d = min(int(sqrt(n)), m);
for (int i = 1; i <= n; ++i) {
int now = a[i];
for (int j = 1; j <= d; ++j) {
now = max(now, b[(i + ok[j] - 1) % n + 1]);
add(j, j, now);
}
auto cmp = [&](int i, int j) -> bool {
return b[i] < j;
};
int low = lower_bound(p + 1, p + n + 1, now, cmp) - p, ed = n;
for (int j = n; j >= low; --j) {
int id = p[j];
int abl = max(d + 1, fs[(id - i + n) % n]);
if (abl <= ed) {
add(abl, ed, b[id]);
ed = abl - 1;
}
}
add(d + 1, ed, now);
}
for (int i = 1; i <= m; ++i) {
ans[i] += ans[i - 1];
cout << ans[i] << '\n';
}
}
return 0;
}
T1008 魔法卡牌(HDU 7476)
不會。
T1009 暱稱檢索(HDU 7477)
對於每一個暱稱算包含它的最短字首 \([1, x]\),對於每一個日期算包含它的最短字尾 \([y, n]\),一對暱稱-日期會產生 \(1\) 的貢獻當且僅當 \(x < y\),做一個字首和即可快速計算。
時間複雜度 \(O(n + m + \sum |name|)\)。
Code
#include <iostream>
using namespace std;
const int N = 1e6 + 5;
int n, m;
int la[N][10], nx[N][26], suf[N];
string s;
int main () {
// freopen("1009.in", "r", stdin);
// freopen("1009.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
cin >> m >> n >> s, s = " " + s;
for (int i = 1; i <= n; ++i) {
copy(la[i - 1], la[i - 1] + 10, la[i]);
if (s[i] >= '0' && s[i] <= '9') {
la[i][s[i] - '0'] = i;
}
}
const int days[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
auto get_string = [&](int x) -> string {
return to_string(x / 10) + to_string(x % 10);
};
fill(suf, suf + n + 2, 0);
for (int month = 1; month <= 12; ++month) {
for (int day = 1; day <= days[month - 1]; ++day) {
string goal = get_string(month) + get_string(day);
int pos = n + 1;
for (int i = 3; pos && ~i; --i) {
int x = goal[i] - '0';
pos = la[pos - 1][x];
}
++suf[pos];
}
}
for (int i = n; i; --i) {
suf[i] += suf[i + 1];
}
fill(nx[n + 1], nx[n + 1] + 26, n + 1);
for (int i = n; i; --i) {
copy(nx[i + 1], nx[i + 1] + 26, nx[i]);
if (s[i] >= 'a' && s[i] <= 'z') {
nx[i][s[i] - 'a'] = i;
}
}
int ans = 0;
for (string s; m--; ) {
cin >> s;
int pos = 0;
for (auto c : s) {
int x = c - 'a';
pos = nx[pos + 1][x];
if (pos >= n) break;
}
if (pos + 1 <= n) ans += suf[pos + 1];
}
cout << ans << '\n';
}
return 0;
}
T1010 矩陣的週期(HDU 7478)
不會。
T1011 找環(HDU 7479)
不會。
T1012 尋找寶藏(HDU 7480)
設 \(f_i\) 表示從 \((0, 0)\) 走到第 \(i\) 個寶藏的位置,能獲得的最大收益,\(g_i\) 表示從 \((n + 1, n + 1)\) 走到第 \(i\) 個寶箱的位置,能獲得的最大收益,這個都容易用樹狀陣列最佳化求出。
考慮一個禁區矩形 \([x_1, x_2] \times [y_1, y_2]\),答案有可能是來自以下四種情況:
-
選一個座標為 \([1, x_1) \times (y_2, n]\) 的點 \(A\),答案為 \(f_A + g_A - v_A\)。
-
選一個座標為 \((x_2, n] \times [1, y_1)\) 的點 \(A\),答案為 \(f_A + g_A - v_A\)。
-
選一個座標為 \([1, x_1) \times [1, y_2]\) 的點 \(A\),再選一個座標為 \([x_1, n] \times (y_2, n]\) 的點 \(B\),答案為 \(f_A + g_B\)。
-
選一個座標為 \([1, x_2] \times [1, y_1)\) 的點 \(A\),再選一個座標為 \((x_2, n] \times [y_1, n]\) 的點 \(B\),答案為 \(f_A + g_B\)。
注意到後兩種情況 \(A\) 和 \(B\) 的選取是獨立的,所以還是相當於二位數點問題,掃描線 + 樹狀陣列即可。
時間複雜度 \(O(n \log n)\)。
Code
#include <iostream>
#include <vector>
using namespace std;
using LL = long long;
const int N = 3e5 + 5;
int n, m;
int p[N], v[N];
LL f[N], g[N], c[N], ans[N], sum1[N], sum2[N];
int x1[N], x2[N], y1[N], y2[N];
vector<int> vx1[N], vx2[N];
void Insert (int x, LL y) {
for (; x <= n; x += (x & -x)) {
c[x] = max(c[x], y);
}
}
LL Query (int x) {
LL res = 0;
for (; x; x -= (x & -x)) {
res = max(res, c[x]);
}
return res;
}
int main () {
cin.tie(0)->sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> p[i] >> v[i];
}
fill(c, c + n + 1, 0);
for (int i = 1; i <= n; ++i) {
f[i] = Query(p[i]) + v[i];
Insert(p[i], f[i]);
}
fill(c, c + n + 1, 0);
for (int i = n; i; --i) {
g[i] = Query(n - p[i] + 1) + v[i];
Insert(n - p[i] + 1, g[i]);
}
fill(vx1 + 1, vx1 + n + 1, vector<int>());
fill(vx2 + 1, vx2 + n + 1, vector<int>());
for (int i = 1; i <= m; ++i) {
cin >> x1[i] >> y1[i] >> x2[i] >> y2[i];
vx1[x1[i]].push_back(i);
vx2[x2[i]].push_back(i);
}
fill(ans + 1, ans + m + 1, 0);
fill(sum1 + 1, sum1 + m + 1, 0);
fill(sum2 + 1, sum2 + m + 1, 0);
fill(c, c + n + 1, 0);
for (int i = 1; i <= n; ++i) {
for (auto j : vx1[i]) {
ans[j] = max(ans[j], Query(n - y2[j]));
}
Insert(n - p[i] + 1, f[i] + g[i] - v[i]);
}
fill(c, c + n + 1, 0);
for (int i = n; i; --i) {
for (auto j : vx2[i]) {
ans[j] = max(ans[j], Query(y1[j] - 1));
}
Insert(p[i], f[i] + g[i] - v[i]);
}
fill(c, c + n + 1, 0);
for (int i = 1; i <= n; ++i) {
for (auto j : vx1[i]) {
sum1[j] += Query(y2[j]);
}
Insert(p[i], f[i]);
}
fill(c, c + n + 1, 0);
for (int i = n; i; --i) {
Insert(n - p[i] + 1, g[i]);
for (auto j : vx1[i]) {
sum1[j] += Query(n - y2[j]);
}
}
fill(c, c + n + 1, 0);
for (int i = 1; i <= n; ++i) {
Insert(p[i], f[i]);
for (auto j : vx2[i]) {
sum2[j] += Query(y1[j] - 1);
}
}
fill(c, c + n + 1, 0);
for (int i = n; i; --i) {
for (auto j : vx2[i]) {
sum2[j] += Query(n - y1[j] + 1);
}
Insert(n - p[i] + 1, g[i]);
}
for (int i = 1; i <= m; ++i) {
cout << max(ans[i], max(sum1[i], sum2[i])) << '\n';
}
}
return 0;
}