【總結】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\)
因為每替換一次和的改變數 \(<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;
}