【做题笔记】板刷 AtCoder
[ABC364D] K-th Nearest
很好想的题目。
首先可以考虑到答案具有单调性,所以对于每一次询问用二分处理即可。
然后考虑如何判合法。我们需要找到所有 \(a_i-b\) 中 \(\le x\) 且 \(\ge -x\) 的个数。可以现将 \(a_i\) 排好序,最后用两个二分统计个数看是否 \(\ge k\) 即可。
时间复杂度 \(O(q \log^2 n)\)。
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
int n, q, a[N], k, b;
bool check(int x) {
int L = n+1, R = 0;
int l = 1, r = n;
while(l <= r) {
int mid = l + r >> 1;
if(a[mid] - b <= x) {
R = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
l = 1, r = n;
while(l <= r) {
int mid = l + r >> 1;
if(a[mid] - b >= -x) {
L = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
return (R-L+1) >= k;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
For(i,1,n) cin >> a[i];
sort(a + 1, a + n + 1);
while(q--) {
int l = 0, r = 2e8, ans = 0;
cin >> b >> k;
while(l <= r) {
int mid = l + r >> 1;
if(check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans << '\n';
}
return 0;
}
[ARC172A] Chocolate
第一个想法是将所有的矩形面积求和并与给定区域面积作比较,然后会被最后一个样例卡掉。手推一下发现这个只是一个必要条件。
考虑到将较大的矩形放在左上角(角落)是最优的。然后考虑缩小判断范围,假设当前要放置长度为 \(a\) 的矩形。则放置范围为 \(h:[1,2^a\times\left\lfloor\dfrac{H}{2^a}\right\rfloor]\),\(w:[1,2^a\times\left\lfloor\dfrac{W}{2^a}\right\rfloor]\)。考虑到如果所有长度大于 \(a\) 的矩形之和小于等于放置范围的面积之和,则当前状态合法。
于是枚举 \(a\in[0,25]\),然后依次判断即可。
时间复杂度 \(O(n^2)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e3 + 5;
int n, m, p, a[N];
int sum(int r) {
int sum0 = 0;
For(i,1,p) if(a[i] >= r) sum0 += ((1ll << a[i]) * (1ll << a[i]));
return sum0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> p;
For(i,1,p) cin >> a[i];
For(r,0,25) {
int H = (n / (1ll << r)), W = (m / (1ll << r));
int k = H * W * (1ll << r) * (1ll << r);
if(sum(r) > k) {
cout << "No\n";
return 0;
}
}
cout << "Yes\n";
return 0;
}
[AGC032E] Modulo Pairing
考虑到如果没有模数,则可按照贪心思路将 \(a\) 序列从小到大排序,然后将最大和最小配对,次大和次小配对...以此类推。
发现 \(a_i \in [0,m)\),于是 \(\forall i,j\in[1,n], a_i+a_j\le2m-2\)。可以考虑到将 \(a\) 序列分成两个部分,使得左部的答案贡献 \(< m\),右部的答案贡献 \(\ge m\)(未取模之前)。并且总答案贡献最小。然后对于两部分分别进行贪心配对计算答案。
发现这个分割点具有单调性于是直接二分即可。二分同时计算即可。
时间复杂度 \(O(n \log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
int n, m, ans = INT_MAX, a[N];
bool check(int x) {
int res = 0;
For(i,1,x) {
if(a[i] + a[x - i + 1] >= m) return 1;
res = max(res, a[i] + a[x - i + 1]);
}
For(i,x+1,2*n) {
if(a[i] + a[(2*n) - i + x + 1] < m) return 0;
res = max(res, a[i] + a[(2*n) - i + x + 1] - m);
}
ans = min(ans, res);
return 1;
}
signed main() {
//freopen("duck.in", "r", stdin);
//freopen("duck.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,2*n) cin >> a[i];
sort(a + 1, a + 2 * n + 1);
int l = 0, r = n;
while(l <= r) {
int mid = l + r >> 1;
if(check(mid * 2)) {
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans << '\n';
return 0;
}
</details>
# [[ABC203D] Pond](https://www.luogu.com.cn/problem/AT_abc203_d)
有一点整体二分的感觉。
考虑先二分出中位数的最小值 $x$。然后对于原矩阵统计出 01 矩阵:当 $a_{i,j}$ 大于 $x$ 时,记为 $1$;反之记为 $0$。然后枚举子矩阵的左上角的端点坐标,同时用前缀和统计出 $((x1,y1),(x2,y2))$ 中的 $1$ 的个数。即 $> x$ 的个数。若存在矩阵 $>x$ 的个数小于 $\left \lfloor \frac{k^2}{2} \right \rfloor + 1$。则将二分的范围上界变小,否则下界变大。
最后二分出来的即为最终答案。(考虑原矩阵中存在子矩阵中 $> x$ 的数的个数小于 $\left \lfloor \frac{k^2}{2} \right \rfloor + 1$,并且这个数存在于这个矩阵并且尽可能的小。所以这个数肯定是某子矩阵数集的中位数,并且满足最小 -> ~~这几点一开始没有想清楚~~)。
时间复杂度 $O(n^2 \log n)$。
<details>
<summary>点击查看代码</summary>
```cpp
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 8e2 + 10;
int n, k, a[N][N], b[N][N], c[N][N], ans = -1;
int ask(int x1, int y1, int x2, int y2) {
return (b[x2][y2] - b[x1-1][y2] - b[x2][y1-1] + b[x1-1][y1-1]);
}
bool check(int x) {
For(i,1,n) {
For(j,1,n) {
b[i][j] = b[i-1][j] + b[i][j-1] - b[i-1][j-1] + (a[i][j] > x);
}
}
bool f = 0;
For(i,1,n-k+1) {
For(j,1,n-k+1) {
int sum = ask(i, j, i+k-1, j+k-1);
f |= (sum < (k*k)/2+1);
}
}
return f;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> k;
For(i,1,n) For(j,1,n) cin >> a[i][j];
int l = 0, r = 1e9;
while(l <= r) {
int mid = l + r >> 1;
if(check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans << '\n';
return 0;
}
[ABC310E] NAND repeatedly
很典的套路。
考虑暴力从 \(i\) 向后枚举求解答案的过程,每个位置的答案贡献状态的 \(0/1\) 都是由前一位的 \(0/1\) 状态决定的。不妨将所有这些状态的转移过程打包。
记 \(s_{0/1,i}\) 表示当前 \(i\) 位答案贡献状态为 \(0/1\) 的个数。考虑转移状态:
- 若 \(a_i=0\),则 \(i-1\) 位的所有 \(0/1\) 都可转移至当前 \(i\) 为的 \(1\) 状态,并且 \(0\) 状态贡献加一(算上 \(a_i\) 本身)。即 \(s_{0,i}=1,s_{1,i}=s_{0,i-1}+s_{1,i-1}\);
- 若 \(a_i = 1\),则 \(i-1\) 位的所有 \(0\) 都可转移至当前 \(i\) 为的 \(1\) 状态, \(i-1\) 位的所有 \(1\) 都可转移至当前 \(i\) 为的 \(0\) 状态,并且 \(1\) 状态贡献加一(算上 \(a_i\) 本身)。即 \(s_{0,i}=s_{1,i-1},s_{1,i}=s_{0,i-1}+1\);
最后把所有的 \(1\) 状态加和即可。
时间复杂度 \(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e6 + 10;
int n, s[2][N], ans = 0;
char a[N];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
cin >> (a + 1);
s[a[1]-'0'][1]++;
For(i,2,n) {
if(a[i] == '0') {
s[0][i]++;
s[1][i] = s[0][i-1] + s[1][i-1];
} else {
s[0][i] = s[1][i-1];
s[1][i] = s[0][i-1] + 1;
}
}
For(i,1,n) ans += s[1][i];
cout << ans << '\n';
return 0;
}
[ABC311E] Defect-free Squares
考察对待问题的转化。
直接枚举 \(i,j,k\) 表示以 \((i,j)\) 为左上角,长度为 \(k\) 的正方形是否合法,用前缀和 \(O(1)\) check。这样可以做到 \(O(n^3)\)。
换一个角度,记 \(f_{i,j}\) 表示前 \(i\) 行 \(j\) 列所有合法正方形的数量和,记 \(g_{i,j}\) 表示以\((i,j)\) 为右下角的合法正方形数量。转移则为:
只要快速求解 \(g_{i,j}\) 即可。
考虑正方形的合法长度具有单调性,直接二分长度,再 \(O(1)\) 用前缀和 check 即可。这样计算 \(g_{i,j}\) 的时间复杂度为 \(O(\log n)\)。
总时间复杂度为 \(O(n^2\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e3 + 10;
int n, m, q, sum[N][N], ans;
int ask(int x1, int y1, int x2, int y2) {
return (sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]);
}
signed main() {
cin >> n >> m >> q;
For(i,1,q) {
int x, y; cin >> x >> y;
sum[x][y]++;
}
For(i,1,n) {
For(j,1,m) {
sum[i][j] += (sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1]);
}
}
For(i,1,n) {
For(j,1,m) {
int l = 1, r = min(i, j), num = 0;
while(l <= r) {
int mid = l + r >> 1;
if(ask(i - mid + 1, j - mid + 1, i, j) == 0) {
num = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
ans += num;
}
}
cout << ans << '\n';
return 0;
}
[ABC313E] Duplicate
需要注意力的一道题,没有反应过来 qwq。
先判断无解,显然如果有相邻的数同时不为 \(1\),则无解。
考虑每一位的贡献:当前位的贡献只与再它之后的数有关,考虑倒序计算贡献,记录当前操作数为 \(ans_i\),则删除第 \(i\) 位的后缀的代价为 \(ans_i=ans_{i+1}\times s_{i}+s_{i}\)。即每个数要删掉自己会多出 \(s_i\) 个 \(1\),而且这一轮之前的每一轮都会多删除 \(s_i\) 个 \(1\)。然后就递推即可。
时间复杂度 \(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 998244353
using namespace std;
const int N = 1e6 + 10;
char s[N];
int n, ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> (s + 1);
For(i,2,n-1) {
if(s[i] != '1' && s[i+1] != '1') {
cout << "-1\n";
return 0;
}
}
FOR(i,n,2) {
cout << ans << '\n';
ans = (ans * (s[i]-'0') % mod + (s[i]-'0') % mod) % mod;
}
cout << ans << '\n';
return 0;
}
[ABC315E] Prerequisites
思维题。
假设 \(x\) 的必读书为 \(y\),则 \(x\to y\)。按照此图拓扑排序,在 \(1\) 之后出现的数便是必读书了(答案为拓扑序倒序)。
由于跟 \(1\) 在队列中同时出现的数并不是必读书,于是将拓扑排序的队列改为大根堆即可。保证除 \(1\) 之外的数全部出队之后再统计答案。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
struct Node {
int v, nx;
} e[N << 1];
int n, c[N], h[N], tot, in[N], ans[N], len;
void add(int u, int v) {
e[++tot] = (Node){v, h[u]};
h[u] = tot;
}
void topu_sort() {
priority_queue<int> q;
For(i,1,n) if(!in[i]) q.push(i);
bool f = 0;
while(!q.empty()) {
int x = q.top();
q.pop();
if(f == 1) ans[++len] = x;
if(x == 1) f = 1;
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
if(!(--in[y])) {
q.push(y);
}
}
}
FOR(i,len,1) cout << ans[i] << ' ';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) {
cin >> c[i];
For(j,1,c[i]) {
int x; cin >> x;
add(i, x);
in[x]++;
}
}
topu_sort();
return 0;
}
[ABC317E] Avoid Eye Contact
很无聊的题。
所有人的视线是固定的,于是可以预处理出那些位置不能走。然后 bfs 一下最短路径即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e3 + 10;
const int dx[] = {0, 0, -1, 1};
const int dy[] = {-1, 1, 0, 0};
int n, m, sx, sy, ex, ey, ans[N][N];
struct Node {
int x, y;
};
char a[N][N];
bool vis[N][N], st[N][N];
bool stp(int x, int y) {
return (a[x][y] == '#' || a[x][y] == '>' || a[x][y] == '<' || a[x][y] == 'v' || a[x][y] == '^');
}
void bfs() {
queue<Node> q;
q.push((Node){sx, sy});
st[sx][sy] = 1;
while(!q.empty()) {
Node a = q.front();
q.pop();
For(i,0,3) {
int nx = a.x + dx[i], ny = a.y + dy[i];
if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && !vis[nx][ny] && !st[nx][ny]) {
st[nx][ny] = 1;
ans[nx][ny] = ans[a.x][a.y] + 1;
q.push((Node){nx, ny});
}
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) For(j,1,m) cin >> a[i][j];
For(i,1,n) {
For(j,1,m) {
if(vis[i][j]) continue;
if(a[i][j] == 'S') sx = i, sy = j;
if(a[i][j] == 'G') ex = i, ey = j;
if(a[i][j] == '>') {
vis[i][j] = 1;
for (int k = j; k <= m; ++k) {
vis[i][k] = 1;
if(stp(i, k+1)) break;
}
} else if(a[i][j] == '<') {
vis[i][j] = 1;
for (int k = j; k >= 1; --k) {
vis[i][k] = 1;
if(stp(i, k-1)) break;
}
} else if(a[i][j] == 'v') {
vis[i][j] = 1;
for (int k = i; k <= n; ++k) {
vis[k][j] = 1;
if(stp(k+1, j)) break;
}
} else if(a[i][j] == '^') {
vis[i][j] = 1;
for (int k = i; k >= 1; --k) {
vis[k][j] = 1;
if(stp(k-1, j)) break;
}
} else if(a[i][j] == '#') vis[i][j] = 1;
}
}
bfs();
cout << (!ans[ex][ey] ? -1 : ans[ex][ey]) << '\n';
return 0;
}
[ABC318E] Sandwiches
思维题。
记 \(cnt_i\) 表示 \(i\) 出现的个数,\(last_i\) 表示 \(i\) 第一次出现的位置,\(b_i\) 表示第一次 \(a_i\) 出现的位置到 \(i\) 中 \(\not= a_i\) 的个数。
答案的贡献即为 \(\sum\limits_{i=1}^n\sum\limits_{j=i}^n [a_j=a_i](b_j-b_i)\),用数组维护 \(b_i\) 的值域后缀和,再打删除标记。贡献计算动态维护即可。
时间复杂度 \(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 3e5 + 10;
int n, a[N], cnt[N], b[N], del[N], last[N], num[N], ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> a[i];
For(i,1,n) {
cnt[a[i]]++;
if(!last[a[i]]) last[a[i]] = i;
b[i] = (i - last[a[i]] + 1) - cnt[a[i]];
num[a[i]] += b[i];
}
For(i,1,n) {
num[a[i]] -= (b[i] - del[a[i]]) * cnt[a[i]];
ans += (num[a[i]]);
del[a[i]] = b[i];
cnt[a[i]]--;
}
cout << ans << '\n';
return 0;
}
[ABC320E] Somen Nagashi
很无聊的题目。
可以发现归队操作不用时刻维护。对于每一次操作 \(i\),只要找到第一个可放置时刻 \(\le T_i\) 的位置统计贡献然后修改可放置时刻即可。
一眼线段树维护最小时刻,然后线段树上二分即可。
时间复杂度 \(O(m\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
struct Node {
int l, r, val;
} t[N << 2];
int n, m, ans[N];
void pushup(int p) {
t[p].val = min(t[ls].val, t[rs].val);
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].val = 0;
if(l == r) return ;
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void find(int p, int k, int w, int dta) {
if(t[p].l == t[p].r) {
if(t[p].val <= k) {
ans[t[p].l] += w;
t[p].val = dta;
}
return ;
}
int mid = t[p].l + t[p].r >> 1;
if(t[ls].val <= k) find(ls, k, w, dta);
else find(rs, k, w, dta);
pushup(p);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
build(1, 1, n);
For(i,1,m) {
int t, w, s;
cin >> t >> w >> s;
find(1, t, w, t + s);
}
For(i,1,n) cout << ans[i] << '\n';
return 0;
}
[ABC324E] Joint Two Strings
很无聊的题。
先记录第 \(i\) 个字符串的最长子序列前缀 \(pre_i\),和最长子序列后缀 \(nxt_i\)。对于第 \(i\) 个字符串,能接在所有 \(j\),\(pre_j\ge len_t-nxt_i\) 的字符串的后面。
于是排序二分统计即可。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e5 + 10;
struct Node {
int pre, nxt;
} a[N];
string t, s[N];
int n, ans;
vector<int> pre;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> t;
For(i,1,n) {
cin >> s[i];
int k = 0;
for (int j = 0; j < s[i].size(); j++) {
if(s[i][j] == t[k]) k++;
}
a[i].pre = k;
pre.push_back(a[i].pre);
k = t.size()-1;
for (int j = s[i].size()-1; j >= 0; j--) {
if(s[i][j] == t[k]) k--;
}
a[i].nxt = t.size() - k - 1;
}
sort(pre.begin(), pre.end());
For(i,1,n) {
vector<int>::iterator x1;
x1 = lower_bound(pre.begin(), pre.end(), t.size()-a[i].nxt);
if(x1 != pre.end()) ans += (pre.size() - (x1 - pre.begin()));
}
cout << ans << '\n';
return 0;
}
[ABC325E] Our clients, please wait a moment
分层图好题(可以当做模板)。
将图分层:第一层为坐汽车的图,第二层为坐火车的图,由于坐火车之后不能再坐汽车,所以将第一层的图向第二层的图连单向边,最后跑 \(1\) 到 \(n\) 的最短路即可。
时间复杂度 \(O(n^2\log n^2)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int D = 1e3 + 10, N = 1e5 + 10, M = 5e6 + 10;
struct Node {
int v, w, nx;
bool operator < (const Node &x) const {
return x.w < w;
}
} e[M];
int n, a, b, c, d[D][D], h[N], dis[N], tot;
bool st[N];
void add(int u, int v, int w) {
e[++tot] = (Node){v, w, h[u]};
h[u] = tot;
}
void dijk() {
priority_queue<Node> q;
q.push((Node){1, 0});
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
while(!q.empty()) {
int x = q.top().v;
q.pop();
if(st[x]) continue;
st[x] = 1;
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
if(dis[y] > dis[x] + e[i].w) {
dis[y] = dis[x] + e[i].w;
if(!st[y]) q.push((Node){y, dis[y]});
}
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> a >> b >> c;
For(i,1,n) {
For(j,1,n) {
cin >> d[i][j];
}
}
For(i,1,n) {
add(i, i + n, 0);
For(j,1,i-1) {
add(i, j, d[i][j] * a);
add(j, i, d[i][j] * a);
add(i + n, j + n, d[i][j] * b + c);
add(j + n, i + n, d[i][j] * b + c);
}
}
dijk();
cout << min(dis[n], dis[n*2]) << '\n';
return 0;
}
[ABC327E] Maximize Rating
dp 好题。
先拆贡献, \(\begin{aligned}\frac{\sum_{i=1}^k(0.9)^{k-i}Q_i}{\sum_{i=1}^k(0.9)^{k-i}}\end{aligned}\) 与 \(\begin{aligned}\frac{1200}{\sqrt{k}}\end{aligned}\) 。枚举 \(k\),后半部分和前半部分的分母就定下来了。
要求 \(k\) 一定时分子最大,则设 \(dp_{i,j}\) 表示前 \(i\) 个数中选 \(k\) 个数的最大值,则:
- 选 \(i\):\(dp_{i,j}=dp_{i-1,j-1}\times 0.9+a_i\);
- 不选 \(i\):\(dp_{i,j}=dp_{i,j-1}\);
两者取最大值即可。
预处理出 \(dp\),接着枚举 \(k\) 算贡献取最大。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 1e9
using namespace std;
const int N = 5e3 + 10;
int n, a[N];
double ans = -inf, dp[N][N], sum[N];
signed main() {
// ios::sync_with_stdio(0);
// cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> a[i];
For(i,1,n) {
For(j,1,i) {
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] * 0.9 + a[i]);
}
}
double pw = 1;
For(i,1,n) {
sum[i] = sum[i-1] + pw;
pw *= 0.9;
}
For(k,1,n) {
ans = max(ans, (1.0 * dp[n][k] / sum[k]) - 1200.0 / sqrt(k));
}
printf("%.15lf\n", ans);
return 0;
}
[ABC319E] Bus Stops
思维题(性质题)。
可以发现 \(P_i\le 8\) 周期很小。总周期为 \(lcm(1,2,\dots,8)=840\)。所以算出出发时间为 \(0s\to839s\) 的所有到达时间,然后按照周期算即可。
若出发时间为 \(x\),则答案为 \(x+ans_{x\bmod 80}\)
时间复杂度 \(O(840n+q)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
struct Node {
int t, p;
} a[N];
int n, x, y, q, ans[840];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> x >> y;
For(i,1,n-1) cin >> a[i].p >> a[i].t;
For(i,0,839) {
int tim = i + x;
ans[i] = x;
For(j,1,n-1) {
while(tim % a[j].p != 0) tim++, ans[i]++;
tim += a[j].t, ans[i] += a[j].t;
}
ans[i] += y;
}
cin >> q;
while(q--) {
cin >> x;
cout << x + (ans[x % 840]) << '\n';
}
return 0;
}
[ABC328E] Modulo MST
很无聊的题。
暴力搜索树边,一边搜索一边判是否有环即可。写一个带撤销的并查集维护,所以不能路径压缩。
时间复杂度 \(O(可过)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 1e18
using namespace std;
const int N = 105;
struct Node {
int u, v, w;
} e[N];
int n, m, k, f[N], ans = inf;
int find(int x) {
return (x == f[x] ? x : find(f[x]));
}
void dfs(int x, int len, int num) {
if(len == n - 1) {
ans = min(ans, num);
return ;
}
For(i,x+1,m) {
int u = find(e[i].u), v = find(e[i].v);
if(u != v) {
f[u] = v;
dfs(i, len + 1, (num + e[i].w) % k);
f[u] = u;
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
For(i,1,n) f[i] = i;
For(i,1,m) {
cin >> e[i].u >> e[i].v >> e[i].w;
}
dfs(0, 0, 0);
cout << ans << '\n';
return 0;
}
[ABC330E] Mex and Update
很无聊的题。
动态维护全局 \(mex\) 板子。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 1e9
using namespace std;
const int N = 2e5 + 10;
struct Node {
int ls, rs, val;
} t[N * 50];
int n, m, rt, a[N], idx;
void pushup(int p) {
t[p].val = min(t[t[p].ls].val, t[t[p].rs].val);
}
void upd(int &p, int l, int r, int x, int k) {
if(!p) p = ++idx;
if(l == r) {
t[p].val += k;
return ;
}
int mid = l + r >> 1;
if(x <= mid) upd(t[p].ls, l, mid, x, k);
else upd(t[p].rs, mid + 1, r, x, k);
pushup(p);
}
int Find(int &p, int l, int r) {
if(!p) p = ++idx;
if(l == r) return l;
int mid = l + r >> 1;
if(t[t[p].ls].val == 0) return Find(t[p].ls, l, mid);
else return Find(t[p].rs, mid + 1, r);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) {
cin >> a[i]; upd(rt, 0, inf, a[i], 1);
}
while(m--) {
int x, k;
cin >> x >> k;
upd(rt, 0, inf, a[x], -1);
upd(rt, 0, inf, k, 1);
a[x] = k;
cout << Find(rt, 0, inf) << '\n';
}
return 0;
}
[ABC331E] Set Meal
很好的数据结构题。
可以给限制按照主菜编号第一关键词,配菜编号第二关键词排序。维护 \(b\) 数组的线段树最大值,然后枚举 \(i\),对于 \(i\to j\) 的限制单点修改,然后全局查询最大值,然后再恢复修改。最后取所有情况的最大值即可。
对于限制可以用指针扫,恢复修改操作即用数组存下来,统计完贡献之后再原封不动的改回去即可。
时间复杂度 \(O((n+q)\log m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e5 + 10;
struct node {
int x, y;
} q[N];
struct Node {
int l, r, val;
} t[N << 2];
int n, m, Q, a[N], b[N], c[N], ans;
bool cmp(node x, node y) {
return (x.x == y.x ? x.y < y.y : x.x < y.x);
}
void pushup(int p) {
t[p].val = max(t[ls].val, t[rs].val);
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if(l == r) {
t[p].val = b[l];
return ;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(p);
}
void upd(int p, int x, int k) {
if(t[p].l == t[p].r) {
t[p].val = k;
return ;
}
int mid = t[p].l + t[p].r >> 1;
if(x <= mid) upd(ls, x, k);
else upd(rs, x, k);
pushup(p);
}
int qry(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {
return t[p].val;
}
int mid = t[p].l + t[p].r >> 1, ans = 0;
if(l <= mid) ans = max(ans, qry(ls, l, r));
if(r > mid) ans = max(ans, qry(rs, l, r));
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> Q;
For(i,1,n) cin >> a[i];
For(i,1,m) cin >> b[i];
build(1, 1, m);
For(i,1,Q) cin >> q[i].x >> q[i].y;
sort(q + 1, q + Q + 1, cmp);
int j = 1, last = 1;
For(i,1,n) {
last = j;
while(q[j].x == i && j <= Q) {
c[j] = qry(1, q[j].y, q[j].y);
upd(1, q[j].y, 0);
j++;
}
ans = max(ans, a[i] + qry(1, 1, m));
For(k,last,j-1) {
upd(1, q[k].y, c[k]);
}
}
cout << ans << '\n';
return 0;
}
[ABC333E] Takahashi Quest
小思维题
考虑如何使怪物全部杀死的情况下药水最少,那就是需要打败 \(x_i\) 的怪兽时才拿 \(x_i\) 药水。
于是考虑逆序求解,用桶统计现在需要杀死那些怪物,然后捡起对应的药水。若当前没有需要杀死的 \(x_i\) 怪物,那就不捡 \(x_i\) 的药水即可。
再顺序求出药水数量的最大值最小即可。
时间复杂度 \(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
struct Node {
int t, x;
} a[N];
int n, cnt[N], Ans, ans[N], len, num;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> a[i].t >> a[i].x;
FOR(i,n,1) {
if(a[i].t == 1) {
if(cnt[a[i].x]) cnt[a[i].x]--, ans[++len] = 1;
else ans[++len] = 0;
} else {
cnt[a[i].x]++;
ans[++len] = -1;
}
}
For(i,1,n) {
if(cnt[i]) {
cout << "-1\n";
return 0;
}
}
FOR(i,len,1) {
if(ans[i] == 1) num++;
else if(ans[i] == -1) num--;
Ans = max(Ans, num);
}
cout << Ans << '\n';
FOR(i,len,1) {
if(ans[i] == -1) continue;
cout << ans[i] << ' ';
}
return 0;
}
[ABC334E] Christmas Color Grid 1
有点无聊的题。
用并查集维护绿色节点。枚举 \((i,j)\) 并计算将其填 #
造成的贡献,即统计合并其上下左右节点后的连通块个数(保证 \(a_{i,j}='.'\) )。
然后计算期望即可。
时间复杂度 \(O(n^2\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 998244353
using namespace std;
const int N = 1e3 + 10, M = 1e6 + 10;
const int dx[] = {0, 0, -1, 1};
const int dy[] = {-1, 1, 0, 0};
int n, m, ans1, ans2, Ans, cnt[M], f[M];
char a[N][N];
int id(int x, int y) {
return (x-1) * m + y;
}
int find(int x) {
return (x == f[x] ? x : f[x] = find(f[x]));
}
int qpow(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = (a * a) % mod) {
if(b & 1) res = (res * a) % mod;
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) {
For(j,1,m) f[id(i, j)] = id(i, j);
}
For(i,1,n) {
For(j,1,m) {
cin >> a[i][j];
if(a[i][j] == '.') ans2++;
else {
For(k,0,3) {
int nx = i + dx[k], ny = j + dy[k];
if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && a[nx][ny] == '#') {
f[find(id(nx, ny))] = find(id(i, j));
}
}
}
}
}
For(i,1,n) {
For(j,1,m) {
if(a[i][j] == '#') {
if(!cnt[find(id(i, j))]) Ans++;
cnt[find(id(i, j))]++;
}
}
}
memset(cnt, 0, sizeof cnt);
For(i,1,n) {
For(j,1,m) {
if(a[i][j] == '.') {
int num = 0;
For(k,0,3) {
int nx = i + dx[k], ny = j + dy[k];
if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && a[nx][ny] == '#') {
if(!cnt[find(id(nx, ny))]) num++;
cnt[find(id(nx, ny))]++;
num %= mod;
}
}
ans1 = (ans1 + (Ans - num + 1) % mod) % mod;
For(k,0,3) {
int nx = i + dx[k], ny = j + dy[k];
if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && a[nx][ny] == '#') {
cnt[find(id(nx, ny))]--;
}
}
}
}
}
cout << (ans1 * qpow(ans2, mod-2)) % mod << '\n';
return 0;
}
[ABC337E] Bad Juice
特别思维的好题。
最少的人数为 $\left \lceil \log_2n \right \rceil $,第 \(i\) 个人喝所有 \(1\) 至 \(n\) 的数中二进制第 \(i\) 位为 \(1\) 的数的果汁(\(i\in[0,n)\))。然后询问的答案即为 \(01\) 序列对应的十进制数。
注意当 \(01\) 序列全 \(0\) 时答案为 \(n\)。
代码不难,重在构造的思维。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 105;
int n, m, len, a[N];
char s[N];
vector<int> v;
signed main() {
cin >> n;
m = ceil(log2(n));
cout << m << endl;
For(i,0,m-1) {
vector<int>().swap(v);
For(j,1,n) {
if((j >> i) & 1) v.push_back(j);
}
cout << v.size() << ' ';
for (auto x : v) cout << x << ' ';
cout << endl;
}
cin >> (s + 1);
len = strlen(s + 1);
int ans = 0;
For(i,0,len-1) {
ans |= (1 << i) * (s[i+1] - '0');
}
if(!ans) ans = n;
cout << ans << '\n';
return 0;
}
[ABC321E] Complete Binary Tree
思维题,同样没反应过来。
可以发现题目给出的连边关系构成的是一棵左偏二叉树(完美二叉树)。
考虑 \(x\) 节点向下走 \(k\) 步可以到达的节点个数,记为 \(F(x,k)\)。发现对于 \(x\) 节点,不断的走左儿子 \(k\) 步到达的节点编号 \(l\),和不断的走右儿子到达的节点编号 \(r\)。\(F(x,k)=\min(r,n)-l+1\)。
对于 \(x\) 节点,除了走儿子之外,还可以走父辈节点。不停的跳父节点,对于 \(x\) 的父节点 \(fa_x\),\(x\) 走 \(k\) 步可以到达的节点的个数为 \(F(fa_x,k-1)-F(x,k-2)\)。(容斥一下)然后跳父节点的同时 \(k=k-1\) 同步更新,再统计答案即可。
时间复杂度 \(O(\log^2 n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
int T, n, x, k;
int F(int x, int k) {
if(k < 0) return 0;
int l = x, r = x;
For(i,1,k) {
l <<= 1, r = r << 1 | 1;
if(l > n) return 0;
}
return min(n, r) - l + 1;
}
void solve() {
cin >> n >> x >> k;
int ans = F(x, k);
while(x / 2) {
k--;
ans += F(x/2, k) - F(x, k-1);
x >>= 1;
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
while(T--) solve();
return 0;
}
[ABC322E] Product Development
很无聊但是很好玩的题。
设 \(dp_{i,p_1,p_2,p_3,p_4,p_5}\) 表示前 \(i\) 个计划中,选出第一种,第二种,第三种,第四种,第五种分别为 \(p_1,p_2,p_3,p_4,p_5\) 的方案数。然后刷表法递推转移即可。
因为 \(P\le5\),所以可能会有空出来的位置,设 \(lim_k\) 表示第 \(k\) 种属性的上限,若 \(k\) 未出现在属性中就令 \(lim_k=0\) 即可。显然当第 \(k\) 种属性 \(p_k+p_{i,k}\ge lim_k\) 可以视为转移至 \(lim_k\)。
答案即为 \(dp_{n,lim_1,lim_2,lim_3,lim_4,lim_5}\),时间复杂度 \(O(P^5n)\)。
写出来有一种脑干缺失的美感。(乐)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 105, M = 6;
struct Node {
int p[6];
} a[N];
int n, k, p, c[N], dp[N][M][M][M][M][M], lim[N], ans = inf;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> k >> p;
For(i,1,n) {
cin >> c[i];
For(j,1,k) cin >> a[i].p[j];
}
memset(dp, 0x3f, sizeof dp);
dp[0][0][0][0][0][0] = 0;
For(i,1,k) lim[i] = p;
For(i,0,n-1) {
For(p1,0,lim[1]) {
For(p2,0,lim[2]) {
For(p3,0,lim[3]) {
For(p4,0,lim[4]) {
For(p5,0,lim[5]) {
dp[i+1][p1][p2][p3][p4][p5] = min(dp[i+1][p1][p2][p3][p4][p5], dp[i][p1][p2][p3][p4][p5]);
dp[i+1][min(lim[1],p1+a[i+1].p[1])][min(lim[2],p2+a[i+1].p[2])][min(lim[3],p3+a[i+1].p[3])][min(lim[4],p4+a[i+1].p[4])][min(lim[5],p5+a[i+1].p[5])] =
min(dp[i+1][min(lim[1],p1+a[i+1].p[1])][min(lim[2],p2+a[i+1].p[2])][min(lim[3],p3+a[i+1].p[3])][min(lim[4],p4+a[i+1].p[4])][min(lim[5],p5+a[i+1].p[5])],
dp[i][p1][p2][p3][p4][p5] + c[i+1]);
if(p1+a[i+1].p[1] >= lim[1] && p2+a[i+1].p[2] >= lim[2] && p3+a[i].p[3] >= lim[3] && p4+a[i+1].p[4] >= lim[4] && p5+a[i+1].p[5] >= lim[5]) {
ans = min(ans, dp[i][p1][p2][p3][p4][p5] + c[i+1]);
}
}
}
}
}
}
}
ans = dp[n][lim[1]][lim[2]][lim[3]][lim[4]][lim[5]];
cout << (ans == inf ? -1 : ans) << '\n';
return 0;
}
[ABC329E] Stamp
考虑能看到的字符串,即给出的字符串 \(s\) 是被覆盖的最上层的字符串。
设 \(f_{i,j}\) 表示 \(s\) 前 \(i\) 位与 \(t\) 的前 \(j\) 位能否匹配。则:
- \(s_i=t_j\) 时,能够接着之前的进行匹配,即 \(f_{i-1,j-1}\);也能新开一个进行匹配,即 \(f_{i-1,m}\);
- \(s_i=t_1\) 时,只能新开一个进行匹配,即 \(f_{i-1,j}\);
答案即为 \(f_{n,m}\),时间复杂度 \(O(nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
int n, m, f[N][6];
char s[N], t[N];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
cin >> (s + 1) >> (t + 1);
f[0][0] = 1;
For(i,1,n) {
For(j,1,m) {
if(s[i] == t[j]) f[i][j] |= (f[i-1][j-1] | f[i-1][m]);
if(s[i] == t[1]) f[i][1] |= f[i-1][j];
}
}
cout << (f[n][m] ? "Yes" : "No") << '\n';
return 0;
}
[ABC335E] Non-Decreasing Colorful Path
有点意思的题。
考虑求解 \(1\) 到 \(n\) 的最长路,可以想到 \(dp\)。而经过所有点需要单调不降有能规定图的方向,将无向图转化为有向图。
但是会有一条边的两个点的点权相等的情况,这样就直接缩在一起就行了,用并查集合并即可。
然后就是 DAG 上 \(dp\) 就可以了。先拓扑排序,将排到的点的 \(dp_x\) 转移至它的后继节点 \(y\) 的 \(dp_y\)。拓扑排序最开始需要将所有入度为 \(0\) 的点入队而不是只入队 \(1\) 号节点。(需要保证拓扑排序的完整过程)。
时间复杂度 \(O(n+m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e6 + 10;
struct Node {
int v, nx;
} e[N];
struct node {
int u, v;
} E[N];
int n, m, a[N], h[N], tot, f[N], in[N], dp[N];
int find(int x) {
return (x == f[x] ? x : f[x] = find(f[x]));
}
void add(int u, int v) {
e[++tot] = (Node){v, h[u]};
h[u] = tot;
}
void topu_sort() {
queue<int> q;
memset(dp, -0x3f, sizeof dp);
dp[find(1)] = 1;
For(i,1,n) if(!in[find(i)] && find(i) == i) {
q.push(find(i));
}
while(!q.empty()) {
int x = q.front();
q.pop();
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
dp[y] = max(dp[y], dp[x] + 1);
if(!(--in[y])) {
q.push(y);
}
}
}
cout << max(0ll, dp[find(n)]) << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) cin >> a[i], f[i] = i;
For(i,1,m) {
int u, v;
cin >> u >> v;
E[i] = (node){u, v};
if(a[u] == a[v]) {
f[find(u)] = find(v);
}
}
For(i,1,n) f[i] = find(i);
For(i,1,m) {
int u = E[i].u, v = E[i].v;
if(a[u] == a[v]) continue;
if(a[find(u)] > a[find(v)]) swap(u, v);
add(find(u), find(v));
in[find(v)]++;
}
topu_sort();
return 0;
}
[ABC338E] Chords
有点意思的题。不过被秒了。
将圆上的点铺在数轴上,对于 \(n\) 个弦转化为 \(n\) 个在数轴上线段。然后就变成了判断线段是否相交(不包括左右端点,不包括包含和被包含的情况)。
先将线段按左端点排好序,右端点为第二关键字。然后从 \(1\) 往 \(n\) 扫,对于第 \(i\) 个线段,每次找到 \(j\in[1,i-1],r_j>l_i\) 的第一个 \(r_j\)。如果 \(l_i<r_j<r_i\),则说明线段相交,反之则否。这个过程只需要维护一个平衡树(set
亦可),支持插入与二分操作即可。(或者直接二分也可以)
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
struct Node {
int l, r;
} a[N];
int n;
bool cmp(Node x, Node y) {
return (x.l == y.l ? x.r < y.r : x.l < y.l);
}
set<int> s;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> a[i].l >> a[i].r;
For(i,1,n) {
if(a[i].l > a[i].r) swap(a[i].l, a[i].r);
}
sort(a + 1, a + n + 1, cmp);
For(i,1,n) {
int x = *(s.upper_bound(a[i].l));
if(a[i].l < x && x < a[i].r) {
cout << "Yes\n"; return 0;
}
s.insert(a[i].r);
}
cout << "No\n";
return 0;
}
[ABC326E] Revenge of "The Salary of AtCoder Inc."
在期望题中算简单题,但是我不会期望。
设 \(p(i)\) 表示选到 \(i\) 且有效的概率,而选中 \(i\) 可以先选中 \(j\in[0,i-1]\)。所以转移为 \(p(i)=\dfrac{1}{n}\sum\limits_{j=0}^{i-1}p(j)\) 。
对于 \(x\) 位置期望即为 \(E(x)=A_x\times p(x)\),\(\sum\limits_{x=1}^n E(i)\) 为总期望。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 998244353
using namespace std;
const int N = 3e5 + 10;
int n, a[N], sum = 1, ans;
int qpow(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = (a * a) % mod) {
if(b & 1) res = (res * a) % mod;
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> a[i];
sum = qpow(n, mod-2);
For(i,1,n) {
ans = (ans + a[i] * sum) % mod;
sum = (sum + (qpow(n, mod-2) * sum) % mod) % mod;
}
cout << ans << '\n';
return 0;
}
[ABC312E] Tangency of Cuboids
人类智慧题。
由于长方体无交,所以它们所在的方格系统中每一个方格最多属于一个长方体。对于一个长方体中某一个格子,它 \(6\) 个方向如果有属于别的长方体的格子,说明它们之间有共面。
可以对长方体所在格子暴力染色(由于无交,每个格子最多会被扫到一次,时间复杂度 \(O(M^3)\))。然后看它前面,右边,下面的的格子是否与它同色然后统计贡献即可。这个过程可以使用 set
维护,统计答案同时去重。
总时间复杂度 \(O(M^3+M^3\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e5 + 10, M = 105;
const int dx[3] = {0, 0, 1}, dy[3] = {0, 1, 0}, dz[3] = {1, 0, 0};
int n, a[M][M][M];
set<int> s[N];
signed main() {
cin >> n;
For(i,1,n) {
int x1, x2, y1, y2, z1, z2;
cin >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;
For(x,x1+1,x2) {
For(y,y1+1,y2) {
For(z,z1+1,z2) {
a[x][y][z] = i;
}
}
}
}
For(x,1,100) {
For(y,1,100) {
For(z,1,100) {
For(i,0,2) {
int nx = x + dx[i], ny = y + dy[i], nz = z + dz[i];
if(a[x][y][z] && a[nx][ny][nz] && a[x][y][z] != a[nx][ny][nz]) {
s[a[x][y][z]].insert(a[nx][ny][nz]);
s[a[nx][ny][nz]].insert(a[x][y][z]);
}
}
}
}
}
For(i,1,n) cout << s[i].size() << '\n';
return 0;
}
[ABC332E] Lucky bag
一眼看不出来的简单题。
可以看看贡献的真正面目是怎么样的:
只要最小化 \(\sum_{i=1}^{D} x_{i}^{2}\) 即可。
然后开始状压 \(dp\),设 \(dp_{i,S}\) 表示分成 \(i\) 组,当前划分 \(S\) 状态的最小贡献。则转移有:
答案即为 \(\dfrac{dp_{n,2^n-1}-\bar{x}^{2}}{D}\)。时间复杂度 \(O(D3^n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 20;
int n, d, w[N], s[1<<N], dp[N][1<<N];
signed main() {
cin >> n >> d;
For(i,0,n-1) {
cin >> w[i];
For(j,0,(1<<n)-1) {
if((j >> i) & 1) s[j] += w[i];
}
}
For(i,0,(1<<n)-1) s[i] *= s[i], dp[0][i] = 1e18;
dp[0][0] = 0;
For(i,1,d) {
For(S,0,(1<<n)-1) {
dp[i][S] = s[S];
for (int j = S; j; j = S & (j - 1)) {
dp[i][S] = min(dp[i][S], dp[i-1][j] + s[S ^ j]);
}
}
}
printf("%.15lf", 1.0*(dp[d][(1<<n)-1] * d - s[(1<<n)-1]) / (d * d));
return 0;
}
[ABC348F] Oddly Similar
垃圾题。暴力 \(O(n^2m)\) 后卡常。
点击查看代码
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
using namespace std;
const int N = 2e3 + 10;
int n, m, a[N][N], ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) For(j,1,m) cin >> a[i][j];
For(i,1,n) {
For(j,1,i-1) {
int num = 0;
For(k,1,m) {
num += (a[i][k] == a[j][k]);
}
if(num & 1) ans++;
}
}
cout << ans << '\n';
return 0;
}
[ABC321F] #(subset sum = K) with Add and Erase
有点意思的题。
可以发现没有删除操作就是一个 \(01\) 背包问题。然后加上了删除操作相当于把与其有关的转移全部撤销就好了。撤销也很简单,怎么转移来的怎么转移回去就行了(加变减)。
时间复杂度 \(O(QK)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 998244353
using namespace std;
const int N = 5e3 + 10;
int n, q, dp[N][N];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> q >> n;
dp[0][0] = 1;
For(i,1,q) {
char op; int x;
cin >> op >> x;
if(op == '+') {
For(j,0,n) {
dp[i][j] = dp[i-1][j];
if(j-x >= 0) dp[i][j] = (dp[i][j] + dp[i-1][j-x]) % mod;
}
} else {
For(j,0,n) dp[i][j] = dp[i-1][j];
For(j,0,n) if(j+x <= n && dp[i][j+x] && dp[i][j]) dp[i][j+x] = (dp[i][j+x] - dp[i][j] + mod) % mod;
}
cout << dp[i][n] << '\n';
}
return 0;
}
[ABC329F] Colored Ball
很无聊的题,但是没想清楚。
启发式合并,重点在如果 \(size(x)>size(y)\) 时,即 \(y\) 合并到 \(x\) 时会出现球在集合 \(x\) 而不在集合 \(y\) 中。此时只要 swap
两个 set
就可以了(这一点没有想到难崩)。
时间复杂度 \(O(q\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
int n, q, a[N];
set<int> s[N];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
For(i,1,n) cin >> a[i], s[i].insert(a[i]);
while(q--) {
int x, y;
cin >> x >> y;
if(s[x].size() < s[y].size()) {
for (auto i : s[x]) s[y].insert(i);
set<int>().swap(s[x]);
} else {
for (auto i : s[y]) s[x].insert(i);
set<int>().swap(s[y]);
swap(s[x], s[y]);
}
cout << s[y].size() << '\n';
}
return 0;
}
[ABC312F] Cans and Openers
有意思的贪心题。
将物品按照类型分类之后从大到小排序,先算出全选普通罐头的贡献。然后枚举选几个普通罐头,对应的可以用二分求出最小需要几个开罐器,然后对应的拉环罐头的个数也就定下来了。
最后将所有的情况的贡献取最大即可。注意,拉环罐头的个数可能在枚举时定为了负数或者超过了全部拉环罐头的个数。这时候需要判断一下边界并注意一下细节。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
int n, m, n1, n2, n3, a[N], b[N], c[N], sum1[N], sum2[N], sum3[N], ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) {
int op, x;
cin >> op >> x;
if(op == 0) {
a[++n1] = x;
} else if(op == 1) {
b[++n2] = x;
} else {
c[++n3] = x;
}
}
sort(a + 1, a + n1 + 1, [](int x, int y){return x > y;});
sort(b + 1, b + n2 + 1, [](int x, int y){return x > y;});
sort(c + 1, c + n3 + 1, [](int x, int y){return x > y;});
For(i,1,n1) sum1[i] = sum1[i-1] + a[i];
For(i,1,n2) sum2[i] = sum2[i-1] + b[i];
For(i,1,n3) sum3[i] = sum3[i-1] + c[i];
ans = (n1 == 0 ? 0 : sum1[min(m, n1)]);
For(i,1,n2) {
int l = 1, r = n3, p = -1;
while(l <= r) {
int mid = l + r >> 1;
if(sum3[mid] >= i) {
p = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
if(p == -1) continue;
if(m - p - i >= 0) ans = max(ans, sum1[min(m - p - i, n1)] + sum2[i]);
}
cout << ans << '\n';
return 0;
}
[ABC315F] Shortcuts
有意思的 \(dp\) 题。
首先可以设 \(dp_{i,j}\) 表示走完前 \(i\) 个点,跳过 \(j\) 个点的最小代价。
转移显然为:\(dp_{i,k}=\min\limits_{j=1}^{i-1}\{dp_{j,k-(i-j-1)}+dis(i, j)\}\),其中 \(dis(i,j)\) 为 \(i\) 号点到 \(j\) 号点的欧氏距离。
看似这个 \(dp\) 为 \(O(n^2\log M)\)(其中 \(M\) 为值域)的。可以发现跳过的点个数不能超过 \(30\) 个(\(\sqrt 2 \times 10^8 < 2^{30}\))。
所以实际上时间复杂度为 \(O(n\log^2 M)\) 的。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int M = 31, N = 1e4 + 10;
struct Node {
int x, y;
} a[N];
int n;
long double dp[N][M], ans = 1e10;
long double dis(int i, int j) {
return sqrt((a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y));
}
int qpow(int a, int b) {
if(b < 0) return 0;
int res = 1;
for (; b; b >>= 1, a = a * a) {
if(b & 1) res = res * a;
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) For(j,0,30) dp[i][j] = 1e10;
dp[1][0] = 0;
For(i,1,n) cin >> a[i].x >> a[i].y;
For(i,2,n) {
For(j,max(1ll,i-30),i-1) {
For(k,0,30) {
if(k >= (i-j-1)) dp[i][k] = min(dp[i][k], dp[j][k-(i-j-1)] + dis(i, j));
}
}
}
For(k,0,30) ans = min(ans, dp[n][k] + qpow(2, k-1));
printf("%.20Lf\n", ans);
return 0;
}
[ABC322F] Vacation Query
很有意思的数据结构题。
用线段树维护节点区间 \([l,r]\) 中最长前缀 \(0/1\),最长后缀 \(0/1\),以及最长连续 \(0/1\) 的长度。
pushup
:合并两个子节点的信息,即前缀信息可以由左儿子的前缀或者左儿子整段加右儿子前缀构成,后缀信息可以由右儿子的后缀或者右儿子整段加左儿子后缀构成。最后再把最长连续 \(0/1\) 的长度由左儿子最长连续 \(0/1\) 的长度和右儿子最长连续 \(0/1\) 的长度和左右儿子的前后缀拼接而成更新。- 区间翻转:打一个翻转标记,翻转时关于 \(0\) 的信息和关于 \(1\) 的信息交换。
- 区间查询:先找出所有的包含于查询区间的节点区间,在分别在它们的 \(LCA\) 合并即可。
时间复杂度 \(O(q\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 100000000
using namespace std;
const int N = 5e5 + 10;
struct Node {
int l, r;
int pre[2], suf[2];
int val[2];
int tag;
} t[N << 2];
int n, m;
char s[N];
void pushup(int p) {
For(i,0,1) {
t[p].val[i] = max({t[ls].val[i], t[rs].val[i], t[ls].suf[i] + t[rs].pre[i]});
t[p].pre[i] = max(t[ls].pre[i], (t[ls].val[i] != t[ls].r - t[ls].l + 1 ? -inf : t[ls].val[i] + t[rs].pre[i]));
t[p].suf[i] = max(t[rs].suf[i], (t[rs].val[i] != t[rs].r - t[rs].l + 1 ? -inf : t[rs].val[i] + t[ls].suf[i]));
}
}
void pushdown(int p) {
if(t[p].tag) {
swap(t[ls].val[0], t[ls].val[1]);swap(t[ls].pre[0], t[ls].pre[1]);swap(t[ls].suf[0], t[ls].suf[1]);
swap(t[rs].val[0], t[rs].val[1]);swap(t[rs].pre[0], t[rs].pre[1]);swap(t[rs].suf[0], t[rs].suf[1]);
t[ls].tag ^= 1;
t[rs].tag ^= 1;
t[p].tag = 0;
return ;
}
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].tag = 0;
if(l == r) {
t[p].val[s[l]-'0']++;
t[p].pre[s[l]-'0']++;
t[p].suf[s[l]-'0']++;
return ;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(p);
}
void upd(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {
swap(t[p].val[0], t[p].val[1]);
swap(t[p].pre[0], t[p].pre[1]);
swap(t[p].suf[0], t[p].suf[1]);
t[p].tag ^= 1;
return ;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1;
if(l <= mid) upd(ls, l, r);
if(r > mid) upd(rs, l, r);
pushup(p);
}
Node qry(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {
return t[p];
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1;
Node ans;
if(l <= mid && mid < r) {
Node Ls = qry(ls, l, r), Rs = qry(rs, l, r);
For(i,0,1) {
ans.val[i] = max({Ls.val[i], Rs.val[i], Ls.suf[i] + Rs.pre[i]});
ans.pre[i] = max(Ls.pre[i], (Ls.val[i] != Ls.r - Ls.l + 1 ? -inf : Ls.val[i] + Rs.pre[i]));
ans.suf[i] = max(Rs.suf[i], (Rs.val[i] != Rs.r - Rs.l + 1 ? -inf : Rs.val[i] + Ls.suf[i]));
}
return ans;
} else if(l <= mid) {
return qry(ls, l, r);
} else if(r > mid) {
return qry(rs, l, r);
}
return ans;
}
signed main() {
cin >> n >> m >> (s + 1);
build(1, 1, n);
while(m--) {
int op, l, r;
cin >> op >> l >> r;
if(op == 1) {
upd(1, l, r);
} else {
cout << qry(1, l, r).val[1] << '\n';
}
}
return 0;
}
[ABC324F] Beautiful Path
板的 \(0/1\) 规划。
二分答案 \(x\),由于图为 \(DAG\),令边权为 \(a_i-xb_i\),然后跑最长路。如果存在 \(1\to n\) 的路径边权和 \(>0\) 则说明答案可行。
时间复杂度 \(O(m\log M)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define eps 1e-10
#define inf 1e10
using namespace std;
const int N = 2e5 + 10;
struct Node {
int u, v;
long double a, b;
} e[N << 1];
int n, m;
long double dp[N], l, r;
bool cmp(Node x, Node y) {
return (x.u == y.u ? x.v < y.v : x.u < y.u);
}
bool check(long double x) {
For(i,1,n) dp[i] = -inf;
dp[1] = 0;
For(i,1,m) {
dp[e[i].v] = max(dp[e[i].v], dp[e[i].u] + (e[i].a - x * e[i].b));
}
return dp[n] > 0;
}
signed main() {
cin >> n >> m;
For(i,1,m) cin >> e[i].u >> e[i].v >> e[i].a >> e[i].b;
sort(e + 1, e + m + 1, cmp);
l = 0, r = 1e4;
while(r - l > eps) {
long double mid = (l + r) / 2;
if(check(mid)) {
l = mid;
} else {
r = mid;
}
}
printf("%.10Lf\n", l);
return 0;
}
[ABC323F] Push and Carry
按照箱子和终点的位置分类讨论。注意一些细节即可。
时间复杂度 \(O(1)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
struct Node {
int x, y;
} st, ed, box;
int dis(int x1, int y1, int x2, int y2) {
return (abs(x1 - x2) + abs(y1 - y2));
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> st.x >> st.y >> box.x >> box.y >> ed.x >> ed.y;
if(ed.x <= box.x && ed.y >= box.y) {
int ans1 = dis(st.x, st.y, box.x, box.y-1) + ((st.x == box.x && st.y > box.y) ? 2 : 0), ans2 = dis(st.x, st.y, box.x+1, box.y) + ((st.y == box.y && st.x < box.x) ? 2 : 0);
cout << min(ans1 + (ed.x == box.x ? 0 : 2), ans2 + (ed.y == box.y ? 0 : 2)) + dis(box.x, box.y, ed.x, ed.y) << '\n';
} else if(ed.x <= box.x && ed.y <= box.y) {
int ans1 = dis(st.x, st.y, box.x, box.y+1) + ((st.x == box.x && st.y < box.y) ? 2 : 0), ans2 = dis(st.x, st.y, box.x+1, box.y) + ((st.y == box.y && st.x < box.x) ? 2 : 0);
cout << min(ans1 + (ed.x == box.x ? 0 : 2), ans2 + (ed.y == box.y ? 0 : 2)) + dis(box.x, box.y, ed.x, ed.y) << '\n';
} else if(ed.x >= box.x && ed.y >= box.y) {
int ans1 = dis(st.x, st.y, box.x, box.y-1) + ((st.x == box.x && st.y > box.y) ? 2 : 0), ans2 = dis(st.x, st.y, box.x-1, box.y) + ((st.y == box.y && st.x > box.x) ? 2 : 0);
cout << min(ans1 + (ed.x == box.x ? 0 : 2), ans2 + (ed.y == box.y ? 0 : 2)) + dis(box.x, box.y, ed.x, ed.y) << '\n';
} else if(ed.x >= box.x && ed.y <= box.y) {
int ans1 = dis(st.x, st.y, box.x, box.y+1) + ((st.x == box.x && st.y < box.y) ? 2 : 0), ans2 = dis(st.x, st.y, box.x-1, box.y) + ((st.y == box.y && st.x > box.x) ? 2 : 0);
cout << min(ans1 + (ed.x == box.x ? 0 : 2), ans2 + (ed.y == box.y ? 0 : 2)) + dis(box.x, box.y, ed.x, ed.y) << '\n';
}
return 0;
}
[ABC338F] Negative Traveling Salesman
有点意思的题。
\(n\le 20\),一眼状压 \(dp\),设 \(dp_{i,S}\) 表示走到 \(i\) 状态为 \(S\) 的最小权值和。转移为:
初始化 \(dp_{i,2^{i-1}}=0\),其他赋最大值。其中 \(dis_{i,j}\) 表示 \(i\to j\) 的最短距离,可以用 floyd \(O(n^3)\) 轻松求出。答案即为 \(\min\{dp_{i,2^{n}-1}\}\)。
时间复杂度 \(O(2^nn^2+n^3)\)。6s 稳过。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 1e10
using namespace std;
const int N = 21;
int n, m, dp[N][1<<N], f[N][N], ans = inf;
void floyd() {
For(k,1,n) {
For(i,1,n) {
For(j,1,n) {
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
memset(f, 0x3f, sizeof f);
memset(dp, 0x3f, sizeof dp);
For(i,1,m) {
int u, v, w;
cin >> u >> v >> w;
f[u][v] = w;
}
floyd();
For(i,1,n) dp[i][1<<(i-1)] = 0;
For(S,0,(1<<n)-1) {
For(i,1,n) {
if(((S >> (i-1)) & 1)) {
For(j,1,n) {
if(((S >> (j-1)) & 1) || i == j) {
dp[i][S] = min(dp[i][S], dp[j][S ^ (1 << (i-1))] + f[j][i]);
}
}
}
}
}
For(i,1,n) ans = min(ans, dp[i][(1<<n)-1]);
if(ans == inf) cout << "No\n";
else cout << ans << '\n';
return 0;
}
[ABC326F] Robot Rotation
很好想但很难调的题。
首先有个性质:\(i\) 为奇数则在竖直方向贡献,\(i\) 为偶数则在水平方向贡献,且二者互不影响。于是我们可以按照 \(i\) 的奇偶分别处理。
分开来看之后,操作序列的长度已经 \(\le 40\) 了,可以立即想到折半状压。第一部分用 map
记录 \(2^{20}\) 个情况下的所有可能的贡献,第二部分在查找当前情况下的贡献能否与前半部分的匹配即可。
最后是统计答案,记录一个方位标记,围绕方位和顺逆时针讨论即可。
时间复杂度 \(O(n+2^{\frac{n}{4}})\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 105;
int n, X[2], a[N], a1[N], a2[N], len[2], Ans[2];
char ans[N];
map<int, int> mp;
bool solve(int op) {
map<int, int>().swap(mp);
int m = len[op] / 2;
mp[0] = 0;
For(S,0,(1<<m)-1) {
int res = 0;
For(i,1,m) {
res += (((S >> (i-1)) & 1) ? 1 : -1) * (op == 0 ? a1[i] : a2[i]);
}
mp[res] = S;
}
For(S,0,(1<<(len[op]-m))-1) {
int res = 0;
For(i,m+1,len[op]) {
res += (((S >> (i-m-1)) & 1) ? 1 : -1) * (op == 0 ? a1[i] : a2[i]);
}
if(mp.count(X[op] - res)) {
Ans[op] = (S << m) + mp[X[op] - res];
return 1;
}
}
return 0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> X[1] >> X[0];
For(i,1,n) cin >> a[i];
For(i,1,n) {
if(i & 1) a1[++len[0]] = a[i];
else a2[++len[1]] = a[i];
}
if(solve(0) && solve(1)) {
cout << "Yes\n";
int dist = 0;
For(i,1,n) {
int p = (i - 1) / 2;
if(i & 1) {
if((Ans[0] >> p) & 1) {
cout << (dist == 0 ? "L" : "R"); dist = 3;
} else {
cout << (dist == 0 ? "R" : "L"); dist = 1;
}
} else {
if((Ans[1] >> p) & 1) {
cout << (dist == 3 ? "R" : "L"); dist = 0;
} else {
cout << (dist == 3 ? "L" : "R"); dist = 2;
}
}
}
} else {
cout << "No\n";
}
return 0;
}
[ABC339F] Product Equality
一眼 \(O(n^2)\) map
维护,两眼 \(a_i\le 10^{1000}\),三眼哈希做完。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 19491001
#define mod2 998244853
#define mk make_pair
using namespace std;
const int N = 1e3 + 10;
string s[N];
int n, a[N], b[N], ans;
map<pair<int, int>, int> mp;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> s[i];
For(i,1,n) {
for (int j = 0; j < s[i].size(); ++j) {
a[i] = ((a[i] * 10) % mod + (s[i][j] - '0')) % mod;
b[i] = ((b[i] * 10) % mod2 + (s[i][j] - '0')) % mod2;
}
mp[mk(a[i], b[i])]++;
}
For(i,1,n) {
For(j,1,n) {
ans += mp[mk((a[i] * a[j]) % mod, (b[i] * b[j]) % mod2)];
}
}
cout << ans << '\n';
return 0;
}
[ABC350F] Transpose
很无聊的数据结构题。
用栈处理翻转关系,然后用文艺平衡树维护翻转即可。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e5 + 10;
struct Node {
int l, r, val, key, siz, tag;
} t[N];
int n, stk[N], top, root, sum[N], idx;
char s[N];
int x, y, z;
mt19937 rd(114514);
int New(int val) {
t[++idx] = (Node){0, 0, val, rd(), 1, 0};
return idx;
}
void pushup(int p) {
t[p].siz = t[t[p].l].siz + t[t[p].r].siz + 1;
}
void pushdown(int p) {
if(t[p].tag) {
swap(t[p].l, t[p].r);
t[t[p].l].tag ^= 1;
t[t[p].r].tag ^= 1;
t[p].tag = 0;
return ;
}
}
void split(int p, int k, int &x, int &y) {
if(!p) {x = y = 0; return ;}
pushdown(p);
if(k > t[t[p].l].siz) {
k -= (t[t[p].l].siz + 1);
x = p;
split(t[p].r, k, t[p].r, y);
} else {
y = p;
split(t[p].l, k, x, t[p].l);
}
pushup(p);
return ;
}
int merge(int x, int y) {
if(!x || !y) return x | y;
if(t[x].key < t[y].key) {
pushdown(x);
t[x].r = merge(t[x].r, y);
pushup(x);
return x;
} else {
pushdown(y);
t[y].l = merge(x, t[y].l);
pushup(y);
return y;
}
}
void print(int p) {
if(!p) return ;
pushdown(p);
print(t[p].l);
if(s[t[p].val] != '(' && s[t[p].val] != ')') cout << s[t[p].val];
print(t[p].r);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> (s + 1);
n = strlen(s + 1);
For(i,1,n) root = merge(root, New(i));
For(i,1,n) {
if(s[i] == '(') stk[++top] = i;
else if(s[i] == ')') {
int l = stk[top--];
sum[l]++, sum[i+1]--;
split(root, i, x, z);
split(x, l-1, x, y);
t[y].tag ^= 1;
root = merge(x, merge(y, z));
}
}
For(i,1,n) {
sum[i] += sum[i-1];
if(sum[i] & 1) {
if(s[i] >= 'a' && s[i] <= 'z') s[i] += ('A' - 'a');
else if(s[i] >= 'A' && s[i] <= 'Z') s[i] += ('a' - 'A');
}
}
print(root);
return 0;
}
[ABC325F] Sensor Optimization Dilemma
很小清新的 dp 题。
设 \(dp_{i,j}\) 表示前 \(i\) 个数中选出 \(j\) 个一号检测器所需的最少的二号检测器的数量。转移为:
答案即为 \(\min\{i\times c_1 + dp_{n,i}\times c_2\}\),记得判断 \(dp_{n,i}\le k_2\)。时间复杂度为 \(O(nk^2)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 1e18
using namespace std;
const int N = 1e3 + 10;
int n, d[N], l[2], c[2], K[2], dp[N][N], ans = inf;
int Ceil(int x, int y) {
return (x - 1 + y) / y;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> d[i];
For(i,0,1) cin >> l[i] >> c[i] >> K[i];
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
For(i,1,n) {
For(j,0,K[0]) {
For(k,0,j) {
dp[i][j] = min(dp[i][j], dp[i-1][j-k] + Ceil(max(0ll, d[i] - k * l[0]), l[1]));
}
}
}
For(i,0,K[0]) {
if(dp[n][i] <= K[1])
ans = min(ans, i * c[0] + dp[n][i] * c[1]);
}
cout << (ans == inf ? -1 : ans) << '\n';
return 0;
}
[ABC357F] Two Sequence Queries
无聊题。
线段树维护 \(A_i\), \(B_i\),\(A_i\times B_i\) 及它们的区间标记即可。时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 998244353
using namespace std;
const int N = 2e5 + 10;
struct Node {
int l, r, val, a, b, tag[2];
} t[N << 2];
int n, m, a[N], b[N];
void pushup(int p) {
t[p].val = (t[ls].val + t[rs].val) % mod;
t[p].a = (t[ls].a + t[rs].a) % mod;
t[p].b = (t[ls].b + t[rs].b) % mod;
}
void pushdown(int p) {
if(t[p].tag[0]) {
t[ls].val = (t[ls].val + (t[p].tag[0] * t[ls].b) % mod) % mod;
t[rs].val = (t[rs].val + (t[p].tag[0] * t[rs].b) % mod) % mod;
t[ls].a = (t[ls].a + (t[p].tag[0] * (t[ls].r - t[ls].l + 1)) % mod) % mod;
t[rs].a = (t[rs].a + (t[p].tag[0] * (t[rs].r - t[rs].l + 1)) % mod) % mod;
t[ls].tag[0] = (t[ls].tag[0] + t[p].tag[0]) % mod;
t[rs].tag[0] = (t[rs].tag[0] + t[p].tag[0]) % mod;
t[p].tag[0] = 0;
}
if(t[p].tag[1]) {
t[ls].val = (t[ls].val + (t[p].tag[1] * t[ls].a) % mod) % mod;
t[rs].val = (t[rs].val + (t[p].tag[1] * t[rs].a) % mod) % mod;
t[ls].b = (t[ls].b + (t[p].tag[1] * (t[ls].r - t[ls].l + 1)) % mod) % mod;
t[rs].b = (t[rs].b + (t[p].tag[1] * (t[rs].r - t[rs].l + 1)) % mod) % mod;
t[ls].tag[1] = (t[ls].tag[1] + t[p].tag[1]) % mod;
t[rs].tag[1] = (t[rs].tag[1] + t[p].tag[1]) % mod;
t[p].tag[1] = 0;
}
return ;
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if(l == r) {
t[p].val = (a[l] * b[l]) % mod;
t[p].a = a[l] % mod;
t[p].b = b[l] % mod;
return ;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(p);
}
void upd(int p, int l, int r, int k, int op) {
if(l <= t[p].l && t[p].r <= r) {
if(!op) {
t[p].val = (t[p].val + (k * t[p].b) % mod) % mod;
t[p].a = (t[p].a + (k * (t[p].r - t[p].l + 1)) % mod) % mod;
} else {
t[p].val = (t[p].val + (k * t[p].a) % mod) % mod;
t[p].b = (t[p].b + (k * (t[p].r - t[p].l + 1)) % mod) % mod;
}
t[p].tag[op] = (t[p].tag[op] + k % mod) % mod;
return ;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1;
if(l <= mid) upd(ls, l, r, k, op);
if(r > mid) upd(rs, l, r, k, op);
pushup(p);
}
int qry(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {
return t[p].val;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1, ans = 0;
if(l <= mid) ans = (ans + qry(ls, l, r) % mod) % mod;
if(r > mid) ans = (ans + qry(rs, l, r) % mod) % mod;
return ans;
}
signed main() {
cin >> n >> m;
For(i,1,n) cin >> a[i];
For(i,1,n) cin >> b[i];
build(1, 1, n);
while(m--) {
int op, l, r, x;
cin >> op >> l >> r;
if(op == 1) {
cin >> x;
upd(1, l, r, x, 0);
} else if(op == 2) {
cin >> x;
upd(1, l, r, x, 1);
} else {
cout << qry(1, l, r) << '\n';
}
}
return 0;
}
[ABC327F] Apples
无聊题。
考虑转化题意。给定平面直角坐标系内的 \(n\) 个点,求出 \(d\times w\) 的矩形最多能覆盖多少个点。
然后横坐标升序排序双指针维护,纵坐标线段树维护 \([i,i+w-1]\) 区间内有多少个点。线段树支持区间加,区间最大值查询维护即可。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 3e5
using namespace std;
const int N = 1e6 + 10;
struct node {
int l, r, val, tag;
} t[N << 2];
struct Node {
int x, y;
} a[N];
int n, d, w, ans = 0;
bool cmp(Node x, Node y) {
return (x.x == y.x ? x.y < y.y : x.x < y.x);
}
void pushup(int p) {
t[p].val = max(t[ls].val, t[rs].val);
}
void pushdown(int p) {
if(t[p].tag) {
t[ls].val += t[p].tag;
t[rs].val += t[p].tag;
t[ls].tag += t[p].tag;
t[rs].tag += t[p].tag;
t[p].tag = 0;
return ;
}
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].tag = t[p].val = 0;
if(l == r) return ;
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(p);
}
void upd(int p, int l, int r, int k) {
if(l <= t[p].l && t[p].r <= r) {
t[p].val += k;
t[p].tag += k;
return ;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1;
if(l <= mid) upd(ls, l, r, k);
if(r > mid) upd(rs, l, r, k);
pushup(p);
}
int qry(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) {
return t[p].val;
}
pushdown(p);
int mid = t[p].l + t[p].r >> 1, ans = 0;
if(l <= mid) ans = max(ans, qry(ls, l, r));
if(r > mid) ans = max(ans, qry(rs, l, r));
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> d >> w;
For(i,1,n) cin >> a[i].x >> a[i].y;
build(1, 1, inf);
sort(a + 1, a + n + 1, cmp);
int r = 1;
For(l,1,n) {
while(a[r].x - a[l].x + 1 <= d && r <= n) {
upd(1, a[r].y - w + 1, a[r].y, 1);
ans = max(ans, qry(1, 1, inf));
r++;
}
upd(1, a[l].y - w + 1, a[l].y, -1);
}
cout << ans << '\n';
return 0;
}
[ABC311F] Yet Another Grid Task
很性质的 dp 题。
首先可以知道如果一个点是黑点,那么它的下面和右下的点一定要是黑点,故按照此规则,设 \(h_i\) 表示第 \(i\) 列最多多高,\(dp_{i,j}\) 表示第 \(i\) 列摆 \(j\) 个的方案数。
则转移为:\(dp_{i,j}=\sum\limits_{k=0}^{j+1}dp_{i-1,k}\)
前缀和优化做完,时间复杂度 \(O(nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 998244353
using namespace std;
const int N = 2e3 + 10;
int n, m, h[N], dp[N][N], sum[N][N];
char a[N][N];
signed main() {
cin >> n >> m;
For(i,1,n) For(j,1,m) cin >> a[i][j];
For(j,1,m) {
For(i,1,n) {
if(a[i][j] == '#') {
h[j] = n - i + 1;
break;
}
}
}
dp[0][0] = 1;
For(i,1,m) {
For(j,0,n+1) sum[i][j] = ((j == 0 ? 0 : sum[i][j-1]) + dp[i-1][j]) % mod;
For(j,h[i],n) {
dp[i][j] = (dp[i][j] + sum[i][j+1]) % mod;
}
}
int ans = 0;
For(i,h[m],n) ans = (ans + dp[m][i]) % mod;
cout << ans << '\n';
return 0;
}
[ABC345F] Many Lamps
有点思维的题。
可以发现一条边操作无非三种贡献:
- \(u\) 和 \(v\) 都是亮的,全部变成了暗的,此时状态贡献 \(-2\);
- \(u\) 和 \(v\) 都是暗的,全部变成了亮的,此时状态贡献 \(+2\);
- \(u\) 和 \(v\) 一亮一暗,还是变成一亮一暗,此时状态贡献不变;
每一次贡献都是偶数,所以 \(k\) 为偶数才会优解。
考虑如何构造方案。可以领出一个生成树。按照如下方式构造:
- 选出生成树的任意一个点 \(u\) 及其儿子 \(v\),若 \(v\) 暗着,则操作该边。
- 然后在生成树中删除该边。
发现这样贡献要么贡献不变,要么贡献 \(+2\)。最后如果贡献 \(=k\) 则输出方案结束。
时间复杂度 \(O(n\log n)\)(写丑了用 map
记录了一个边的编号)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10;
struct Node {
int v, nx;
} e[N << 2];
int n, m, h[N], tot, cnt, k, tag[N];
vector<int> ans;
bool vis[N];
map<int, int> E[N];
void add(int u, int v) {
e[++tot] = (Node){v, h[u]};
h[u] = tot;
}
void dfs(int x) {
vis[x] = 1;
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
if(vis[y]) continue;
dfs(y);
if(cnt < k && !tag[y]) {
cnt -= (tag[y] + tag[x]);
tag[y] ^= 1, tag[x] ^= 1;
cnt += (tag[y] + tag[x]);
ans.push_back(E[x][y]);
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
For(i,1,m) {
int u, v;
cin >> u >> v;
add(u, v); add(v, u);
E[u][v] = E[v][u] = i;
}
For(i,1,n) if(!vis[i]) dfs(i);
if(cnt != k) cout << "No\n";
else {
cout << "Yes\n";
cout << ans.size() << '\n';
for (auto x : ans) cout << x << ' ';
cout << '\n';
}
return 0;
}
[ABC341F] Breakdown
一个很有意思的思维题。
首先可以按照限制 \(w_u > w_v\) 对于贡献的转移重新连边,然后可以得出贡献之间的转移关系图,形成一个 DAG。
然后对于限制 \(\sum\limits_{v\in S} w_v < w_u\),可以抽象成:在儿子中选点,总重量不超过 \(w_u\),使价值最大。于是就是一个 \(01\) 背包了。
时间复杂度 \(O(nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e6 + 10;
struct Node {
int v, u, nx;
} e[N], E[N];
int n, m, tot, h[N], w[N], a[N], in[N], f[N], dp[N], ans;
bool vis[N];
void add(int u, int v) {
e[++tot] = (Node){v, u, h[u]};
h[u] = tot;
in[v]++;
}
void dfs(int x) {
if(vis[x]) return ;
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
dfs(y);
}
vis[x] = 1;
memset(dp, 0, sizeof dp);
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
FOR(j,w[x]-1,w[y]) dp[j] = max(dp[j], dp[j - w[y]] + f[y]);
}
f[x] = dp[w[x]-1] + 1;
return ;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,m) cin >> E[i].u >> E[i].v;
For(i,1,n) cin >> w[i];
For(i,1,n) cin >> a[i];
For(i,1,m) {
int u = E[i].u, v = E[i].v;
if(w[u] < w[v]) add(v, u);
else if(w[u] > w[v]) add(u, v);
}
For(i,1,n) if(!in[i]) dfs(i);
For(i,1,n) ans += f[i] * a[i];
cout << ans << '\n';
return 0;
}