hdu 多校 2019 选做
orz sqc
hdu6701 多校2019Day10K Make Rounddog Happy
题目传送门
https://vjudge.net/problem/HDU-6701
(vjudge 重度依赖,原谅我不尊重原题哈)
题解
我们很轻松地求出一个最大值管辖哪些区域,但是由于“所选区间每个元素要互不相同”这一限制条件的存在,所以不能直接求出。最优的方法需要枚举一个端点,然后通过预处理 \(pre_i\) 表示 \(i\) 位置上的数之前的最后一次出现的位置,那么这个东西的前缀最大值就是可行以 \(i\) 为右端点时的极限左端点。
但是很显然这样做是 \(O(n^2)\) 的。
可以发现,每次如果只处理跨过最大值的区间,然后递归剩下的区间,就可以做出来了。但是每一次需要扫左右两边的其中一个区间。
所以可以用启发式分裂(这样叫是因为最后反过来看这就是一个启发式合并),每次扫描较小区间。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 300000 + 7;
const int LOG = 19;
int n, k;
ll ans;
int a[N], pre[N], vpre[N], nxt[N], vnxt[N];
pii rmq[N][LOG];
inline void rmq_init() {
for (int i = 1; i <= n; ++i) rmq[i][0] = pii(a[i], i);
for (int j = 1; (1 << j) <= n; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; ++i)
rmq[i][j] = std::max(rmq[i][j - 1], rmq[i + (1 << (j - 1))][j - 1]);
}
inline pii qmax(int l, int r) {
int k = std::__lg(r - l + 1);
return std::max(rmq[l][k], rmq[r - (1 << k) + 1][k]);
}
inline void solve(int l, int r) {
if (l > r) return;
int mid = qmax(l, r).se;
if (mid - l <= r - mid) {
int mn, nd = a[mid] - k;
for (int i = mid; i >= l; --i) {
mn = std::min(nxt[i], r);
if (mn < mid) break;
if (mn - i + 1 >= nd) ans += mn - std::max(mid, i + nd - 1) + 1;
}
} else {
int mx = 0, nd = a[mid] - k;
for (int i = mid; i <= r; ++i) {
mx = std::max(pre[i], l);
if (mx > mid) break;
if (i - mx + 1 >= nd) ans += std::min(mid, i - nd + 1) - mx + 1;
}
}
solve(l, mid - 1), solve(mid + 1, r);
}
inline void work() {
rmq_init();
ans = 0;
solve(1, n);
printf("%lld\n", ans);
}
inline void init() {
read(n), read(k);
memset(vpre, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; ++i) {
read(a[i]);
pre[i] = std::max(pre[i - 1], vpre[a[i]] + 1);
vpre[a[i]] = i;
}
for (int i = 1; i <= n; ++i) vnxt[i] = n + 1;
nxt[n + 1] = n + 1;
for (int i = n; i; --i) nxt[i] = std::min(nxt[i + 1], vnxt[a[i]] - 1), vnxt[a[i]] = i;
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}
hdu6643 多校2019Day6J Ridiculous Netizens
题目传送门
https://vjudge.net/problem/HDU-6643
题解
其实还是很妙的。
首先点分治+dfs序以后转化为序列背包是一个经典套路了,dfs 序上如果选这个点直接从前一个转移过来,如果不选就跳过自己的子树来转移。
主要是这个背包应该怎么做。
如果直接令 \(dp[i][j]\) 表示前 \(i\) 个数构成的乘积为 \(j\) 的方案数的话,时间复杂度为 \(O(nm)\) 还有点分治的 \(\log\) 无法接受。
不过可以发现,题目要求的只是乘上一些数以后 \(\leq m\),我们只关心一个数后面会不会乘到 \(m\) 以上。
所以,如果 \(\lfloor\frac ma\rfloor = \lfloor\frac mb\rfloor\) 的话,其实 \(a\) 和 \(b\) 是没有区别的,因为只要这个东西一样,后面要乘到大于 \(m\) 所需要的乘积也是一样的。
所以可以不妨考虑成一开始有一个 \(m\),问多少方案可以把 \(m\) 除到不为 \(0\)。
这样就可以设 \(dp[i][j]\) 表示现在 \(m\) 已经被除乘了 \(j\) 的方案数。\(j\) 一定是 \(m\) 的约数,所以 \(j\) 只有 \(O(\sqrt m)\) 个。
这样的总时间复杂度为 \(O(n\sqrt m\log n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 2000 + 7;
const int M = 1e6 + 7;
const int P = 1e9 + 7;
int n, m, cnt, sum, mima, rt, dfc, ans;
int a[N], v[M], p[N], siz[N], vis[N], pre[N], dfn[N], dp[N][N];
struct Edge { int to, ne; } g[N << 1]; int head[N], tot;
inline void addedge(int x, int y) { g[++tot].to = y, g[tot].ne = head[x], head[x] = tot; }
inline void adde(int x, int y) { addedge(x, y), addedge(y, x); }
inline int smod(int x) { return x >= P ? x - P : x; }
inline void sadd(int &x, const int &y) { x += y; x >= P ? x -= P : x; }
inline int fpow(int x, int y) {
int ans = 1;
for (; y; y >>= 1, x = (ll)x * x % P) if (y & 1) ans = (ll)ans * x % P;
return ans;
}
inline void getrt(int x, int fa = 0) {
int f = 0; siz[x] = 1;
for fec(i, x, y) if (y != fa && !vis[y]) getrt(y, x), siz[x] += siz[y], smax(f, siz[y]);
smax(f, sum - siz[x]);
if (smin(mima, f)) rt = x;
}
inline void dfs(int x, int fa = 0){
siz[x] = 1;
for fec(i, x, y) if (y != fa && !vis[y]) dfs(y, x), siz[x] += siz[y];
dfn[x] = ++dfc, pre[dfc] = x;
}
inline void calc(int x) {
dfc = 0, dfs(x);
dp[0][v[m]] = 1;
for (int i = 1; i <= dfc; ++i) {
memset(dp[i], 0, sizeof(int) * (cnt + 1));
for (int j = 1; j <= cnt; ++j)
if (p[j] / a[pre[i]]) sadd(dp[i][v[p[j] / a[pre[i]]]], dp[i - 1][j]);
for (int j = 1; j <= cnt; ++j) sadd(dp[i][j], dp[i - siz[pre[i]]][j]);
}
for (int i = 1; i <= cnt; ++i) sadd(ans, dp[dfc][i]);
sadd(ans, P - 1);
}
inline void solve(int x) {
vis[x] = 1, calc(x);
for fec(i, x, y) if (!vis[y]) {
mima = sum = siz[y], getrt(y);
solve(rt);
}
}
inline void ycl() {
for (int i = 1; i <= m; ++i)
if (i == 1 || m / (i - 1) != m / i) p[++cnt] = m / i, v[m / i] = cnt;
}
inline void work() {
ycl();
sum = mima = n, getrt(1);
solve(rt);
printf("%d\n", ans);
}
inline void cls() {
cnt = tot = ans = 0;
memset(head, 0, sizeof(int) * (n + 1));
memset(vis, 0, sizeof(int) * (n + 1));
}
inline void init() {
read(n), read(m);
cls();
for (int i = 1; i <= n; ++i) read(a[i]);
int x, y;
for (int i = 1; i < n; ++i) read(x), read(y), adde(x, y);
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}
hdu6635 多校2019Day6B Nonsense Time
题目传送门
https://vjudge.net/problem/HDU-6635
题解
既然题目保证排列是完全随机的,那么这个排列的 LIS 在期望情况下是 \(O(\sqrt n)\) 的。
加点不太好考虑,我们不妨反过来删点。
删去一个点时候,如果这个点不在之前的 LIS 中,那么 LIS 长度显然不会改变。否则,我们暴力重构一遍 LIS。
因为 LIS 的期望长度是 \(O(\sqrt n)\),所以一个点在 LIS 中的概率也就是 \(\sqrt n\)。所以期望被暴力重构 \(\sqrt n\) 次。
所以在期望情况下,这个算法的时间复杂度为 \(O(n\sqrt n \log n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 50000 + 7;
#define lowbit(x) ((x) & -(x))
int n, ansl;
int a[N], t[N], tp[N], dp[N], pre[N], v[N], ans[N];
pii s[N];
inline void qadd(int x, pii k) {
for (; x <= n; x += lowbit(x))
smax(s[x], k);
}
inline pii qmax(int x) {
pii ans(0, 0);
for (; x; x -= lowbit(x)) smax(ans, s[x]);
return ans;
}
inline void rebuild(int ti) {
memset(s, 0, sizeof(pii) * (n + 1));
memset(v, 0, sizeof(int) * (n + 1));
pii ans(0, 0);
for (int i = 1; i <= n; ++i) if (t[i] < ti) pre[i] = qmax(a[i]).se, dp[i] = dp[pre[i]] + 1, qadd(a[i], pii(dp[i], i)), smax(ans, pii(dp[i], i));
ansl = ans.fi;
for (int x = ans.se; x; x = pre[x]) v[x] = 1;
}
inline void work() {
rebuild(n + 1);
for (int i = n; i; --i) {
ans[i] = ansl;
if (v[tp[i]]) rebuild(i);
}
for (int i = 1; i <= n; ++i) printf("%d%c", ans[i], " \n"[i == n]);
}
inline void init() {
read(n);
for (int i = 1; i <= n; ++i) read(a[i]);
for (int i = 1; i <= n; ++i) read(tp[i]), t[tp[i]] = i;
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}
hdu6619 多校2019Day4F Horse
题目传送门
https://vjudge.net/problem/HDU-6619
http://acm.hdu.edu.cn/showproblem.php?pid=6619
题解
一直以为第二个操作是能量增加吃树高度,没想到是达到吃树高度。
令 \(s_i\) 表示 \(a_i\) 的前缀和,\(ss_i\) 表示 \(s_i\) 的前缀和。
如果没有这两种操作的话,那么总贡献就是 \(\sum\limits_{i=1}^n s_i = \sum\limits_{i=1}^n (n-i+1)a_i\)。
那么如果有了第一种操作的话,那么如果选择 \(i\) 这个点相当于就是把答案贡献了 \((n-i+1)a_i\)。所以可以选择 \(m\) 个最大的 \((n-i+1)a_i\)。
但是第二个操作的直接设置可能会破坏掉这个东西。不妨把第二个操作看成直接设成 \(0\),然后保留之前的所有吃树的贡献。
这样,第二个操作就和第一个操作无关了。这个就相当于把原序列分成 \(k+1\) 段,每一段的贡献是 \(\sum\limits_{i=1}^n (s_i - s_{l-1})\)。
所以可以直接 dp,很容易发现这个 dp 式子可以用斜率优化转移。
时间复杂度 \(O(nk)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 10000 + 7;
const int M = 50 + 7;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, k, m;
ll ans;
int a[N], s[N], q[N];
ll b[N], ss[N], dp[M][N];
inline void work1() {
for (int i = 1; i <= n; ++i) b[i] = (ll)a[i] * (n - i + 1);
std::sort(b + 1, b + n + 1, std::greater<ll>());
for (int i = 1; i <= m; ++i) ans += b[i];
}
inline ll slope_y(const int &i, const int &j, const int &k) { return (dp[i - 1][k] - ss[k] + (ll)s[k] * k) - (dp[i - 1][j] - ss[j] + (ll)s[j] * j); }
inline ll slope_x(const int &j, const int &k) { return s[k] - s[j]; }
inline void work2() {
for (int i = 1; i <= n; ++i) ss[i] = ss[i - 1] + s[i];
for (int i = 1; i <= n; ++i) dp[0][i] = INF;
for (int j = 1; j <= k; ++j) {
int hd = 0, tl = 0;
for (int i = 1; i <= n; ++i) {
while (hd < tl && slope_y(j, q[tl], i) * slope_x(q[tl - 1], q[tl]) <= slope_y(j, q[tl - 1], q[tl]) * slope_x(q[tl], i)) --tl;
q[++tl] = i;
while (hd < tl && slope_y(j, q[hd], q[hd + 1]) <= i * slope_x(q[hd], q[hd + 1])) ++hd;
dp[j][i] = dp[j - 1][q[hd]] + ss[i] - ss[q[hd]] - (ll)s[q[hd]] * (i - q[hd]);
}
}
ans -= dp[k][n];
}
inline void work() {
work1();
work2();
printf("%lld\n", ans);
}
inline void cls() {
memset(dp, 0, sizeof(dp));
ans = 0;
}
inline void init() {
cls();
read(n), read(k), read(m), ++k;
for (int i = 1; i <= n; ++i) read(a[i]), s[i] = s[i - 1] + a[i];
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}
hdu6637 多校2019Day6D peed Dog
题目传送门
https://vjudge.net/problem/HDU-6637
题解
首先最优方案一定是在两个人花费的时间一样的情况下最优对吧。
那么如果每个人都选择自己的时间较少的来做。但是这样就不一定能保证时间一样了。
为了协调时间的同事保证最大时间最小,所以我们优先选择自己的任务所花时间比上另一个人的较大的移动。
也就是说,如果我们把每一项任务按照 \(\frac {a_i}{b_i}\) 排序,那么一定是前边一段是第一个人做,后面一段是第二个人做,中间存在一个合作的。花费时间相等出现在合作的那一项。
想要找到这一段可以二分,找到以后推一波式子就可以求出答案了。
但是现在要按照顺序来加入。所以可以用树状数组维护前后缀的和。
时间复杂度 \(O(n\log^2 n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 2500000 + 7;
#define lowbit(x) ((x) & -(x))
int n;
int a[N], b[N], p[N], h[N];
struct BIT {
int s[N];
inline void qadd(int x, int k) {
for (; x <= n; x += lowbit(x))
s[x] += k;
}
inline int qsum(int x) {
int ans = 0;
for (; x; x -= lowbit(x)) ans += s[x];
return ans;
}
inline int qsum(int l, int r) { return qsum(r) - qsum(l - 1); }
} pr, sf;
inline bool check(int x) { return pr.qsum(1, x - 1) <= sf.qsum(x, n); }
inline bool cmp(const int &x, const int &y) { return (ll)a[x] * b[y] < (ll)a[y] * b[x]; }
inline void work() {
for (int i = 1; i <= n; ++i) p[i] = i;
std::sort(p + 1, p + n + 1, cmp);
for (int i = 1; i <= n; ++i) h[p[i]] = i;//, dbg("p[%d] = %d\n", i, p[i]);
for (int i = 1; i <= n; ++i) {
pr.qadd(h[i], a[i]);
sf.qadd(h[i], b[i]);
int l = 1, r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
// dbg("l = %d\n", l);
int pre = pr.qsum(1, l - 1), suf = sf.qsum(l + 1, n);
assert(p[l] <= i);
ll ans1 = (ll)pre * b[p[l]] + (ll)suf * a[p[l]] + (ll)a[p[l]] * b[p[l]], ans2 = a[p[l]] + b[p[l]], p = std::__gcd(ans1, ans2);
assert(p);
printf("%lld/%lld\n", ans1 / p, ans2 / p);
}
}
inline void init() {
read(n);
memset(pr.s, 0, sizeof(int) * (n + 1));
memset(sf.s, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; ++i) read(a[i]), read(b[i]);
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}
hdu6587 多校2019Day1J Kingdom
题目传送门
https://vjudge.net/problem/HDU-6587
题解
如果不存在这样的 \(0\) 的点,那么我们回忆一下应该怎样构造一棵树。
首先对于在中序遍历中找到先序遍历第一个点,可以通过这个位置把中序遍历分成两部分,从而推出先序遍历的两部分,递归解决即可。
所以考虑令 \(dp[i][l][r]\) 表示以先序遍历第 \(i\) 个点为根,中序遍历中 \(l .. r\) 的这一段是一个子树的方案数。
那么如果 \(a_i \neq 0\) 且 \(a_i\) 在 \(b\) 出现过的话,那么可以直接以这个位置把 dp 分解成两个子问题这样 dp 下去;
如果 \(a_i \neq 0\) 且 \(a_i\) 未在 \(b\) 中出现过,那么 \(b_{l.. r}\) 里面每一个 \(0\) 都有可能是 \(a_i\),所以枚举这些点来分解即可。
如果 \(a_i = 0\),那么 \(b_{l..r}\) 中每一个 \(0\) 或者是没有在 \(a\) 中出现过的 \(b_i\) 都有可能是 \(a_i\) 对应的东西。所以还是枚举这些点分解。
但是我们可以发现,对于 \(0\) 对应 \(0\) 的情况,我们并没有指出这些点应该是哪个编号。我们找出所有的既没有在 \(a\) 中出现过,也没有在 \(b\) 中出现过的数,设其个数为 \(k\),那么他们可以任意支配这些编号,所以方案还需要再乘上 \(k!\)。
时间复杂度 \(O(Tn^4)\)。这竟然能过……我傻了都。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 100 + 7;
const int P = 998244353;
int n;
int a[N], b[N], aa[N], bb[N];
int dp[N][N][N];
inline int smod(int x) { return x >= P ? x - P : x; }
inline void sadd(int &x, const int &y) { x += y; x >= P ? x -= P : x; }
inline int fpow(int x, int y) {
int ans = 1;
for (; y; y >>= 1, x = (ll)x * x % P) if (y & 1) ans = (ll)ans * x % P;
return ans;
}
inline void work() {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n + 1; ++i)
for (int j = 1; j <= n + 1; ++j) dp[i][j][j - 1] = 1;
for (int i = n; i; --i) {
for (int j = 1; j <= n; ++j)
for (int k = j; k <= n - i + j && k <= n; ++k) {
if (a[i]) {
if (bb[a[i]]) {
if (bb[a[i]] >= j && bb[a[i]] <= k) dp[i][j][k] = (ll)dp[i + 1][j][bb[a[i]] - 1] * dp[i + bb[a[i]] - j + 1][bb[a[i]] + 1][k] % P;
else dp[i][j][k] = 0;
} else for (int l = j; l <= k; ++l) if (!b[l]) sadd(dp[i][j][k], (ll)dp[i + 1][j][l - 1] * dp[i + l - j + 1][l + 1][k] % P);
} else for (int l = j; l <= k; ++l) if (!b[l] || !aa[b[l]]) sadd(dp[i][j][k], (ll)dp[i + 1][j][l - 1] * dp[i + l - j + 1][l + 1][k] % P);
}
}
int kk = 0, kkk = 1;
for (int i = 1; i <= n; ++i) if (!aa[i] && !bb[i]) ++kk;
while (kk) kkk = (ll)kkk * (kk--) % P;
kkk = (ll)kkk * dp[1][1][n] % P;
printf("%d\n", kkk);
}
inline void init() {
read(n);
memset(aa, 0, sizeof(int) * (n + 1));
memset(bb, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; ++i) read(a[i]), a[i] && (aa[a[i]] = i);
for (int i = 1; i <= n; ++i) read(b[i]), b[i] && (bb[b[i]] = i);
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}
hdu6625 多校2019Day5B three arrays
题目传送门
https://vjudge.net/problem/HDU-6625
题解
建立两棵 Trie,然后每一次都用两个点从根向下跑。如果可以走相同的方向(方向就是 \(0/1\))那么就尽量走,否则就走剩下的方向。这样可以保证每一次取到的都是尽量优的解。
但是这样会有一个问题,就是如果有一个时刻,两个方向都可以走的话,我们无法判断应该走哪一个方向。不过我们可以发现两个方向其实都可以走,而且是相互独立的,不会干扰,所以可以随便走一个,剩下的一个留给下一次走。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 1e5 + 7;
#define lc c[0]
#define rc c[1]
int n, m, nod1 = 1, nod2 = 1;
int ans[N];
struct Node { int c[2], val; } t1[N * 31], t2[N * 31];
inline void ins(Node *t, int &nod, int x) {
int o = 1;
++t[o].val;
for (int i = 30; ~i; --i) {
int p = (x >> i) & 1;
if (!t[o].c[p]) t[o].c[p] = ++nod;
o = t[o].c[p], ++t[o].val;
}
}
inline int qry() {
int o1 = 1, o2 = 1, ans = 0;
--t1[o1].val, --t2[o2].val;
for (int i = 30; ~i; --i) {
if (t1[t1[o1].lc].val && t2[t2[o2].lc].val) o1 = t1[o1].lc, o2 = t2[o2].lc, --t1[o1].val, --t2[o2].val;
else if (t1[t1[o1].rc].val && t2[t2[o2].rc].val) o1 = t1[o1].rc, o2 = t2[o2].rc, --t1[o1].val, --t2[o2].val;
else if (t1[t1[o1].lc].val && t2[t2[o2].rc].val) o1 = t1[o1].lc, o2 = t2[o2].rc, --t1[o1].val, --t2[o2].val, ans += 1 << i;
else if (t1[t1[o1].rc].val && t2[t2[o2].lc].val) o1 = t1[o1].rc, o2 = t2[o2].lc, --t1[o1].val, --t2[o2].val, ans += 1 << i;
}
return ans;
}
inline void work() {
for (int i = 1; i <= n; ++i) ans[i] = qry();
std::sort(ans + 1, ans + n + 1);
for (int i = 1; i <= n; ++i) printf("%d%c", ans[i], " \n"[i == n]);
memset(t1, 0, sizeof(Node) * (nod1 + 1));
memset(t2, 0, sizeof(Node) * (nod2 + 1));
}
inline void init() {
read(n);
nod1 = 1, nod2 = 1;
int x;
for (int i = 1; i <= n; ++i) read(x), ins(t1, nod1, x);
for (int i = 1; i <= n; ++i) read(x), ins(t2, nod2, x);
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
int T;
read(T);
while (T--) {
init();
work();
}
fclose(stdin), fclose(stdout);
return 0;
}