[题解]一氧化二氢
\(\mathcal{Back\;To\;The\;Menu}\).
2022-02-12 一氧化二氢
只能用 H2O 来形容这个比赛了......真就暴力都可以出不止一次奇迹了😅.
好 / Good
天生对 DP 的畏惧使我并没有怎么分析这个题......被 不需要删完 给难到了......
首先注意到,删除的是串,即使最后是中间删掉了一些再拼起来,实际上我们删掉的都是连续的一段,回过头来看 不一定要删完 其实就很好处理了 —— 只需要计算一下 \(dp(l,r)\) 表示区间 \([l,r]\) 删完的最大价值,最后再用一个一维 DP 就可以解决掉了。
于是现在的问题来到了 \(dp(l,r)\),显然,类似一般的区间 DP,我们可以枚举一个断点,然后分成两个区间去分别删完。这种转移的特点是,\(l\) 和 \(r\) 并不是在同一次删除中被删掉,那么显然还有一种转移 —— \(l,r\) 被同时删掉,此时我们需要枚举一个峰值 \(k\in [l,r]\) 使得 \(a_l\le a_k\land a_r\le a_k\),此时我们需要处理出两个东西,\(p(l,k)\) 和 \(q(k,j)\),前者表示删完 \([l,k]\) 之后剩下一个从 \(a_l\) 到 \(a_k\) 的递增序列的最大价值,后者表示删完 \([l,k]\) 之后剩下一个从 \(a_k\) 到 \(a_j\) 的递减序列的最大价值,然后我们就可以进行两个转移:
现在还有一个小问题:\(p(l,r)\) 和 \(q(l,r)\) 怎么算?两者很相似,就说 \(p\) 了。枚举一个 \(k\) 使得 \(a_k=a_r-1\),那么 \(p(l,r)=p(l,k)+dp(k+1,r-1)\),即枚举递增数列的倒数第二项,就很容易转换为一个子问题。
最后的复杂度是 \(\mathcal O(n^3)\) 的。
/** @author Arextre */
#include <bits/stdc++.h>
using namespace std;
#define USING_FREAD
// #define NDEBUG
// #define NCHECK
#include <cassert>
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }
#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif
template<class T> inline T readret(T x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = x % 10, x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
} // namespace Elaina
using namespace Elaina;
const int inf = 0x3f3f3f3f;
const int maxn = 400;
int n;
int a[maxn + 5], v[maxn + 5];
int dp[maxn + 5][maxn + 5];
int p[maxn + 5][maxn + 5], q[maxn + 5][maxn + 5];
inline void input() {
readin(n);
rep (i, 1, n) readin(v[i]);
rep (i, 1, n) readin(a[i]);
}
inline void solve() {
rep (i, 1, n) dp[i][i] = v[1];
for (int len = 2; len <= n; ++len) {
for (int l = 1; l <= n - len + 1; ++l) {
int r = l + len - 1;
dp[l][r] = p[l][r] = q[l][r] = -inf;
if (a[l] < a[r] && a[r] - a[l] + 1 <= r - l + 1) {
for (int k = l; k < r; ++k) if (a[k] == a[r] - 1)
chkmax(p[l][r], p[l][k] + dp[k + 1][r - 1]);
}
else if (a[l] > a[r] && a[l] - a[r] + 1 <= r - l + 1) {
for (int k = l + 1; k <= r; ++k) if (a[k] == a[l] - 1)
chkmax(q[l][r], dp[l + 1][k - 1] + q[k][r]);
}
for (int k = l; k < r; ++k) chkmax(dp[l][r], dp[l][k] + dp[k + 1][r]);
for (int k = l; k <= r; ++k) if (a[k] >= a[l] && a[k] >= a[r]) {
if (a[k] - a[l] + a[k] - a[r] + 1 <= n)
chkmax(dp[l][r], p[l][k] + q[k][r] + v[a[k] - a[l] + a[k] - a[r] + 1]);
}
}
}
}
int f[maxn + 5];
signed main() {
freopen("good.in", "r", stdin);
freopen("good.out", "w", stdout);
input();
solve();
for (int i = 1; i <= n; ++i) {
chkmax(f[i], f[i - 1]);
for (int j = 0; j < i; ++j)
chkmax(f[i], f[j] + dp[j + 1][i]);
}
writln(f[n]);
return 0;
}
水 / Water
原题目不是这个名字,但是这个题的数据是真的水,并且,如果将这道题和下一道题的题目改掉,就可以形容这场考试的 H2O 属性了。
写了个 \(\mathcal O(qn\log n)\) 的算法还过了......甚至这个暴力都是暴力中最劣的......
转换原题 —— 求所有 两个端点颜色不同的边 中的最小权值。
就这个东西,不难想到 \(\mathcal O(q\sqrt{m\log n})\sim \mathcal O(q\sqrt m\log n)\) 的大小点分治。实现稍微好一点,最优下的复杂度将近 \(\mathcal O(5e8)\) 左右,在该题数据不水的情况下,可能会丢一部分的分。
可能还存在更优的做法,此时应当分析性质,不难发现及证明,最终选择的边一定在 MST 上,因此,我们只需要在 MST 上维护上述东西就行了。实际上,我们可以比较暴力一点地维护这个东西,对于每个点开很多堆,记录其所有儿子中所有颜色集合的边权值,肯定用 vector/unordered_map 啊,不会有人开静态数组吧?,再开一个可删堆维护所有 两个端点颜色不同的边 的权值。修改的时候看一下父亲的堆,自己的堆,维护一下答案堆即可。最后的复杂度是 \(\mathcal O(n\log n+q\log n)\).
/** @author Arextre */
#include <bits/stdc++.h>
using namespace std;
#define USING_FREAD
// #define NDEBUG
// #define NCHECK
#include <cassert>
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }
#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif
template<class T> inline T readret(T x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = x % 10, x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
} // namespace Elaina
using namespace Elaina;
const int maxn = 2e5;
const int maxm = 3e5;
int n, m, c, q;
int col[maxn + 5];
struct edge { int u, v, w; } Es[maxm + 5];
inline void input() {
readin(n, m, c, q);
int u, v, w;
rep (i, 1, m) {
readin(u, v, w);
Es[i] = { u, v, w };
}
rep (i, 1, n) readin(col[i]);
}
namespace ufs {
int fa[maxn + 5];
inline void build() { rep (i, 1, n) fa[i] = i; }
inline int find(int u) {
while (u ^ fa[u]) u = fa[u] = fa[fa[u]];
return u;
}
inline bool merge(int u, int v) {
u = find(u), v = find(v);
return u == v? false: (fa[u] = v, true);
}
} // namespace ufs
vector<pii> g[maxn + 5];
inline void add_edge(int u, int v, int w) {
g[u].push_back({ v, w });
g[v].push_back({ u, w });
}
inline void buildTre() {
static int tmp[maxm + 5];
rep (i, 1, m) tmp[i] = i;
sort(tmp + 1, tmp + m + 1, [](int i, int j) { return Es[i].w < Es[j].w; });
ufs::build();
rep (i, 1, m) if (ufs::merge(Es[tmp[i]].u, Es[tmp[i]].v))
add_edge(Es[tmp[i]].u, Es[tmp[i]].v, Es[tmp[i]].w);
}
struct heap {
priority_queue<int, vector<int>, greater<int>> rem, del;
inline void push(int val) { rem.push(val); }
inline void erase(int val) { del.push(val); }
inline void move() {
while (!del.empty() && rem.top() == del.top())
rem.pop(), del.pop();
}
inline int size() { return move(), (int)(rem.size()) - (int)(del.size()); }
inline int top() { return move(), rem.top(); }
} ans;
unordered_map<int, heap> buc[maxn + 5];
int fa[maxn + 5], tofa[maxn + 5];
void init(int u, int par) {
for (const auto& [v, w]: g[u]) if (v ^ par) {
buc[u][col[v]].push(w);
fa[v] = u, tofa[v] = w;
init(v, u);
}
for (auto& [c, H]: buc[u]) if (c ^ col[u])
ans.push(H.top());
}
inline void modify(int u, int c) {
if (col[u] == c) return ;
if (fa[u]) { // not root
heap& h = buc[fa[u]][col[u]];
if (col[fa[u]] ^ col[u]) ans.erase(h.top());
h.erase(tofa[u]);
if (!h.size()) buc[fa[u]].erase(col[u]);
else if (col[fa[u]] ^ col[u]) ans.push(h.top());
heap& th = buc[fa[u]][c];
if (th.size() && col[fa[u]] != c) ans.erase(th.top());
th.push(tofa[u]);
if (col[fa[u]] ^ c) ans.push(th.top());
}
if (buc[u].count(col[u])) ans.push(buc[u][col[u]].top());
if (buc[u].count(c)) ans.erase(buc[u][c].top());
col[u] = c;
}
signed main() {
freopen("color.in", "r", stdin);
freopen("color.out", "w", stdout);
input();
buildTre();
init(1, 0);
for (int x, y; q--; ) {
readin(x, y);
modify(x, y);
if (!ans.size()) writln(0);
else writln(ans.top());
}
return 0;
}
哟 / Ohh
原题目不是这个名字,但是如果将这道题上一道题的题目改掉,就可以形容这场考试的 H2O 属性了。
优化全部来自 \(\mathcal O(n^2)\) 的算法,而关键全在于 \(v_i\le v_{i+1}\) 这一条件。
刚开始想用生成函数来解决这个问题,但是发现其对于 border 的刻画异常艰难,于是转战其他方向。
如果没有 border 的限制,答案是 trivial 的,即 \(\displaystyle \prod_{i=1}^n v_i\),但是考虑上 border 的限制之后就没有那么显然了,先设计 \(f(i)\) 表示 \([1,i]\) 中,没有 border 的串的个数,接下来转移有两种方法:直接计算/容斥。
直接计算好像有点难,因为一般的思路就是增量法,但是问题在于,可能一段长度小于 \(i\) 且存在 border 的串,在加上后面的一些东西之后没有 border 了,而 \(f\) 并没有包含这种东西,这使得转移较为困难。
那么我们就要想容斥了,用 \(\displaystyle \prod_{i=1}^n v_i\) 减去存在 border 的情况,而存在 border 的非法方案的去重又有两个思路,枚举最小 border 长度和最大 border 长度,但是无论如何我们都要枚举一个 border 的长度 \(l\),且强制要求 \(s[1:l]=s[i-l+1:i]\),这个 要求 又 要求 了 \(s[i-l+1:i]\) 可以和 \(s[1:l]\) 一样,注意到 \(v_i\le v_{i+1}\),因此这个 要求 是可以被满足的,并且这提示我们思路是没有问题的,接下来就是选择枚举最长 border 和最短 border 两种选择。
如果枚举最长 border 长度,似乎转移也没有什么用,但是如果枚举最短 border 长度,那么就要求了这个长度的 border 中不能还存在 border,这就归约到子问题,可以转移,因此,我们不难写出转移方程:
其中 \(\displaystyle P(n)=\prod_{i=1}^nv_i\).
这是一个分治 NTT 的形式,唯一需要处理一下的就是上界 \(\ddiv{n}{2}\),这相当于 \(i\le j\),就是前一半卷后一半,稍微处理一下就行了,复杂度 \(\mathcal O(n\log^2 n)\),对于 \(n\le 10^6\) 很卡,但是可以做一个小优化,由于我们只需要计算 \(f(n)\),因此我们只需要算出 \(f(1)\sim f\brak{\ddiv{n}{2}}\) 的值即可,所以分治 NTT 的时候只需要算这个区间即可,这个剪枝减小了常数。
/** @author Arextre */
#include <bits/stdc++.h>
using namespace std;
#define USING_FREAD
// #define NDEBUG
// #define NCHECK
#include <cassert>
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }
#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif
template<class T> inline T readret(T x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = x % 10, x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
} // namespace Elaina
using namespace Elaina;
const int maxn = 1e6;
const int mod = 998244353;
inline void chkadd(int& x, int y) { if ((x += y) >= mod) x -= mod; }
inline int qkpow(int a, int n) {
int ret = 1;
for (; n; n >>= 1, a = 1ll * a * a % mod)
if (n & 1) ret = 1ll * ret * a % mod;
return ret;
}
namespace NTT {
int G[55];
inline void init() {
for (int i = 1; i <= 50; ++i)
G[i] = qkpow(3, (mod - 1) / (1 << i));
}
int rev[maxn * 10 + 5], n;
inline void prepare(int len) {
for (n = 1; n < len; n <<= 1);
for (int i = 0; i < n; ++i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1)? (n >> 1) : 0);
}
inline void ntt(vector<int>& f, const int opt = 1) {
f.resize(n);
for (int i = 0; i < n; ++i) if (i < rev[i])
swap(f[i], f[rev[i]]);
for (int p = 2, lev = 1; p <= n; p <<= 1, ++lev) {
int len = p >> 1, w = G[lev];
for (int k = 0; k < n; k += p) {
int buf = 1, tmp;
for (int i = k; i < k + len; ++i, buf = 1ll * buf * w % mod) {
tmp = 1ll * f[i + len] * buf % mod;
f[i + len] = (f[i] + mod - tmp) % mod;
chkadd(f[i], tmp);
}
}
}
if (opt != 1) {
reverse(f.begin() + 1, f.end());
int invn = qkpow(n, mod - 2);
for (int i = 0; i < n; ++i) f[i] = 1ll * f[i] * invn % mod;
}
}
} // namespace NTT
int n;
int prod[maxn + 5], invp[maxn + 5];
inline void input() {
readin(n);
prod[0] = 1;
for (int i = 1; i <= n; ++i) {
readin(prod[i]);
prod[i] = 1ll * prod[i - 1] * prod[i] % mod;
invp[i] = qkpow(prod[i], mod - 2);
}
}
int dp[maxn + 5];
void solve(int l, int r) {
if (l == r) {
// printf("before, dp[%d] == %d\n", l, dp[l]);
dp[l] = (prod[l] + mod - dp[l]) % mod;
if (l << 1 <= n) chkadd(dp[l << 1], dp[l]);
// printf("dp[%d] == %d\n", l, dp[l]);
return ;
}
int mid = l + r >> 1;
solve(l, mid);
if (l + mid + 1 <= n) {
// printf("Now l == %d, r == %d, mid == %d\n", l, r, mid);
static vector<int> f, g;
f.resize(mid - l + 1);
g.resize(r - mid);
for (int i = l; i <= mid; ++i) f[i - l] = 1ll * dp[i] * invp[i] % mod;
for (int i = mid + 1; i <= r; ++i) g[i - mid - 1] = prod[i];
// for (int x: f) writln(x, ' '); Endl;
// for (int x: g) writln(x, ' '); Endl;
NTT::prepare(r - l);
NTT::ntt(f), NTT::ntt(g);
for (int i = 0; i < NTT::n; ++i) f[i] = 1ll * f[i] * g[i] % mod;
NTT::ntt(f, 1919810);
// for (int x: f) writln(x, ' '); Endl;
for (int i = 0; i < NTT::n; ++i) {
if (i + l + mid + 1 > n) break;
chkadd(dp[i + l + mid + 1], f[i]);
}
}
solve(mid + 1, r);
}
signed main() {
freopen("music.in", "r", stdin);
freopen("music.out", "w", stdout);
NTT::init();
input();
solve(1, n);
writln(dp[n]);
return 0;
}