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

HDU-ACM 2024 Day4的更多相关文章

  1. hdu acm 1028 数字拆分Ignatius and the Princess III

    Ignatius and the Princess III Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K ...

  2. HDU ACM 题目分类

    模拟题, 枚举1002 1004 1013 1015 1017 1020 1022 1029 1031 1033 1034 1035 1036 1037 1039 1042 1047 1048 104 ...

  3. hdu acm 1166 敌兵布阵 (线段树)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  4. hdu acm 2082 找单词

    找单词 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...

  5. HDU ACM 1325 / POJ 1308 Is It A Tree?

    Is It A Tree? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  6. HDU ACM 1134 Game of Connections / 1130 How Many Trees?(卡特兰数)

    [题目链接]http://acm.hdu.edu.cn/showproblem.php?pid=1134 [解题背景]这题不会做,自己推公式推了一段时间,将n=3和n=4的情况列出来了,只发现第n项与 ...

  7. HDU ACM Eight

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043 解题背景: 看到八数码问题,没有任何的想法,偶然在翻看以前做的题的时候发现解决过类似的一道题,不 ...

  8. HDU ACM 1690 Bus System (SPFA)

    Bus System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  9. HDU ACM 1224 Free DIY Tour (SPFA)

    Free DIY Tour Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  10. HDU ACM 1869 六度分离(Floyd)

    六度分离 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

随机推荐

  1. 深度学习用什么卡比较给力?—— A100/H100真的么有RTX4090好吗?

    近日看到这么一个帖子: https://www.zhihu.com/question/612568623/answer/3131709693 ============================= ...

  2. hibernate validation,spring validation自定义参数校验

    1.背景 在实际开发中,我们除了会使用常用的参数判断,如字符串不为空,最大值,最小值等 我们还可以自定义参数校验规则 2.实际生产问题 实际生产中同步订单的时候, 假设我们要求订单状态值只能是 -1, ...

  3. 基础数据结构->set&&map

    set&&map BEGIN:惜墨如金 set用法 基本用法 #include<bits/stdc++.h> using namespace std; void the_s ...

  4. 当 Spring 循环依赖碰上 Aysnc,调试过程中出现 BeanCurrentlyInCreationException,有点意思

    开心一刻 前两天有个女生加我,我同意了 第一天,她和我聊文学,聊理想,聊篮球,聊小猫小狗 第二天,她和我说要看我腹肌 吓我一跳,我反手就删除拉黑,我特喵一肚子的肥肉,哪来的腹肌! 循环依赖 关于 Sp ...

  5. ARMv8-A 地址翻译技术之MMU的前世今生

    MMU的重要性不言而喻,支撑操作系统之上的各种复杂应用.但在正式讲MMU之前,我们先说说MMU的发展史,因为ARMv8-A的MMU相当复杂,直接切入正题,会显得比较枯燥.废话不多说,咱们马上开始: 一 ...

  6. Echarts 5 动态按需引入图表

    官网提供的按需引入方法为全量按需引入,在打包分离中,仍旧存在使用不到的图表被打包进去. 例如:组件A使用了折线图.柱状图,组件B只用到了折线图,但是打包组件B的时候,柱状图也就被打包进去. 本文提供一 ...

  7. Microsoft Build 2023 After Party (Placeholder)

    一年过去了,微软BUILD又回来了! https://build.microsoft.com/en-US/home This is a placeholder page for Microsoft B ...

  8. Linux 文件系统层次结构(FHS)解释

    详细的目录解释可以使用 man file-hierarchy 和 man hier 命令查看. 常见目录: 目录 描述 /bin binaries.在单用户模式下需要用到的基本命令的二进制文件,包括启 ...

  9. NSmartProxy:一款.NET开源、跨平台的内网穿透工具

    前言 今天大姚给大家分享一款.NET开源.免费(MIT License).跨平台的内网穿透工具,采用.NET Core的全异步模式打造:NSmartProxy. 内网穿透工具介绍 内网穿透工具是一种能 ...

  10. RestoreFormer++: Towards Real-World Blind Face Restoration from Undegraded Key-Value Pairs(IEEE,2023,8)

    RestoreFormer++: Towards Real-World Blind Face Restoration from Undegraded Key-Value Pairs(IEEE,2023 ...