【总结】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;
}