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. 【Vue】Re22 Axios

    Axios[AJAX I\O System] 创建案例项目并且安装Axios npm install axios --save 接口测试网址: http://httpbin.org/ 案例提供的数据地 ...

  2. 并行化强化学习 —— 初探 —— 并行reinforce算法的尝试 (中篇:强化学习在大规模仿真环境下单步交互并行化设计的可行性)

    本篇博客是前篇博客并行化强化学习 -- 初探 -- 并行reinforce算法的尝试 (上篇:强化学习在多仿真环境下单步交互并行化设计的可行性)的继续,文中代码地址为:https://gitee.co ...

  3. [POI2015] POD 题解

    前言 题目链接:洛谷. 题意简述 长度为 \(n\) 的一串项链,每颗珠子是 \(k\) 种颜色之一.第 \(i\) 颗与第 \(i-1, i+1\) 颗珠子相邻,第 \(n\) 颗与第 \(1\) ...

  4. 23 暑假友谊赛 No.4(UKIEPC 2017)

    23 暑假友谊赛 No.4(UKIEPC 2017) Problem A Alien Sunset hh,开始一眼差分,但是写寄了qwq,后来换枚举过了(Orz,但是看学长差分是能做的,我就说嘛,差分 ...

  5. Win32 处理多个按钮共用一个事件消息

    今天在学习制作计算器小程序中,碰到要多个按钮共用一个事件的问题, 现记录下来. 在窗体上按钮排列 排列的时候要按顺序排放,也就是说,0-9的ID号要连着的. #define IDD_DIALOG1 1 ...

  6. Mac 使用 Caps Lock 键切换输入法失灵问题解决

    Mac 上的 Caps Lock 键对于多语言用户来说,除了切换输入大小写的作用外还承担着切换输入法的功能.正常情况下,轻按一下 Caps Lock 键是切换输入法,长按是切换输入大小写.然而有时这个 ...

  7. 使用 AWS CLI 管理 EC2

    EC2 EC2 是 AWS 的云服务器服务 EC2: Elastic Compute Cloud 创建实例 选择一个系统镜像(AMI): AMI(Amazon Machine Image)定义了启动实 ...

  8. [golang]查询ssl证书剩余有效天数并邮件提醒

    前言 自从云厂商的免费ssl证书改成3个月,而且证书数量还是20个之后,自己网站的ssl证书就换成了其它免费方案.但是免费方案不会提醒证书过期,所以写个工具每天定时查询证书剩余有效天数,如果证书即将过 ...

  9. 牛逼!Vue3.5的useTemplateRef让ref操作DOM更加丝滑

    前言 vue3中想要访问DOM和子组件可以使用ref进行模版引用,但是这个ref有一些让人迷惑的地方.比如定义的ref变量到底是一个响应式数据还是DOM元素?还有template中ref属性的值明明是 ...

  10. SQL查折线图数据

    在做后台管理系统的时候,有很大概率是要与各种图表数据打交道, 那么如何通过SQL查出基本图表的数据呢,以折线图为例, X轴为日期,Y轴为统计数据,那么我们通过SQL如何查询出连续日期下的统计数据呢, ...