A Jujubesister
题意:给定长度为 n n n 的数列 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n, q q q 次询问区间 [ l , r ] [l,r] [l,r] 上满足 a i = a k > a j a_i=a_k>a_j ai=ak>aj 且满足 l ≤ i < j < k ≤ r l \le i <j<k \le r l≤i<j<k≤r 的三元组 ( i , j , k ) (i,j,k) (i,j,k) 数目。 1 ≤ n , q ≤ 5 × 1 0 5 1 \le n,q \le 5\times 10^5 1≤n,q≤5×105, 1 ≤ a i ≤ n 1 \le a_i \le n 1≤ai≤n。
朴素想法: [ l , r ] → [ l , r + 1 ] [l,r] \to [l, r+1] [l,r]→[l,r+1], k = r k=r k=r 增加,找在区间范围内的 a i = a r + 1 a_i=a_{r+1} ai=ar+1,离线去查 r + 1 r+1 r+1 前小于 a i a_i ai 的 j j j 个数,
解法:首先考虑如何快速维护这样的三元组。由于有 i < j < k i<j<k i<j<k 的约束不好做,考虑维护每个下标 i i i 前面有多少下标 j j j 满足 1 ≤ j < i 1 \le j<i 1≤j<i 且 a j < a i a_j<a_i aj<ai,用 c i c_i ci 数组维护。对于一组满足 a i = a k a_i=a_k ai=ak 的 ( i , k ) (i,k) (i,k),则合法的 j j j 有 c j − c i c_j-c_i cj−ci 个。
( i , j , k ) (i,j,k) (i,j,k) 满足 i < k i<k i<k 且 j < k j<k j<k 满足 a i = a k a_i=a_k ai=ak, a j < a k a_j<a_k aj<ak 的 j j j 个数。要求 i < j i<j i<j 的个数,等价于(全集-不合法的) c k − c i c_k-c_i ck−ci,
考虑莫队维护答案,可以再进行一次前缀和,将以 k k k 为三元组右端点的 ( i , j ) (i,j) (i,j) 个数再求和(即对 c k c_k ck 和同色点的 c i c_i ci 做前缀和—— ∑ i = 1 , a i = a k k c k − c i \displaystyle \sum_{i=1, a_i=a_k}^k c_k-c_i i=1,ai=ak∑kck−ci,动态维护区间前缀中同色点数目 c n t a i cnt_{a_i} cntai 和同色点的和 s u m a i sum_{a_i} sumai)。这样移动区间的时候,可以利用差分快速求出当前的更新量,可以更快维护三元组数目。
因而整体复杂度为莫队的复杂度 O ( n n + n log n ) \mathcal O\left(n \sqrt n+n\log n\right) O(nn+nlogn)。
#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)using namespace std;
using ll = long long;
const int N = 5e5 + 5;
int n, m, B, S, T, a[N], b[N], c[N], cnt[N];
ll ANS, sum[N], ans[N];
struct Q { int l, r, id; } q[N];
void add(int i) { for (; i <= n; i += i & -i) ++c[i]; }
int qry(int i, int w = 0) { for (; i; i -= i & -i) w += c[i]; return w; }
void addL(int x) {ANS += sum[a[x]] - (ll)cnt[a[x]] * b[x];++cnt[a[x]], sum[a[x]] += b[x];
}
void addR(int x) {ANS += (ll)cnt[a[x]] * b[x] - sum[a[x]];++cnt[a[x]], sum[a[x]] += b[x];
}
void delL(int x) {--cnt[a[x]], sum[a[x]] -= b[x];ANS -= sum[a[x]] - (ll)cnt[a[x]] * b[x];
}
void delR(int x) {--cnt[a[x]], sum[a[x]] -= b[x];ANS -= (ll)cnt[a[x]] * b[x] - sum[a[x]];
}
void Solve() {scanf("%d%d", &n, &m);B = sqrt(n) + 1, S = n / sqrt(m) + 1, T = n / B + 1;fp(i, 1, n) scanf("%d", a + i), b[i] = qry(a[i] - 1), add(a[i]);fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;sort(q, q + m, [&](Q a, Q b) { return a.l / S == b.l / S ? (a.l / S & 1 ? a.r > b.r : a.r < b.r) : a.l < b.l; });int L = 1, R = 0;fp(i, 0, m - 1) {int ql = q[i].l, qr = q[i].r;while (L > ql) addL(--L);while (R < qr) addR(++R);while (L < ql) delL(L++);while (R > qr) delR(R--);ans[q[i].id] = ANS; }fp(i, 0, m - 1) printf("%lld\n", ans[i]);
}
int main() {int t = 1;// scanf("%d", &t);while (t--) Solve();return 0;
}
B Circle of Mistery
题意:考虑长度为 n n n 的每个排列 P = { p 1 , p 2 , ⋯ , p n } P=\{p_1,p_2,\cdots,p_n\} P={p1,p2,⋯,pn},并给定权值数组 { w } i = 1 n \{w\}_{i=1}^n {w}i=1n 和权值阈值 k k k,若 P P P 中存在一个任意长度的置换环 { a 1 , a 2 , ⋯ , a l } \{a_1,a_2,\cdots,a_l\} {a1,a2,⋯,al} 满足 ∑ i = 1 l w a i ≥ k \displaystyle \sum_{i=1}^l w_{a_i} \ge k i=1∑lwai≥k,则考虑统计该排列 P P P 的逆序对数。问符合条件的排列中最小逆序对数目是多少。 1 ≤ n ≤ 1 0 3 1 \le n \le 10^3 1≤n≤103, − 1 0 6 ≤ w i , k ≤ 1 0 6 -10^6 \le w_i,k \le 10^6 −106≤wi,k≤106。
解法:既然需要逆序对数目最少,那么除了给定置换环,那么其他位置 p i p_i pi 一定都是 p i = i p_i=i pi=i 以减少其他部分产生的逆序对数目。
考虑现在置换环构成的数字,假设由 a 1 , a 2 , ⋯ , a l a_1,a_2,\cdots,a_l a1,a2,⋯,al( a 1 < a 2 < ⋯ < a l a_1 <a_2 < \cdots <a_l a1<a2<⋯<al)构成,则为了逆序对最少,可以考虑按下面构造: p a 1 = a 2 , p a 2 = a 3 , ⋯ , p a l − 1 = a l , p a l = a 1 p_{a_1}=a_2,p_{a_2}=a_3,\cdots,p_{a_{l-1}}=a_l,p_{a_l}=a_1 pa1=a2,pa2=a3,⋯,pal−1=al,pal=a1。此时逆序对数目由 p a l p_{a_l} pal 与前面 p a i p_{a_i} pai 构成 l − 1 l-1 l−1 个,然后还有满足 p i = i p_i=i pi=i 且 i ∈ [ a 1 + 1 , a l − 1 ] i \in [a_1+1,a_l-1] i∈[a1+1,al−1] 的数字构成。因而总逆序对数目等于 2 ( a l − a 1 ) − l + 1 2(a_l-a_1)-l+1 2(al−a1)−l+1。因而可以得到一个转化题意:从 { 1 , 2 , 3 , ⋯ , n } \{1,2,3,\cdots,n\} {1,2,3,⋯,n} 中选出 l l l 个数字 { a 1 , a 2 , ⋯ , a l } \{a_1,a_2,\cdots,a_l\} {a1,a2,⋯,al},满足 ∑ i = 1 l w a i ≥ k \displaystyle \sum_{i=1}^l w_{a_i} \ge k i=1∑lwai≥k,求 2 ( a l − a 1 ) − l + 1 2(a_l-a_1)-l+1 2(al−a1)−l+1 的最小值。
注意到 n ≤ 1 0 3 n \le 10^3 n≤103,一个朴素的想法是枚举 a 1 a_1 a1,顺推枚举 a l a_l al,
纯暴力: [ a 1 , a l ] [a_1,a_l] [a1,al] 区间根据 w i w_i wi 排序,尽可能选大的直到和大于等于 k k k。
使用优先队列维护 [ a 1 , a l ] [a_1,a_l] [a1,al] 中哪些数字选出来可以使得其权值和大于等于 k k k,如果和确定大于等于 k k k 则按 2 ( a l − a 1 ) − l + 1 2(a_l-a_1)-l+1 2(al−a1)−l+1 更新答案。一个贪心的想法是区间中全部的正数必选,尽量选较大的负数,恰好使得其和大于等于 k k k。但是如果朴素实现,就会发现在顺推过程中维护一个优先队列维护负数会出现上限 k − w a l − w a 1 k-w_{a_l}-w_{a_1} k−wal−wa1 不单调,因而导致优先队列无法快速维护满足条件的负数集合。
这时需要考虑一个性质:边界点 a 1 a_1 a1 和 a l a_l al 的 w w w 值必然严格大于 0 0 0。考虑固定 a 1 a_1 a1,对于 a l a_l al 和 a l + 1 a_l+1 al+1,假定 w a l > 0 w_{a_l}>0 wal>0 而 w a l + 1 ≤ 0 w_{a_l+1} \le 0 wal+1≤0。如果选定置换环末端点为 a l + 1 a_l+1 al+1 而非 a l a_l al,则显然选择 a l + 1 a_l+1 al+1 可选数字个数(置换环大小)不会优于 a l a_l al 因为确定多选择一个非正权值 w a l + 1 w_{a_l+1} wal+1,即 l l l 不会变得更大,而 a l + 1 > a l a_l+1>a_l al+1>al,这样 2 ( a l − a 1 ) − l + 1 2(a_l-a_1)-l+1 2(al−a1)−l+1 一定不会更小。同理对于 a 1 a_1 a1 可以证明类似结论。因而区间两端 a 1 a_1 a1 和 a l a_l al 的 w w w 值必然严格大于 0 0 0。
这时再考虑原始做法。由于确定了 a 1 a_1 a1 和 a l a_l al 为正数,则当固定 a 1 a_1 a1 而依次增大 a l a_l al 时,随着遍历区间变大,区间中正权值数目和正权值总和会越来越大,这时可以在区间中选择的非正数也会越来越多,这时就可以用一个对顶堆结构维护所有的非正数权值。复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
关于对顶堆更加详细一些的说明:考虑使用一个 used
小根优先队列表示当前列入区间选择的非正数权值,deleted
大根优先队列维护当前区间中未被选择的非正数权值。当加入一个新的非正数权值 x x x 时,先考虑加入 used
堆, 如果 deleted
堆顶比 x x x 大,则将 x x x 替换入 deleted
,deleted
堆顶送入 used
。然后再维护 used
堆使得它与正数权值和加起来大于等于 k k k,可以考虑从 deleted
堆顶送入若干个最大的元素,或者 used
堆踢出若干最小的。
#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)using namespace std;
using ll = long long;
const int N = 1e3 + 5;
int n, k, a[N];
void Solve() {scanf("%d%d", &n, &k);int sum = 0, mx = -1e9, ans = 1e9;fp(i, 1, n) {scanf("%d", a + i);mx = max(mx, a[i]);sum += a[i] > 0 ? a[i] : 0;}if (mx >= k) return puts("0"), void();if (sum < k || (sum == 0 && mx < k)) return puts("-1"), void();fp(l, 1, n) {if (a[l] <= 0) continue;priority_queue<int> q1, q2;// q1 : selected non-positive, top = lowest// q2 : unselected non-positive, top = biggestint s = a[l], cnt = 1;for (int r = l + 1; r <= n; ++r) {if (a[r] > 0) s += a[r], ++cnt;else {q2.push(a[r]);while (!q1.empty() && !q2.empty() && -q1.top() < q2.top()) {int x = -q1.top(), y = q2.top();s += x - y, q1.pop(), q2.pop(), q1.push(-y), q2.push(x);}continue;}while (!q2.empty() && s + q2.top() >= k)q1.push(-q2.top()), s += q2.top(), ++cnt, q2.pop();if (s >= k) ans = min(ans, 2 * (r - l) - cnt + 1);}}printf("%d\n", ans);
}
int main() {int t = 1;// scanf("%d", &t);while (t--) Solve();return 0;
}
C Cheeeeen the Cute Cat
题意:给定一个二分图,图左右两部都有 n n n 个点。 ∀ i , j ∈ [ 1 , n ] \forall i,j \in [1,n] ∀i,j∈[1,n],若 ( i , j + n ) (i,j+n) (i,j+n) 存在边,则 ( j , i + n ) (j,i+n) (j,i+n) 不存在边。使用邻接矩阵给出该图的 n ( n − 1 ) 2 \dfrac{n(n-1)}{2} 2n(n−1) 条边。问该图的二分图最大匹配数。 1 ≤ n ≤ 3 × 1 0 3 1 \le n \le 3\times 10^3 1≤n≤3×103。
解法:考虑将连边 ( i , j + n ) (i,j+n) (i,j+n) 转化到 ( i , j ) (i,j) (i,j) 连边。考虑原二分图上的匹配转化到这个新图上的一条路径—— ( a 1 , a 2 + n ) , ( a 2 , a 3 + n ) , ⋯ , ( a k − 1 , a k + n ) (a_1,a_2+n),(a_2,a_3+n),\cdots,(a_{k-1},a_k+n) (a1,a2+n),(a2,a3+n),⋯,(ak−1,ak+n) 的 k − 1 k-1 k−1 个点的匹配等价于新图上一条长度为 k k k 的路径 a 1 → a 2 → ⋯ a k a_1 \to a_2 \to \cdots a_k a1→a2→⋯ak。
这时原图转化到一个 n n n 个点的竞赛图(有向完全图)。由于竞赛图必有哈密顿路径,因而答案至少为 n − 1 n-1 n−1。考虑何时答案为 n n n,即存在哈密顿回路。显然一个点数大于等于 3 3 3 的强连通分量,必然存在哈密顿回路,也就等价于原图上该子图的完美匹配。因而答案为 n n n 当且仅当图上每个强连通分量都是大于等于 3 3 3。只需要特判掉存在孤立强连通块(大小为 1 1 1)的情况即可。
#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)using namespace std;
using ll = long long;
const int N = 3e3 + 5;
int n, deg[N];
void Solve() {scanf("%d\n", &n);fp(i, 1, n) fp(j, 1, n) {char c = getchar(); getchar();deg[i] += c - '0';}sort(deg + 1, deg + n + 1);for (int i = 1, j = 0, s = 0; i <= n; ++i) {s += deg[i];if (s == i * (i - 1) / 2) {if (i - j < 3) return printf("%d\n", n - 1), void();j = i;}}printf("%d\n", n);
}
int main() {int t = 1;// scanf("%d", &t);while (t--) Solve();return 0;
}
D Cirno’s Perfect Equation Class
题意:给定 c , k , n c,k,n c,k,n,计算满足 k a + b = c ka+b=c ka+b=c 且满足 gcd ( a , b ) ≥ n \gcd(a,b) \ge n gcd(a,b)≥n 的正整数对 ( a , b ) (a,b) (a,b) 个数。 1 ≤ c , k , n ≤ 1 0 9 1 \le c,k,n \le 10^9 1≤c,k,n≤109。
解法:枚举 c c c 的因子 b b b,计算出 k a ka ka,检查是否可以被 k k k 整除,然后计算出 a a a,最后检查 gcd ( a , b ) ≥ n \gcd(a,b) \ge n gcd(a,b)≥n 即可。复杂度 O ( c log c ) \mathcal O\left(\sqrt{c} \log c\right) O(clogc)。
#include <bits/stdc++.h>
using namespace std;
int main()
{int q, k, c, n;scanf("%d", &q);while (q--){scanf("%d%d%d", &k, &c, &n);vector<int> factor;for (int i = 1; 1ll * i * i <= c; i++)if (c % i == 0){factor.push_back(i);if (i * i != c)factor.push_back(c / i);}int ans = 0;for (auto b : factor){int left = c - b;if (left % k || left <= 0)continue;int a = left / k;if (__gcd(a, b) >= n)ans++;}printf("%d\n", ans);}return 0;
}
E Red and Blue and Green
题意:构造一个长度为 n n n 的排列,使得满足 m m m 个约束条件: [ l i , r i ] [l_i,r_i] [li,ri] 区间逆序对个数是奇数或偶数。满足这些约束条件的区间包含或不相交。 1 ≤ n , m ≤ 1 0 3 1 \le n, m \le 10^3 1≤n,m≤103。
解法:首先所有的区间不相交或包含意味着这些区间可以构成一个树形结构。首先考虑使用一个 [ 1 , n ] [1,n] [1,n] 区间包络所有的大区间,并要求区间 [ l , r ] [l,r] [l,r] 上的数字都在 [ l , r ] [l,r] [l,r] 范围。在这个区间包含树(去掉重复区间)上进行 dfs:
- 当前节点是叶子节点。如果当前逆序对数目为奇数,则交换区间最开头的两个数字,并返回。如果区间长度为 1 1 1,返回
-1
。 - 当前节点存在子节点 { [ l 1 , r 1 ] , [ l 2 , r 2 ] , ⋯ , [ l k , r k ] } \{[l_1,r_1],[l_2,r_2],\cdots,[l_k,r_k]\} {[l1,r1],[l2,r2],⋯,[lk,rk]}。首先子节点遍历,并统计它们逆序对数目奇偶性,将未被子区间覆盖的区间填上相应数字,即对于当前区间 [ l , r ] [l,r] [l,r],若 x ∈ [ l , r ] x \in [l,r] x∈[l,r] 不属于任何子区间, a x ← x a_x \leftarrow x ax←x。
- 如果当前所有子区间各自内部逆序对奇偶性与大区间相同,直接返回。
- 如果当前所有子区间逆序对奇偶性与大区间不同,考虑在第一个区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] 和第一个不在第一个区间的位置进行交换。分为以下几种情况:
- l 1 = l l_1=l l1=l。考虑交换 r 1 r_1 r1 和 r 1 + 1 r_1+1 r1+1。如果 r 1 + 1 = l 2 r_1+1=l_2 r1+1=l2,则找到 [ l 2 , r 2 ] [l_2,r_2] [l2,r2] 的最小值与 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] 最大值交换。如果 r 1 + 1 < l 2 r_1+1 <l_2 r1+1<l2,则直接交换 r 1 + 1 r_1+1 r1+1 和 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] 最大值。
- r 1 = r r_1=r r1=r。考虑交换 l 1 − 1 l_1-1 l1−1 和 l 1 l_1 l1。找到 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] 的最大值与 l 1 − 1 l_1-1 l1−1 交换。
#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)using namespace std;
using ll = long long;
const int N = 1e3 + 5;
struct Seg { int l, r, k; };
int n, m, a[N], pos[N], deg[N];
vector<Seg> s;
vector<int> G[N];
map<pair<int, int>, int> mp;
void op(int k) { swap(pos[k], pos[k + 1]); }
void dfs(int u) {int w = s[u].k;sort(G[u].begin(), G[u].end(), [&](int a, int b) { return s[a].l < s[b].l; });for (auto v : G[u])dfs(v), w ^= s[v].k;if (!w) return;if (G[u].empty()) op(s[u].l);else if (s[G[u][0]].l == s[u].l) op(s[G[u][0]].r);else op(s[G[u][0]].l - 1);// fp(i, 1, n) printf("%d%c", pos[i], " \n"[i == n]);
}
void Solve() {scanf("%d%d", &n, &m);fp(i, 1, n) pos[i] = i, mp[{i, i}] = 0;for (int l, r, k; m--;) {scanf("%d%d%d", &l, &r, &k);if (mp.count({l, r}) && mp[{l, r}] != k)return puts("-1"), void();if (mp.count({l, r})) continue;mp[{l, r}] = k;s.push_back({l, r, k});}m = s.size();sort(s.begin(), s.end(), [](Seg a, Seg b) { return a.r - a.l < b.r - b.l; });fp(i, 0, m - 1) fp(j, i + 1, m - 1)if (s[j].l <= s[i].l && s[i].r <= s[j].r) {G[j].push_back(i), ++deg[i];break;}fp(i, 0, m - 1)if (!deg[i])dfs(i);fp(i, 1, n) a[pos[i]] = i;fp(i, 1, n) printf("%d%c", a[i], " \n"[i == n]);
}
int main() {int t = 1;// scanf("%d", &t);while (t--) Solve();return 0;
}
G Go to Play Maimai DX
题意:给定长度为 n n n 的数列 { a } i = 1 n = { 1 , 2 , 3 , 4 } n \{a\}_{i=1}^n =\{1,2,3,4\}^n {a}i=1n={1,2,3,4}n,问长度最短的区间包含的 { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 和至少 k k k 个 4 4 4。 1 ≤ k ≤ n ≤ 1 0 5 1 \le k \le n \le 10^5 1≤k≤n≤105。
解法:右端点单调右移的时候,左端点必然也单调右移。双指针扫描一下即可。复杂度 O ( n ) \mathcal O(n) O(n)。
#include<bits/stdc++.h>
using namespace std;
#define fre(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout)
typedef long long ll;
template<typename T>inline void read(T &a){char c=getchar();T x=0,f=1;while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}a=f*x;
}
inline void write(ll x){char P[105];int w=0;if(x<0)putchar('-'),x=-x;if(x==0)printf("0");while(x)P[++w]=x%10+'0',x/=10;for(int i=w;i;i--)putchar(P[i]);
}
const int N=1e5+10;
int cnt[N];
int a[N];
int n,k;
bool pd(){for(int i=1;i<=3;++i){if(!cnt[i])return false;}if(cnt[4]<k)return false;return true;
}
int main()
{scanf("%d%d",&n,&k);for(int i=1;i<=n;++i)scanf("%d",&a[i]);int minlen=n;for(int l=1,r=0;r<=n&&l<=n;++l){while((!pd())&&r<n){cnt[a[++r]]++;}if(pd())minlen=min(minlen,r-l+1);cnt[a[l]]--;}printf("%d\n",minlen);return 0;
}
H Nazrin the Greeeeeedy Mouse
题意:给定 n n n 个奶酪,体积为 a i a_i ai,价值为 b i b_i bi,只能从 1 1 1 至 n n n 顺序拿,如果要拿第 i + 1 i+1 i+1 个奶酪,第 i i i 个奶酪必须拿走或者打洞,被打洞的奶酪不能再被拿走。给定 m m m 个背包,体积为 { s i z e } i = 1 m \{{\rm size}\}_{i=1}^m {size}i=1m,每次拿着一个背包从第一个奶酪出发拿奶酪,问 m m m 个背包下能拿走多少价值的奶酪。 1 ≤ n ≤ 200 1\le n \le 200 1≤n≤200, 1 ≤ a i ≤ 200 1 \le a_i\le 200 1≤ai≤200, 1 ≤ b i ≤ 1 0 5 1 \le b_i\le 10^5 1≤bi≤105, 1 ≤ s i z e i ≤ 200 1\le {\rm size}_i \le 200 1≤sizei≤200, 1 ≤ m ≤ 1 0 5 1\le m \le 10^5 1≤m≤105。
解法:不难注意到,每次有效拿背包装奶酪至少会拿一个奶酪走。因而至多只需要 min ( n , m ) \min(n,m) min(n,m) 个背包即可,并且一定是贪心取最大的背包。并且每个奶酪只有拿走或者不拿走(被打洞),而且还是顺序拿走。
考虑背包: f i , j , k f_{i,j,k} fi,j,k 表示从第 i i i 个奶酪开始拿,拿到第 j j j 个奶酪取得体积为 k k k 的最大价值。一个非常经典的 01 背包:
f i , j , k ← max ( f i , j − 1 , k , f i , j − 1 , k − a i + b i ) f_{i,j,k} \leftarrow \max(f_{i,j-1,k},f_{i,j-1,k-a_i}+b_i) fi,j,k←max(fi,j−1,k,fi,j−1,k−ai+bi)
维护该背包的复杂度为 O ( n 2 V ) \mathcal O(n^2V) O(n2V)。然后考虑维护前缀 min \min min: f i , j , k ← max ( f i , j , k − 1 , f i , j , k ) f_{i,j,k} \leftarrow \max(f_{i,j,k-1},f_{i,j,k}) fi,j,k←max(fi,j,k−1,fi,j,k)。
然后再维护一个 dp: g i , j g_{i,j} gi,j 表示用了 i i i 个背包,现在已经拿到第 j j j 个奶酪的最大价值。转移是显然的:
g i , j ← max k ∈ [ 0 , j − 1 ] g i − 1 , k + f i , j , s i z e i g_{i,j} \leftarrow \max_{k \in [0,j-1]}g_{i-1,k}+f_{i,j,{\rm size}_{i}} gi,j←k∈[0,j−1]maxgi−1,k+fi,j,sizei
因而总复杂度 O ( n 2 V + n V ) \mathcal O(n^2V+nV) O(n2V+nV)。
#include <bits/stdc++.h>
using namespace std;
const int N = 200;
int a[N + 5], siz[100005], use[N + 5];
int f[N + 5][N + 5][N + 5], b[N + 5], g[N + 5][N + 5];
int main()
{int n, m;scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++)scanf("%d%lld", &a[i], &b[i]);for (int i = 1; i <= n; i++){for (int j = i; j <= n; j++){for (int w = 0; w <= N; w++)f[i][j][w] = f[i][j - 1][w];for (int w = a[j]; w <= N; w++)f[i][j][w] = max(f[i][j][w], f[i][j - 1][w - a[j]] + b[j]);}for (int j = 1; j <= n; j++)for (int w = 1; w <= N; w++)f[i][j][w] = max(f[i][j][w], f[i][j][w - 1]);}for (int i = 1; i <= m; i++)scanf("%d", &siz[i]);int cnt = 0, ans = 0;for (int i = max(1, m - n); i <= m; i++)use[++cnt] = siz[i];for (int i = 1; i <= cnt; i++)for (int j = 1; j <= n; j++){for (int k = 0; k < j; k++)g[i][j] = max(g[i][j], g[i - 1][k] + f[k + 1][j][use[i]]);ans = max(ans, g[i][j]);}printf("%d", ans);return 0;
}
I The Yakumo Family
题意:给定长度为 n n n 的数列 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n,定义 f ( l , r ) = ⨁ i = l r a i \displaystyle f(l,r)=\bigoplus_{i=l}^r a_i f(l,r)=i=l⨁rai,求下式:
∑ 1 ≤ l 1 ≤ r 1 ≤ n ∑ r 1 < l 2 ≤ r 2 ≤ n ∑ r 2 < l 3 ≤ r 3 ≤ n f ( l 1 , r 1 ) × f ( l 2 , r 2 ) × f ( l 3 , r 3 ) \sum_{1 \le l_1 \le r_1 \le n} \sum_{r_1 <l_2 \le r_2 \le n}\sum_{r_2 < l_3\le r_3 \le n} f(l_1,r_1)\times f(l_2,r_2) \times f(l_3,r_3) 1≤l1≤r1≤n∑r1<l2≤r2≤n∑r2<l3≤r3≤n∑f(l1,r1)×f(l2,r2)×f(l3,r3)
1 ≤ n ≤ 2 × 1 0 5 1 \le n \le 2\times 10^5 1≤n≤2×105, 0 ≤ a i ≤ 1 0 9 0 \le a_i \le 10^9 0≤ai≤109。
解法:显然对于本题应该考虑拆位。
首先考虑如何求
∑ 1 ≤ l 1 ≤ r 1 ≤ n f ( l 1 , r 1 ) \sum_{1 \le l_1 \le r_1 \le n} f(l_1,r_1) 1≤l1≤r1≤n∑f(l1,r1)
以拆位视角,仅考虑第 k k k 个二进制位的贡献。当对 { a } \{a\} {a} 进行前缀异或操作(得到 { s } i = 0 n \{s\}_{i=0}^n {s}i=0n)后,对于所有右端点 r 1 r_1 r1 确定的区间 [ x , r 1 ] [x,r_1] [x,r1],该区间在该二进制位上贡献为 1 1 1 的次数为所有 s x − 1 ≠ s r 1 s_{x-1} \ne s_{r_1} sx−1=sr1 且满足 x ≤ r 1 x \le r_1 x≤r1 的下标 x x x 数目,且每次出现对上式的贡献都为 1 1 1。因而可以考虑随着 r 1 r_1 r1 增大维护数组 P 0 / P 1 P_0/P_1 P0/P1 表示该位上前缀异或和 s x s_x sx 是 0 / 1 0/1 0/1 的下标 x x x 次数。
因而综合每一个二进制位,我们可以维护 { S 1 } i = 1 n \{S_1\}_{i=1}^n {S1}i=1n 数组,其中 S 1 , i S_{1,i} S1,i 表示区间右端点固定在 i i i 的区间的异或和的和。这时考虑对 S 1 , i S_{1,i} S1,i 进行前缀和操作得到 { T 1 } \{T_{1}\} {T1} 数组,则 T 1 , n T_{1,n} T1,n 即为 ∑ 1 ≤ l 1 ≤ r 1 ≤ n f ( l 1 , r 1 ) \displaystyle \sum_{1 \le l_1 \le r_1 \le n} f(l_1,r_1) 1≤l1≤r1≤n∑f(l1,r1)。
接下来考虑求
∑ 1 ≤ l 1 ≤ r 1 ≤ n ∑ r 1 < l 2 ≤ r 2 ≤ n f ( l 1 , r 1 ) × f ( l 2 , r 2 ) \sum_{1 \le l_1 \le r_1 \le n}\sum_{r_1 < l_2 \le r_2 \le n} f(l_1,r_1)\times f(l_2,r_2) 1≤l1≤r1≤n∑r1<l2≤r2≤n∑f(l1,r1)×f(l2,r2)
首先还是考虑拆位。考虑当 r 2 r_2 r2 固定为 i i i 时,所有区间 [ x , r 2 ] [x,r_2] [x,r2] 如果该位上异或和为 1 1 1,必须满足 s x − 1 ≠ s r 2 s_{x-1} \ne s_{r_2} sx−1=sr2 且满足 x ≤ r 2 x \le r_2 x≤r2 的下标 x x x,且每个 x x x 对答案的贡献为 T 1 , x − 1 T_{1,x-1} T1,x−1——即第一区间的右端点在 [ 1 , x − 1 ] [1,x-1] [1,x−1] 上。因而可以考虑随着 r 1 r_1 r1 增大维护数组 P 0 / P 1 P_0/P_1 P0/P1 表示该位上前缀异或和 s x s_x sx 是 0 / 1 0/1 0/1 的 T 1 , x T_{1,x} T1,x 的和。
这时综合每个二进制位,就可以维护 { S 2 } i = 1 n \{S_2\}_{i=1}^n {S2}i=1n 数组表示第二区间右端点固定在 i i i 的区间的异或乘积和。
因而对于如果有 k k k 个不相交区间,也可以类似处理。本题 k = 3 k=3 k=3,因而只需要进行三次。总复杂度 O ( k n log a i ) \mathcal O(kn \log a_i) O(knlogai)。
#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = b; i <= i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = b; i >= i##_; --i)using namespace std;
using ll = long long;
const int N = 2e5 + 5, M = 30, P = 998244353;
int n, a[N], f[N], s[N];
void Solve() {scanf("%d", &n);s[0] = 1;fp(i, 1, n) scanf("%d", a + i), a[i] ^= a[i - 1], s[i] = 1;fp(_, 1, 3) {fp(j, 0, M) {ll c[2] = {s[0], 0};fp(i, 1, n) {f[i] = (f[i] + (c[(a[i] >> j & 1) ^ 1] << j)) % P;(c[a[i] >> j & 1] += s[i]) %= P;}}s[0] = 0;fp(i, 1, n) s[i] = (s[i - 1] + f[i]) % P, f[i] = 0;}printf("%d\n", s[n]);
}
int main() {int t = 1;// scanf("%d", &t);while (t--) Solve();return 0;
}