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);
}

浙公网安备 33010602011771号