HDU-ACM 2024 Day4

hztmax0發表於2024-08-17

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;
}