Rank
有点可惜,
A. 玩游戏
绝妙贪心题。感觉这种能产生很多假做法且都可 hack 的贪心都是好题。
赛时不知道为什么犯唐没交一开始的暴力贪心。
考虑双指针,设左右指针分别为 \(l,r\)。主要思路是实时维护当前两个指针向两边最近的一个区间和不为正的段,记录该区间的和 \(sum1/sum2\) 以及中途和的最大值 \(max1/max2\),并记录另一个端点 \(ii/jj\),以及维护当前区间的和 \(sum=\sum_{i=l}^r\ a_i\)。
首先考虑能否向两边移动,比较平凡,满足 \(sum+max1/max2\le 0\) 即可(注意前提是这个区间存在)。如果直接能移动到 \([1,n]\) 说明合法,直接返回 true,如果无法移动说明不合法,直接返回 false。
然后就来到了这个算法的精髓:将指针移至 \([1,n]\),从两边向中间,不断减去某个区间和,判断是否能到达当前 \([l,r]\)。维护变量相同,思路也相同,判断变为减去某个区间后是否保持和 \(\le 0\),如果能移动到 \([l,r]\) 说明合法,否则不合法。
至此判断结束,因为不会有回跳的操作,复杂度是严格 \(\mathcal{O(n)}\) 的,轻松最优解。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 1e5 + 5;
int n, k;
ll a[N];
namespace Wisadel
{bool Wsol(){int l = k + 1, r = k;ll sum = 0, sum1 = 0, sum2 = 0;ll m1 = -1e18, m2 = -1e18;int ii, jj;fu(i, l - 1, 0){sum1 += a[i];m1 = max(m1, sum1);if(sum1 <= 0){ii = i; break;}}fo(i, r + 1, n + 1){sum2 += a[i];m2 = max(m2, sum2);if(sum2 <= 0){jj = i; break;}}while(1){if(l == 1 && r == n) return 1;if(sum + m1 <= 0 && ii){sum += sum1, l = ii;sum1 = 0, m1 = -1e18;fu(i, l - 1, 0){sum1 += a[i];m1 = max(m1, sum1);if(sum1 <= 0){ii = i; break;}}}else if(sum + m2 <= 0 && jj <= n){sum += sum2, r = jj;sum2 = 0, m2 = -1e18;fo(i, r + 1, n + 1){sum2 += a[i];m2 = max(m2, sum2);if(sum2 <= 0){jj = i; break;}}}else break;}if(l > r) return 0;sum = 0;fo(i, 1, n) sum += a[i], a[i] = -a[i];if(sum > 0) return 0;sum1 = sum2 = 0;m1 = m2 = a[l] = a[r] = -1e18;int L = 0, R = n + 1;fo(i, L + 1, l){sum1 += a[i];m1 = max(m1, sum1);if(sum1 <= 0){ii = i; break;}}fu(i, R - 1, r){sum2 += a[i];m2 = max(m2, sum2);if(sum2 <= 0){jj = i; break;}}while(1){if(L + 1 == l && R - 1 == r) return 1;if(sum + m1 <= 0 && ii != l){sum += sum1, L = ii;sum1 = 0, m1 = -1e18;fo(i, L + 1, l){sum1 += a[i];m1 = max(m1, sum1);if(sum1 <= 0){ii = i; break;}}}else if(sum + m2 <= 0 && jj != r){sum += sum2, R = jj;sum2 = 0, m2 = -1e18;fu(i, R - 1, r){sum2 += a[i];m2 = max(m2, sum2);if(sum2 <= 0){jj = i; break;}}}else break;}return 0;}short main(){int T = qr;while(T--){n = qr, k = qr;fo(i, 0, n - 1) a[i] = qr;n--, k--;a[0] = a[n + 1] = -1e18;puts(Wsol() ? "Yes" : "No");}return Ratio;}
}
signed main(){return Wisadel::main();}
B. 排列
好 dp,笛卡尔树,先润。
\(\mathcal{O(2^n)}\) 枚举排列 + \(k=1\) 时打表发现答案为 \(2^{n-1}\),可以得到共 50pts。
好的,学到了不需要笛卡尔树的做法,神奇 dp。设 \(f_{i,j,0/1,0/1}\) 表示长度为 \(i\),至多操作 \(j\) 次变为一个,左/右边界外是否存在大于该区间内最大数的数的区间个数。首先恰好转不超过的容斥已经很套路了,其次这样设置状态可以很好地进行转移操作。
我们再枚举 \(i,j\) 后,再枚举区间内的一个拼接点,通过分讨拼接点与两侧区间的值大小关系来转移。
- \(k\) 为整个序列最大值,比较一眼,有:
- \(k\) 为右区间最大值,在左区间左侧还有更大的值,此时左区间需要在 \(j-1\) 次内消完,留一次消最大值 \(k\):
- 同上,更大值在右边:
- 左右区间两侧都有比 \(k\) 大的值,此时左右任意一区间在 \(j-1\) 次内消完即可,容斥除去两区间都要 \(j\) 次消完的方案,有:
由于 \(m\gt n\) 时答案一直为 0,所以复杂度是 \(\mathcal{O(n^2\log n)}\) 的。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 1000 + 5;
int n, m, mod;
ll f[N][N][2][2], c[N][N];
namespace Wisadel
{short main(){n = qr, m = qr, mod = qr;fo(i, 0, n){c[i][0] = 1;fo(j, 1, i) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;}fo(i, 0, m) f[0][i][1][1] = f[0][i][1][0] = f[0][i][0][1] = f[0][i][0][0] = 1;fo(i, 1, n) fo(j, 1, m) fo(k, 1, i){f[i][j][0][0] = (f[i][j][0][0] + f[k - 1][j][0][1] * f[i - k][j][1][0] % mod * c[i - 1][k - 1] % mod) % mod;f[i][j][1][0] = (f[i][j][1][0] + f[k - 1][j - 1][1][1] * f[i - k][j][1][0] % mod * c[i - 1][k - 1] % mod) % mod;f[i][j][0][1] = (f[i][j][0][1] + f[k - 1][j][0][1] * f[i - k][j - 1][1][1] % mod * c[i - 1][k - 1] % mod) % mod;f[i][j][1][1] = (f[i][j][1][1] + (f[k - 1][j][1][1] * f[i - k][j][1][1] % mod - (f[k - 1][j][1][1] - f[k - 1][j - 1][1][1] + mod) % mod * (f[i - k][j][1][1] - f[i - k][j - 1][1][1] + mod) % mod) % mod * c[i - 1][k - 1] % mod) % mod;}printf("%lld\n", (f[n][m][0][0] - f[n][m - 1][0][0] + mod) % mod);return Ratio;}
}
signed main(){return Wisadel::main();}
C. 最短路
暴力 dfs 20pts。
学了学长的神奇做法,二维 dijkstra。双向建图,设 \(dis_{i,j}\) 表示正向图到 \(x\) 反向图到 \(j\) 的最小代价,答案即为 \(dis_{n,n}\),记录经过的城市可以用 bitset 压一下,很好理解。由于转移经过城市是动态的,所以正确性是有保证的,而且这样做正反会保证经过图中每一个点,所以不会漏情况。时间复杂度 \(\mathcal{O(m\log m)}\)。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 250 + 5, M = 250 * 250 + 5;;
int n, m, ans;
int val[N], dis[N][N];
int pp[N], ot[M], en[M], tnc;
int hh[N], to[M], ne[M], cnt;
bool yz[N][N];
bitset<N> b[N][N];
struct rmm
{int d, x, y;bool operator < (const rmm &A) const{return d > A.d;}
};
namespace Wisadel
{void Wadd(int u, int v){to[++cnt] = v;ne[cnt] = hh[u];hh[u] = cnt;}void Wadd_(int u, int v){ot[++tnc] = v;en[tnc] = pp[u];pp[u] = tnc;}void Wdij(){priority_queue<rmm> q;memset(dis, 0x3f, sizeof dis);dis[1][1] = val[1];b[1][1][1] = 1;q.push({dis[1][1], 1, 1});while(q.size()){int u1 = q.top().x, u2 = q.top().y;q.pop();if(yz[u1][u2]) continue;yz[u1][u2] = 1;for(int i = hh[u1]; i != -1; i = ne[i]){int v = to[i], zc = 0;if(!b[u1][u2][v]) zc = val[v];if(dis[v][u2] > dis[u1][u2] + zc){b[v][u2] = b[u1][u2];b[v][u2][v] = 1;dis[v][u2] = dis[u1][u2] + zc;q.push({dis[v][u2], v, u2});}}for(int i = pp[u2]; i != -1; i = en[i]){int v = ot[i], zc = 0;if(!b[u1][u2][v]) zc = val[v];if(dis[u1][v] > dis[u1][u2] + zc){b[u1][v] = b[u1][u2];b[u1][v][v] = 1;dis[u1][v] = dis[u1][u2] + zc;q.push({dis[u1][v], u1, v});}}}}short main(){n = qr, m = qr;memset(hh, -1, sizeof hh);memset(pp, -1, sizeof pp);fo(i, 1, n) val[i] = qr;fo(i, 1, m){int a = qr, b = qr;Wadd(a, b);Wadd_(b, a);}Wdij();printf("%d\n", dis[n][n] == dis[0][0] ? -1 : dis[n][n]);return Ratio;}
}
signed main(){return Wisadel::main();}
D. 矩形
感觉本场比赛最简单的
平面矩形与交有关,很容易联想到扫描线。记录矩形的纵边,在纵轴上建一棵线段树,维护区间的所属块。每遇到一条边先查询该段上的块,并在查询过程中合并遇到的所有块,比较好想,如图:
然后插入/删除该边。插入比较好写,因为已经把区间上所有块都合并了,所以直接覆盖即可。删除需要考虑的多一些,因此我们考虑多记一个信息:每区间上整体段的数量。删除时直接使这个值减一即可,只有在这一区间上存在整体段时才可以用该段的所属块信息。意会一下。
然后是复杂度问题,注意到某区间上没有整体段时我们不取其信息,所以在查询时会递归得更深,但是复杂度是正确的,因为我们在查询时会合并途径的所有块,这样的操作最多是 \(n\) 次的,因此均摊不会影响整体复杂度。所以时间复杂度依然是 \(\mathcal{O(n\log n)}\) 的,可能常数略大,但足以薄纱题解给的双 \(\log\) 做法。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 1e5 + 5;
int n, tot;
int fx[N];
struct rmm{int lx, ly, rx, ry; } b[N];
struct edge{int x, l, r, id, op; } e[N << 1];
struct sgt{int num = 0, v = 0, all = 0; } t[N << 2];
int lazy[N << 2];
namespace Wisadel
{inline int Wfind(int x){if(x == fx[x]) return x;return fx[x] = Wfind(fx[x]);}#define ls (rt << 1)#define rs (rt << 1 | 1)#define mid ((l + r) >> 1)inline sgt Wpushup(int rt, sgt a, sgt b, int op){sgt zc = {0, 0, t[rt].all};zc.num = a.num + b.num;if(a.v == b.v) zc.v = a.v;if(op){if(!a.v && !b.v) zc.v = 0;else if(!a.v) zc.v = b.v;else if(!b.v) zc.v = a.v;else{int _ = Wfind(a.v), __ = Wfind(b.v);fx[__] = _;zc.v = _;}}return zc;}inline void Wpushdown(int rt, int l, int r){if(lazy[rt] > 0){t[ls].v = t[rs].v = t[rt].v;t[ls].all += lazy[rt], t[rs].all += lazy[rt];t[ls].num += (mid - l + 1) * lazy[rt];t[rs].num += (r - mid) * lazy[rt];lazy[ls] += lazy[rt], lazy[rs] += lazy[rt];lazy[rt] = 0;}else{t[ls].all += lazy[rt], t[rs].all += lazy[rt];if(!t[ls].all) t[ls].v = 0;if(!t[rs].all) t[rs].v = 0;t[ls].num += (mid - l + 1) * lazy[rt];t[rs].num += (r - mid) * lazy[rt];lazy[ls] += lazy[rt], lazy[rs] += lazy[rt];lazy[rt] = 0;}}inline sgt Wq(int rt, int l, int r, int x, int y){if(x <= l && r <= y && t[rt].v) return t[rt];if(lazy[rt]) Wpushdown(rt, l, r);sgt zc = {0, 0, 0};if(x <= mid && t[ls].num) zc = Wpushup(rt, zc, Wq(ls, l, mid, x, y), 1);if(y > mid && t[rs].num) zc = Wpushup(rt, zc, Wq(rs, mid + 1, r, x, y), 1);return zc;}inline void Wupd(int rt, int l, int r, int x, int y, int op, int id){if(x <= l && r <= y){if(op == 1){t[rt].v = id;t[rt].all++;t[rt].num += (r - l + 1);lazy[rt]++;}else{t[rt].all--;if(!t[rt].all) t[rt].v = 0;t[rt].num -= (r - l + 1);lazy[rt]--;}return ;}if(lazy[rt]) Wpushdown(rt, l, r);if(x <= mid) Wupd(ls, l, mid, x, y, op, id);if(y > mid) Wupd(rs, mid + 1, r, x, y, op, id);t[rt] = Wpushup(rt, t[ls], t[rs], 0);}short main(){n = qr;int L = 1000000, R = 0;fo(i, 1, n){fx[i] = i;b[i].lx = qr, b[i].ly = qr, b[i].rx = qr, b[i].ry = qr;L = min(L, b[i].ly), R = max(R, b[i].ry);e[++tot] = {b[i].lx, b[i].ly, b[i].ry, i, 1};e[++tot] = {b[i].rx, b[i].ly, b[i].ry, i, 0};}sort(e + 1, e + 1 + tot, [](edge A, edge B){return A.x == B.x ? A.op > B.op : A.x < B.x;});fo(i, 1, tot){sgt zc = Wq(1, L, R, e[i].l, e[i].r);if(zc.v != 0){int _ = Wfind(zc.v), __ = Wfind(e[i].id);fx[__] = _;}Wupd(1, L, R, e[i].l, e[i].r, e[i].op, e[i].id);}int ans = 0;fo(i, 1, n) if(i == Wfind(i)) ans++;printf("%d\n", ans);return Ratio;}
}
signed main(){return Wisadel::main();}
末
这场有点突然,导致做 T1 时脑子极其不清醒,到结束也没有一个很好的办法,最后秉持着打假做法没意义的思想交了个随机数上去。
T2 暴力很好拿,无需多言,T3 是先看的,打了暴力就没多想,唯一很好的是开 T4 开的很早,并且很快有了思路,可惜为了卡常漏了一个 corner case 没有达成赛时切 T4 的壮举。
比较还行吧,吃了个教训,贪心假的也要写,谁知道水数据能让你拿多少分?