1. 程式人生 > 其它 >【總結】BalticOI 2021 Day1

【總結】BalticOI 2021 Day1

T1

有一個未知的長度為 \(N\) 的單調上升序列 \(X\),每次可以詢問一個位置上的數,給定常數 \(A\),需要找到 \(K\) 個數使得 這些數之和在區間 \([A,2A]\) 中。最多詢問 \(40\) 次,\(N\le 10^5, 3\le K\le 10\)

比較好的思維題。

首先我們需要找到第一個 \(\ge A\) 的位置。顯然不會選這個位置後面的數。因為如果選了後面的某個數,將其替換為當前數一定更優。如果選了後面兩個及以上的數,和已經 \(>2A\) 了。

所以先判斷第一個大於 \(A\) 的數和最小的 \(K - 1\) 個數之和是否 \(\le 2A\)

接下我們考慮所有 \(<A\)

的數,我們先找出最小的 \(K\) 個數和,每次將一個數替換成未選的 \(<A\) 中最大的數並判斷是否滿足條件即可。

因為每替換一次和的改變數 \(<A\),所以一定是正確的。

typedef long long LL;
LL u[15];
void check(int n, int K, LL A){
	LL s = 0;
	rep(i, 1, K)s += u[i];
	if(s > 2 * A){impossible();return ;}
	if(s >= A){
		vector<int>ans;
		rep(i, 1, K)ans.pb(i);
		answer(ans); return ;
	}
	pre(i, K, 1){
		s -= u[i];
		s += skim(n - i + 1);
		if(s >= A){
			vector<int>ans;
			rep(j, 1, i - 1)ans.pb(j);
			rep(j, 1, K - i + 1)ans.pb(n - j + 1);
			answer(ans); return ;
		}
	}
	impossible();
}
void solve(int N, int K, LL A, int S) {
	int l = 1, r = N, ed = 0;
	while(l <= r){
		int mid = (l + r) >> 1;
		if(skim(mid) >= A)ed = mid, r = mid - 1;
		else l = mid + 1;
	}
	rep(i, 1, K)u[i] = skim(i);
	if(!ed)check(N, K, A);
	else if(ed < K)impossible();
	else {
		LL ss = 0;
		rep(i, 1, K - 1)ss += u[i];
		if(ss + skim(ed) <= 2 * A){
			vector<int>ans;
			rep(i, 1, K - 1)ans.pb(i);
			ans.pb(ed);
			answer(ans); return ;
		}
		if(ed > K)check(ed - 1, K, A);
		else impossible();
	}
}

T2

很神的 DS 題。

首先考慮 \(Q\) 詢問。經過轉化,等價於查詢 \(d\to a\) 的路徑上的數是否單調遞增,且所有數都小於給定值。

直接樹上倍增可以做到 \(\mathcal{O}(N\log N)\)

考慮 \(C\) 詢問,等價於詢問從 \(a\) 出發能到達的點數。

如果 \(C\) 詢問在所有 \(S\) 之後,那麼整個問題是個靜態問題,可以直接動態規劃,狀態為 \(f_{i,j}\) 表示從與 \(i\) 相連的點中第 \(j\) 大的邊進入 \(i\),可達的點數。時間複雜度 \(\mathcal{O}(N)\)

#define N 240005
int n, k;
vector<Pr>e[N];
struct node{
	int op, x, y;
}a[N];
int d[N], f[N][17], t, p[2][N][17], u[N], dfn[N], idx, sz[N];
void dfs(int x,int fa){
	d[x] = d[f[x][0] = fa] + 1, dfn[x] = ++idx, sz[x] = 1;
	rp(i, t)f[x][i] = f[f[x][i - 1]][i - 1];
	go(y, e[x])if(y.se != fa)u[y.se] = y.fi, dfs(y.se, x), sz[x] += sz[y.se];
}
void init(int x,int fa){
	p[0][x][0] = (u[x] < u[fa]), p[1][x][0] = (u[x] > u[fa]);
	rp(i, t){
		p[0][x][i] = p[0][x][i - 1] & p[0][f[x][i - 1]][i - 1];
		p[1][x][i] = p[1][x][i - 1] & p[1][f[x][i - 1]][i - 1];
	}
	go(y, e[x])if(y.se != fa)init(y.se, x);
}
inline bool ck(int x,int y){return dfn[x] <= dfn[y] && dfn[x] + sz[x] > dfn[y];}
bool check(int x,int y,int lim){
	if(x == y)return true;
	int op = 0;
	if(d[x] < d[y])op ^= 1, swap(x, y);
	if(ck(y, x)){
		if(op && u[x] > lim)return false;
		pre(i, t, 0)if(d[f[x][i]] > d[y]){
			if(!p[op][x][i])return false;
			x = f[x][i];
		}
		if(!op && u[x] > lim)return false;
		return true;
	}
	else{
		if(op && u[x] > lim)return false;
		if(!op && u[y] > lim)return false;
		pre(i, t, 0)if(d[f[x][i]] >= d[y]){
			if(!p[op][x][i])return false;
			x = f[x][i];
		}
		pre(i, t, 0)if(f[x][i] != f[y][i]){
			if(!p[op][x][i] || !p[op ^ 1][y][i])return false;
			x = f[x][i], y = f[y][i];
		}
		return (u[x] < u[y]) ^ op;
	}
}
vector<int>g[N];
int solve(int x, int y){
	if(~g[x][y])return g[x][y];
	g[x][y] = 1;
	if(y < si(e[x])){
		int w = e[x][y].se;
		int cur = lower_bound(e[w].begin(), e[w].end(), mp(e[x][y].fi, x)) - e[w].begin();
		g[x][y] += solve(w, cur + 1) + solve(x, y + 1) - 1;
	}
	return g[x][y];
}
int main() {
	//int T = read();while(T--)solve();
	n = read(), k = read(), t = log2(n);
	rp(i, n + k - 1){
		char op[2];
		scanf("%s", op);
		if('S' == *op){
			int x = read(), y = read();
			e[x].pb(mp(i, y)), e[y].pb(mp(i, x));
		}
		else if('Q' == *op)	a[i].op = 1, a[i].x = read(), a[i].y = read();
		else a[i].op = 2, a[i].x = read();
	}
	rp(i, n){
		sort(e[i].begin(), e[i].end());
		rep(j, 0, si(e[i]))g[i].pb( ~0 );
	}
	dfs(1, 0), init(1, 0);
	rp(i, n + k - 1)
		if(1 == a[i].op){
			if(check(a[i].y, a[i].x, i))puts("yes");
			else puts("no");
		}
		else if(2 == a[i].op)printf("%d\n", solve(a[i].x, 0));
	return 0;
}

