JOISC2019
LOJ#3030. 「JOISC 2019 Day1」考试
标签:三维偏序,CDQ 分治
三维数点模板题,跑一个 $\mathrm{CDQ}$ 分治即可.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define N 200009 #define M 400009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; const int inf = 400000; struct BIT { int C[M]; int lowbit(int x) { return x & (-x); } void upd(int x, int v) { while(x < M) { C[x] += v; x += lowbit(x); } } int query(int x) { int tmp = 0; while(x) { tmp += C[x]; x -= lowbit(x); } return tmp; } void clr(int x) { while(x < M) { C[x] = 0; x += lowbit(x); } } }T; int n, Q, answer[N], arr[N << 1], tot, cnt; struct Point { int x, y, z; Point(int x = 0, int y = 0, int z = 0):x(x),y(y),z(z){} }A[N]; struct Que { int x, y, z; }B[N]; struct data { int x, y, z, d; data(int x = 0, int y = 0, int z = 0, int d = 0):x(x),y(y),z(z),d(d){} }q[N << 1], tmp[N << 1]; bool cmp(data i, data j) { if(i.x == j.x && i.y == j.y && i.z == j.z) return i.d < j.d; if(i.x == j.x && i.y == j.y) return i.z > j.z; if(i.x == j.x) return i.y > j.y; return i.x > j.x; } void solve(int l, int r) { if(l >= r) { return ; } int mid = (l + r) >> 1, cn = 0,j = l; solve(l, mid), solve(mid + 1, r); // 左右都分别按照顺序排好了,只要归并左右即可. for(int i = mid + 1; i <= r; ++ i) { while(j <= mid && q[j].y >= q[i].y) { if(q[j].d < 0) T.upd(q[j].z, 1); tmp[++cn] = q[j]; ++ j; } tmp[++cn] = q[i]; if(q[i].d > 0) { answer[q[i].d] += T.query(inf) - T.query(q[i].z - 1); } } while(j <= mid) tmp[++cn] = q[j], ++ j; for(int i = l; i <= mid ; ++ i) { T.clr(q[i].z); } for(int i = l; i <= r; ++ i) { q[i] = tmp[i - l + 1]; } } int main() { // setIO("input"); scanf("%d%d", &n, &Q); for(int i = 1; i <= n ; ++ i) { scanf("%d%d",&A[i].x, &A[i].y); A[i].z = A[i].x + A[i].y; arr[++tot] = A[i].z; } for(int i = 1; i <= Q; ++ i) { scanf("%d%d%d", &B[i].x, &B[i].y, &B[i].z); arr[++tot] = B[i].z; } sort(arr + 1, arr + 1 + tot); for(int i = 1; i <= n ; ++ i) { A[i].z = lower_bound(arr + 1, arr + 1 + tot, A[i].z) - arr; } for(int i = 1; i <= Q; ++ i) { B[i].z = lower_bound(arr + 1, arr + 1 + tot, B[i].z) - arr; } // 将 B 与 A 放到一起排,然后看 A 对 B 的贡献(小于) for(int i = 1; i <= n ; ++ i) { ++ cnt; q[cnt].x = A[i].x; q[cnt].y = A[i].y; q[cnt].z = A[i].z; q[cnt].d = -1; // 即没有编号可言,只是贡献一个数字. } for(int i = 1; i <= Q ; ++ i) { ++ cnt; q[cnt].x = B[i].x; q[cnt].y = B[i].y; q[cnt].z = B[i].z; q[cnt].d = i; } sort(q + 1, q + 1 + cnt, cmp); solve(1, cnt); for(int i = 1; i <= Q; ++ i) { printf("%d", answer[i]); if(i != Q) { printf("\n"); } } return 0; }
LOJ#3031. 「JOISC 2019 Day1」聚会
标签:交互,随机化
设当前连通块的根为 $\mathrm{x}$.
随机一个点 $\mathrm{y}$, 然后可以对连通块其他所有点都和 $\mathrm{x,y}$ 查一遍.
要么查询点在链上,要么在链上某个点的子树内部.
然后链上的情况可以写一个排序函数来进行排序,并输出链的答案.
子树的部分则分治下去,随机化加玄学就过了.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #include <cstdlib> #include "meetings.h" #define pb push_back #define ll long long using namespace std; int rt; int ran(int len) { return 1ll * rand() * rand() % len; } bool cmp(int x, int y) { return Query(rt, x, y) == x; } void solve(vector<int>g, int x) { // x is the root of the tree. int y = ran(g.size()), z; // pos 表示随机出来的位置. vector<int>chain; vector<int>son[2004]; for(int i = 0; i < g.size() ; ++ i) { if(i == y) { continue; } int p = Query(x, g[y], g[i]); if(p == g[i]) { chain.pb(g[i]); } else { son[p].pb(g[i]); } } rt = x, sort(chain.begin(), chain.end(), cmp); chain.pb(g[y]); int prev = x; for(int i = 0; i < chain.size() ; ++ i) { Bridge(min(prev, chain[i]), max(prev, chain[i])); prev = chain[i]; } if(son[x].size()) { solve(son[x], x); } for(int i = 0; i < chain.size() ; ++ i) { if(son[chain[i]].size()) { solve(son[chain[i]], chain[i]); } } } void Solve(int N) { vector<int>g; for(int i = 1; i < N ; ++ i) { g.pb(i); } srand(233323); solve(g, 0); }
LOJ#3040. 「JOISC 2019 Day4」合并
标签:并查集,连通块
先判断答案是否为 0:对于任意一条边,在两个端点连通块中有公共颜色.
若所有颜色的不相同,则最优解就是叶子之间相互匹配.
对于相同颜色的点,用并查集缩掉,然后转化成颜色各异的情况.
最后输出缩点后的叶子树/2 向上取整即可.
#include <cstdio> #include <vector> #include <set> #include <cstring> #include <algorithm> #define N 500009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n, K; vector<int>G[N], bu[N]; int fa[N], dep[N], f[N], col[N], deg[N]; void init() { for(int i = 0; i < N ; ++ i) f[i] = i; } void dfs(int x, int ff) { fa[x] = ff, dep[x] = dep[ff] + 1; for(int i = 0; i < G[x].size() ; ++ i) { int v = G[x][i]; if(v == ff) { continue; } dfs(v, x); } } int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } void merge(int x, int y) { // 将点 x 与点 y 合并起来. x = find(x), y = find(y); while(x != y) { if(dep[x] > dep[y]) { swap(x, y); } f[y] = fa[y], y = find(y); } } int main() { // setIO("input"); scanf("%d%d", &n, &K); for(int i = 1; i < n ; ++ i) { int x, y; scanf("%d%d", &x, &y); G[x].pb(y); G[y].pb(x); } for(int i = 1; i <= n ; ++ i) { scanf("%d", &col[i]); bu[col[i]].pb(i); } dfs(1, 0); init(); for(int i = 1; i <= K ; ++ i) { for(int j = 1; j < bu[i].size() ; ++ j) { merge(bu[i][0], bu[i][j]); } } for(int i = 1; i <= n ; ++ i) { for(int j = 0; j < G[i].size() ; ++ j) { int v = G[i][j]; if(find(i) != find(v)) { ++ deg[find(v)]; } } } int cn = 0; for(int i = 1; i <= n ; ++ i) { if(find(i) == i && deg[i] == 1) { ++ cn; } } printf("%d", cn / 2 + (cn & 1)); return 0; }
LOJ#3041. 「JOISC 2019 Day4」矿物
标签:整体二分,交互
理论复杂度是 $\mathrm{1.5n \log n}$, 据说就是正解,但是只能得 $\mathrm{85}$pts.
显然可以通过 $\mathrm{2n}$ 次操作将集合分开成两个部分.
对于第一个部分的每一个数,可以点亮第二个部分的一个前缀,然后二分到有效的最小前缀.
这个有效的最小前缀就是对应的匹配点.
然后这个问题直接二分的话复杂度很高,不妨考虑用整体二分来做.
直接整体二分会拿到 75分,然后把 $\mathrm{mid}$ 设的靠左一点能拿到 $\mathrm{85}$ pts.
#include <cstdio> #include <vector> #include <cmath> #include <cstring> #include "minerals.h" #include <algorithm> #define M 50009 #define pb push_back #define ll long long using namespace std; double BB; int n , match[M << 1],A[M], B[M], tmp[M], tl[M], tr[M], inq[M << 1], cnt; void solve(int l, int r,int d) { if(l == r) { Answer(A[l], B[l]); return ; } if(l > r) return ; int mid = min(r - 1, l + (int)(BB * (r - l + 1))),c1 = 0, c2 = 0, o = 0; if(d == 0) { // A 亮,B 不亮. for(int i = l; i <= mid ; ++ i) { o = Query(A[i]); } for(int i = l; i <= r ; ++ i) { int det = Query(B[i]); if(det != o) { // 说明 B 亮,A 暗. tl[++ c1] = B[i]; } else { tr[++ c2] = B[i]; } o = det; } // (l, mid): A 暗,B 亮. // (mid+1,r): A 亮,B 亮. for(int i = 1; i <= c1; ++ i) { B[l + i - 1] = tl[i]; swap(B[l + i - 1], A[l + i - 1]); } for(int i = 1; i <= c2; ++ i) B[mid + i] = tr[i]; solve(l, mid, 0); solve(mid + 1, r, 1); } if(d == 1) { // 两边都亮. for(int i = l; i <= mid ; ++ i) { o = Query(A[i]); } // 把 A 的一半点暗. for(int i = l; i <= r; ++ i) { int det = Query(B[i]); if(det != o) { tl[++ c1] = B[i]; } else { tr[++ c2] = B[i]; } o = det; } for(int i = l; i <= mid; ++ i) B[i] = tl[i - l + 1]; for(int i = mid+1;i<=r;++i) B[i]=tr[i - mid]; solve(l,mid,2); solve(mid+1,r,0); // (l, mid): A 熄灭, B 熄灭. // (mid + 1, r): A 亮, B 熄灭. } if(d == 2) { for(int i = l; i <= mid; ++ i) { o = Query(A[i]); } for(int i = l; i <= r; ++ i) { int det = Query(B[i]); if(det != o) { // A 熄灭,B 点亮. tr[++ c2] = B[i]; } else tl[++ c1] = B[i]; o = det; } // (l, mid): A 点亮,B 点亮. // (mid + 1, r): A 熄灭,B 点亮. for(int i = 1; i <= c1; ++ i) B[l + i - 1] = tl[i]; for(int i = 1; i <= c2; ++ i) B[mid + i] = tr[i]; for(int i = mid + 1; i <= r; ++ i) swap(A[i], B[i]); solve(l, mid, 1); solve(mid + 1, r, 0); } } void Solve(int N) { n = N; BB = 0.38; int c2 = 0; for(int i = 1; i <= 2 * n ; ++ i) { int cur = Query(i); if(cur > cnt) { A[++cnt] = i; } else { B[++c2] = i; } } solve(1, n, 1); }