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

  

 

 

posted @ 2021-11-23 19:54  guangheli  阅读(123)  评论(0)    收藏  举报