但是 \(C\) 的詢問是實時的,所以需要考慮更好的方法。

如果我們直接按題意模擬,就是 \(\mathcal{O}(N^2)\) 的。由於我們只用記錄當前節點 \(x\) 是否有資料 \(y\),可以 bitset 優化到 \(\mathcal{O}(\dfrac{N^2}{64})\)

進一步觀察不難發現這就是線段樹合併的過程,所以我們直接可持久化線段樹合併即可。對於 \(C\) 詢問,我們離線後倒著做,線段樹上第 \(i\) 個位置表示最大標號為 \(i\) 的路徑是否可達。時間和空間複雜度都是 \(\mathcal{O}(N\log N)\)

有一個無腦但難寫的做法,直接點分治加樹狀陣列維護,時間複雜度是 \(\mathcal{O}(N\log^2N)\)

#define N 120005
int n, k, ans[N], tot, rt[N]; Pr e[N], u[N];
struct node{int l, r, sum;}a[N << 6];
#define ls a[x].l
#define rs a[x].r
int ins(int y,int l,int r,int pos){
	int x = ++tot; a[x] = a[y];
	if(l == r){a[x].sum++; return x;}
	else{
		int mid = (l + r) >> 1;
		if(mid >= pos)a[x].l = ins(a[y].l, l, mid, pos);
		else a[x].r = ins(a[y].r, mid + 1, r, pos);
		a[x].sum = a[ls].sum + a[rs].sum;
		return x;
	}
}
int merge(int x,int y,int l,int r){
	if(!x || !y)return x | y;
	int p = ++tot, mid = (l + r) >> 1;
	a[p].sum = a[x].sum + a[y].sum;
	if(l == r)return p;
	a[p].l = merge(a[x].l, a[y].l, l, mid);
	a[p].r = merge(a[x].r, a[y].r, mid + 1, r);
	return p;
}
int ask(int x,int l,int r,int pos){
	if(l == r)return a[x].sum;
	int mid = (l + r) >> 1;
	if(mid >= pos)return ask(ls, l, mid, pos);
	return ask(rs, mid + 1, r, pos);
}
int calc(int x,int L,int R,int l,int r){
	if(l > r)return 0;
	if(L >= l && R <= r)return a[x].sum;
	int mid = (L + R) >> 1, sum = 0;
	if(mid >= l)sum += calc(ls, L, mid, l, r);
	if(mid < r)sum += calc(rs, mid + 1, R, l, r);
	return sum;
}
int main() {
	//int T = read();while(T--)solve();
	n = read(), k = read();
	int p = 0, q = 0;
	rp(i, n)rt[i] = ins(0, 1, n, i);
	rp(i, n + k - 1){
		char op[2];
		scanf("%s", op);
		if('S' == *op){
			int x = read(), y = read();
			e[++p] = mp(x, y);
			rt[y] = rt[x] = merge(rt[x], rt[y], 1, n);
		}
		else if('Q' == *op){
			int x = read(), y = read();
			ans[++q] = ask(rt[x], 1, n, y);
		}
		else{
			ans[++q] = ~0;
			u[q] = mp(read(), p);
		}
	}
	rp(i, n)rt[i] = 0;
	pr(i, p){
		int x = e[i].fi, y = e[i].se;
		rt[x] = merge(rt[x], rt[y], 1, n);
		rt[y] = rt[x] = ins(rt[x], 1, n, i);
	}
	rp(i, k){
		if(~ans[i])puts(ans[i] ? "yes" : "no");
		else printf("%d\n", 1 + calc(rt[u[i].fi], 1, n, 1, u[i].se));
	}
	return 0;
}