edu 场神秘分类讨论,神秘推式子。
一次偶数那之后都是偶数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> using namespace std;int main () { int J; cin >> J; while (J--) { int n, k; cin >> n >> k; int res = 0 ; if (n & 1 ) { n = max (0 , n - k); res++; } k--; res += (n + k - 1 ) / k; cout << res << endl; } return 0 ; }
Hint 1 矛盾点在,最左右两边的数很可能最后取。什么时候只能最后取?什么时候无限制?
$k$ 比较大的时候一定能选到 $k+1$ 个较大值,先选边上的较大值,向两边选完边上其它所有数后,最后选中间的。具体来说是 $k>1$。
只有 $k=1$ 时,先选某个在中间的数,然后向两边扩散。
分 $k>1$ 和 $k=1$ 两种情况讨论。
时间复杂度 $\operatorname{O}(n\log n)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std;using ll = long long ;int main () { int J; cin >> J; while (J--) { int n, k; cin >> n >> k; vector<int > a (n) ; for (int i = 0 ; i < n; i++) { cin >> a[i]; } if (k == 1 ) { cout << max ({ a[0 ] + *max_element (a.begin () + 1 , a.end ()), a[n - 1 ] + *max_element (a.begin (), a.end () - 1 ) }) << endl; } else { k++; sort (a.begin (), a.end (), greater<>()); cout << accumulate (a.begin (), a.begin () + k, 0ll ) << endl; } } return 0 ; }
经典双指针 / 二分
Hint 1 再仔细读题,必须选两种颜色。因此 $a_{i}=n$ 是假的。
$a{i}$ 取不到 $n$(否则只有一种颜色),先将 $a {i}$ 与 $n-1$ 取较小值。
Hint 2 对于确定的 $a{i},a {j}$,贡献多少答案?
$a{i}+a {j}-n+1$。当然这个数必须是正的才有贡献,也即 $a{i}+a {j} \geqslant n$。
Hint 3 对于确定的 $a{i}$,如何对所有满足 $j>i$ 的 $a {i},a_{j}$ 求答案?
升序排序后,先找到满足 $a{i}+a {p} \geqslant n$ 最小的 $p$。那么答案是 $\sum{j \geqslant p} (a {i}+a{j}-n+1)=\mathrm{num}\cdot(a {i}+n-1)+\sum{j \geqslant p} a {j}$。
上面 $\sum{j \geqslant p} a {j}$ 是后缀和,可以预处理求得。
时间复杂度 $\operatorname{O}(n\log n)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;using ll = long long ;int main () { int J; cin >> J; while (J--) { int n, m; cin >> n >> m; vector<int > a (m) ; ll res = 0 ; for (int i = 0 ; i < m; i++) { cin >> a[i]; if (a[i] == n) { a[i]--; } } sort (a.begin (), a.end ()); vector<ll> suf (m + 1 ) ; for (int i = m - 1 ; i >= 0 ; i--) { suf[i] = suf[i + 1 ] + a[i]; } for (int i = 0 ; i < m; i++) { auto it = lower_bound (a.begin () + i + 1 , a.end (), n - a[i]); if (it != a.end ()) { int num = a.end () - it; ll now = suf[it - a.begin ()] + 1ll * num * (a[i] - n + 1 ); now *= 2 ; res += now; } } cout << res << endl; } return 0 ; }
上面代码也可以用双指针优化为线性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;using ll = long long ;int main () { int J; cin >> J; while (J--) { int n, m; cin >> n >> m; vector<int > a (m) ; ll res = 0 ; for (int i = 0 ; i < m; i++) { cin >> a[i]; if (a[i] == n) { a[i]--; } } sort (a.begin (), a.end ()); ll suf = 0 ; for (int l = 0 , r = m; l < m; l++) { if (r <= l) { suf -= a[r++]; } while (r - 1 > l && a[l] + a[r - 1 ] >= n) { suf += a[--r]; } ll now = suf + 1ll * (m - r) * (a[l] - n + 1 ); now *= 2 ; res += now; } cout << res << endl; } return 0 ; }
Hint 1 从二进制考虑,每次操作相当于什么?
从二进制考虑,每次操作相当于砍掉一个长度为 $k$ 的后缀。要求砍之后,$x,y$ 相同,也就是说要保留 $x,y$ 的前缀(这里的前缀指去除前导零后的前缀,不要求按位对齐)。
下设 $x,y$ 不算公共前缀的长度分别为 $a,b$。
Hint 2 如果对 $x$ 总共除掉 $2^{i}$,对 $y$ 总共除掉 $2^{j}$,$i,j$ 应满足什么条件?
首先 $i = a,j =b$ 是可以的。也可以多除一点,$i=a+1,j=b+1$ 也行,再多也可以,可以写成 $i=a+\Delta,j=b+\Delta$。
如果 $i,j$ 超过了 $x,y$ 二进制的长度,那么就没有 $+\Delta$ 的限制了。
可以枚举满足上述条件的 $(i,j)$。至多枚举 $\operatorname{O}(\log^{2}V)$ 次。
Hint 3 最后一步,已经确定对 $x$ 总共除掉 $2^{i}$,对 $y$ 总共除掉 $2^{j}$,如何求最小代价?
不妨换个思路,直接求所有 $(i,j)$ 的答案 $dp_{i,j}$。
用类似背包的思路 DP,相当于是一个物品 $k$ 可以分给第一个背包也可以给第二个背包也可以不给,即更新 $dp{i+k,j}\leftarrow dp {i,j}$,以及 $dp{i,j+k}\leftarrow dp {i,j}$。(具体见代码)
预处理的时间复杂度 $\operatorname{O}(\log ^{3}V)$。
总时间复杂度 $\operatorname{O}(\log^{3}V+T\log^{2}V)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <bits/stdc++.h> using namespace std;using ull = unsigned long long ;const ull inf = 0x3f3f3f3f3f3f3f3f ;string BIN (ull x) { string res; while (x) { res += (x & 1 ) + '0' ; x >>= 1 ; } return res; } template <typename T>void chmin (T& x, const T& y) { if (x > y) x = y; } int main () { int J; cin >> J; vector dp (60 , vector<ull>(60 , inf)) ; dp[0 ][0 ] = 0 ; for (int k = 0 ; k < 60 ; k++) { auto ndp = dp; for (int x = 0 ; x < 60 ; x++) { for (int y = 0 ; y < 60 ; y++) { if (x + k < 60 ) chmin (ndp[x + k][y], dp[x][y] + (1ull << k)); if (y + k < 60 ) chmin (ndp[x][y + k], dp[x][y] + (1ull << k)); } } dp = ndp; } while (J--) { ull X, Y; cin >> X >> Y; string x = BIN (X), y = BIN (Y); ull res = inf; for (int j = x.size (); j < 60 ; j++) { for (int k = y.size (); k < 60 ; k++) { chmin (res, dp[j][k]); } } while (!x.empty () && !y.empty () && x.back () == y.back ()) { x.pop_back (); y.pop_back (); } for (int j = 0 ; x.size () + j < 60 && y.size () + j < 60 ; j++) { chmin (res, dp[x.size () + j][y.size () + j]); } cout << res << endl; } return 0 ; }
Hint 1 $X{i,k}=X {j,k}$ 意味着什么?$a,b$ 分别可能有多少种取值?
$X{i,k}=X {j,k}\iff a{i}\oplus b {k}=a{j}\oplus b {k}\iff a{i}=a {j}$。也就是说,一旦两个元素相同,那么这两列的表头相同,这两列对应位置也就全部相同。(我赛时没想到)
若要求 $X$ 只有两种取值,那 本质不同的只有两行两列 ,也就是说 $a,b$ 分别最多有两种取值。
若有一者仅有一种取值,方案数很容易算,这里从略。下面只讨论 $a,b$ 都有两种取值的情形。不妨设前两行和前两列本质不同,并设 $a{1} < a {2},\ b{1} < b {2}$。
Hint 2 找到 $a{1},b {1},a{2},b {2}$ 的等价约束条件。
现在 $X{1,1} \neq X {1,2},X{1,1} \neq X {2,1}$,由要求只有两个不同的数,就必须 $X{1,2}=X {2,1}$,即 $a{1}\oplus b {1}\oplus a{2}\oplus b {2}=0$。另一方面,当 $a{1}\oplus b {1}\oplus a{2}\oplus b {2}=0$ 时,$X{1,2}=X {2,1},X{1,1}=X {2,2}$,满足只有两个不同的数的要求。
现在的问题是,求满足 $0 \leqslant a{1} < a {2} \leqslant A,\ 0 \leqslant b{1} < b {2} \leqslant B$,且 $a{1}\oplus a {2}= b{1}\oplus b {2}$ 的数量。
队友给的妙妙思路~
题意 称一个整数序列为美丽的,如果:
每个元素左边有个元素比它小,且 每个元素右边有个元素比它大。 给定一个数组,求最长美丽子序列长度。
Hint 1 如果确定子序列左右端点,最长美丽子序列长度是多少?
问题等价于左端点 $\ell$ 为唯一最小值,右端点 $r$ 为唯一最大值。那么如果确定子序列左右端点,只需计算区间 $\ell<i<r$ 中在 $a{l}<a {i}<a_{r}$ 范围内的下标 $i$ 数量 cnt,长度即为 cnt + 2。
Hint 2 需要枚举哪些左右端点?
只需选取 前缀最小值 为左端点, 后缀最大值 为右端点。因为如果 $\ell$ 左边还有比 $a_{\ell}$ 小的数,加上这个数一定更优,就不需要枚举这个 $\ell$ 了。
Hint 3 考虑双指针,枚举一个端点,动态求解另一个端点的最优答案。改变一者时,答案会有什么变化?
枚举右端点 $r$,「激活」合法($\ell<i<r$ 且 $a{l}<a {i}<a_{r}$)的下标 $i$,把合法总数量记录在左端点 $\ell$ 上,也就是给每个左端点 $\ell$ 记录当前最长美丽子序列长度,如果多一个数合法就给每个 $\ell$ 都 +1,少一个合法就都 -1。
右端点从 $r$ 移动到 $r’<r$ 时,$r’<i<r$ 要全部取消激活。这里按下标枚举 $i$。
并且由于 $a{r’}>a {r}$,$1 \leqslant i<r’$ 中满足 $a{r} \leqslant a {i}<a_{r’}$ 的数要激活。这里按值枚举 $i$。
在整个过程中,每个数只会激活一次取消激活一次,复杂度已经达到线性。但问题在,把合法总数量记录在左端点 $\ell$ 上需要枚举很多 $\ell$,能否优化?
Hint 4 每次激活 $i$ 需要记录的左端点 $\ell$,有什么特点?如何快速找到?
每次激活 $i$ 需要记录的左端点 $\ell$ 其实是连续的区间 $[L,R)$。把合法总数量记录在左端点 $\ell$ 上,只需要做区间加。为求最优答案,只需要区间最大值。用线段树即可维护。
这个区间 $[L,R)$ 中的数 $j$ 满足 $a{j}<a {i}\land j<i$,也即 $L$ 对应于最小满足 $a{j}<a {i}$ 的 $j$,$R$ 对应于最小满足 $j \geqslant i$ 的 $j$。这两者都可以二分求得。
时间复杂度 $\operatorname{O}(n\log^{2}n)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <bits/stdc++.h> using namespace std;using ll = long long ;template <class Info , class Mark >struct SegmentTree {}; struct Mark { int add = 0 ; void apply (const Mark& o, int L, int R) & { add += o.add; } }; struct Info { int mx = 0 ; void apply (const Mark& o, int L, int R) & { mx += o.add; } void apply (const Info& o) & { mx = max (mx, o.mx); } friend Info operator + (const Info& l, const Info& r) { return { max (l.mx, r.mx) }; } }; int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ), cout.tie (nullptr ); int J; cin >> J; while (J--) { int n; cin >> n; vector<int > a (n) ; for (int i = 0 ; i < n; i++) { cin >> a[i]; } auto all = a; sort (all.begin (), all.end ()); all.erase (unique (all.begin (), all.end ()), all.end ()); vector<vector<int >> adj (all.size ()); for (int i = 0 ; i < n; i++) { a[i] = lower_bound (all.begin (), all.end (), a[i]) - all.begin (); adj[a[i]].push_back (i); } if (is_sorted (a.rbegin (), a.rend ())) { cout << 1 << endl; continue ; } vector<int > pre; for (int i = 0 ; i < n; i++) { if (pre.empty () || a[i] < a[pre.back ()]) { pre.push_back (i); } } vector<int > suf; for (int i = n - 1 ; i >= 0 ; i--) { if (suf.empty () || a[i] > a[suf.back ()]) { suf.push_back (i); } } int ans = 1 ; SegmentTree<Info, Mark> tree (0 , n) ; auto add = [&](int i, int v) { int R = lower_bound (pre.begin (), pre.end (), i) - pre.begin (); int L = lower_bound (pre.begin (), pre.end (), a[i], [&](int id, int val) { return a[id] >= val; }) - pre.begin (); tree.apply (L, R, Mark{ v }); }; int lstr = n; a.push_back (0 ); for (auto r : suf) { for (int i = r + 1 ; i < lstr; i++) { if (a[i] < a[lstr]) add (i, -1 ); } for (int v = a[lstr]; v < a[r]; v++) { for (auto i : adj[v]) { if (i < r) add (i, 1 ); } } ans = max (ans, 2 + tree.query (0 , n).mx); lstr = r; } cout << ans << endl; } return 0 ; }