Loading

【总结】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;
}
posted @ 2021-11-13 15:34  7KByte  阅读(230)  评论(0编辑  收藏  举报