2015-2016 Petrozavodsk Winter Training Camp, Moscow SU Trinity Contest
大佬题解
A:
一看就是求矩阵的秩,不知道为啥WA,好像被卡精度了,自闭。。
E:
这题是阅读题。。。首先找到LCA,把a和b每次向上提时,将其父亲的另一个儿子加入到集合中。把a和b都提到LCA后,把LCA往上提,做同样的操作。
#include
using namespace std;
int main() {
int a, b;
scanf("%d%d", &a, &b);
vector<int> ans;
ans.push_back(a);
ans.push_back(b);
int t=a;
set<int> s;
while(t) {
s.insert(t);
t/=2;
}
int lca;
t = b;
while(t) {
if(s.count(t)) {
lca = t;
break;
}
t/=2;
}
if(lca==a||lca==b) {
puts("-1");
return 0;
}
t = a;
while(t/2!=lca) {
ans.push_back(t/2*2+1-t%2);
t/=2;
}
t = b;
while(t/2!=lca) {
ans.push_back(t/2*2+1-t%2);
t/=2;
}
t=lca;
while(t/2) {
ans.push_back(t/2*2+1-t%2);
t/=2;
}
sort(ans.begin(), ans.end());
for(int i=0; i<ans.size(); ++i) printf("%d%c", ans[i], i+1==ans.size()?'\n':' ');
}
F:
树上莫队。
树上莫队的思想就是将树转化为字典序,然后在上面进行修改和删除。首先dfs一次求出字典序, f [ u ] f[u] f[u] 表示进入 u u u 点的字典序, g [ u ] g[u] g[u] 表示出 u u u 点的字典序。对于维护点权,如果 [ L , R ] [L,R] [L,R] 区间的字典序上已经有这个点,那么就删除这个点,如果没这个点,那么就添加这个点。在这种情况下,我们可以观察到:假定 f [ u ] < f [ v ] f[u]<f[v] f[u]<f[v] ,也就是说 u u u 被 v v v 先dfs到,那么如果它们的LCA是 u u u 或 v v v 的其中一个, 那么对应的询问区间应该是 [ f [ u ] , f [ v ] ] [f[u], f[v]] [f[u],f[v]] ,如果LCA另有它值,那么对应的询问区间应该是 [ g [ u ] , f [ v ] ] [g[u], f[v]] [g[u],f[v]] ,且这种情况下区间里肯定是没有它们的LCA的,因此计算答案时还要加上它们的LCA。
对于维护边权,我们将边权转化为其儿子的点权。那么问题反过来了,如果LCA是本身,才要加LCA。
但其实这题可以不用管LCA的,直接求区间 [ f [ u ] + 1 , f [ v ] ] [f[u]+1, f[v]] [f[u]+1,f[v]] 就满足条件了。
维护mex主要思路是将权值分块,因此添加/删除只需要修改计数器即可,复杂度为 O ( 1 ) O(1) O(1) 。查询在块上查找,复杂度是 O ( n ) O(\sqrt n) O(n) 。因此总的复杂度还是 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23) 。
#include
using namespace std;
const int N = 2e5+7;
const int WB = 500;
const int WL = 207;
int f[N], g[N], id[N], n, q, wf[N], fa[N][22], dep[N], index;
int pos[N], ans[N], bc[WL], cnt[N];
bool vis[N];
struct Edge {
int v, w;
};
vector<Edge> adj[N];
struct Query{
int l, r, id;
bool operator < (const Query& rhs) const {
return pos[l]<pos[rhs.l] || (pos[l]==pos[rhs.l]&&r<rhs.r);
}
}qs[N];
int lca(int x, int y) {
if(dep[x]<dep[y]) swap(x, y);
if(dep[x]!=dep[y]) {
int dis = dep[x]-dep[y];
for(int i=20; i>=0; --i) {
if(dis>=(1<<i)) dis-=(1<<i), x=fa[x][i];
}
}
if(x==y) return x;
for(int i=20; i>=0; --i) {
if(fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i];
}
return fa[x][0];
}
void dfs(int u, int w) {
id[f[u]=++index]=u;
wf[u]=w;
for(Edge e : adj[u]) {
if(e.v!=fa[u][0]) {
fa[e.v][0]=u;
dep[e.v] = dep[u]+1;
dfs(e.v, e.w);
}
}
id[g[u]=++index]=u;
}
void add(int x) {
// printf("add %d\n", x);
int w = wf[x];
int b = w/WB;
if(cnt[w]==0) {
++bc[b];
}
++cnt[w];
}
void del(int x) {
// printf("del %d\n", x);
int w = wf[x];
int b = w/WB;
if(cnt[w]==1) {
--bc[b];
}
--cnt[w];
}
void modify(int x) {
if(wf[x]>=100000) return;
if(vis[x]) {
del(x);
vis[x] = false;
} else {
add(x);
vis[x] = true;
}
}
int query() {
for(int i=0; i<WL; ++i) {
if(bc[i]!=WB) {
for(int j=i*WB; j<(i+1)*WB; ++j) {
if(!cnt[j]) return j;
}
}
}
assert(false);
}
int main() {
scanf("%d%d", &n, &q);
for(int i=1; i<n; ++i) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
adj[u].push_back({
v, w});
adj[v].push_back({
u, w});
}
dfs(1, N);
for(int j=1; j<=20; ++j) {
for(int i=1; i<=n; ++i) {
fa[i][j]=fa[fa[i][j-1]][j-1];
}
}
int B = sqrt(index);
for(int i=1; i<=index; ++i) {
pos[i] = (i-1)/B;
}
for(int i=0; i<q; ++i) {
int l, r;
scanf("%d%d", &l, &r);
if(f[l]>f[r]) swap(l, r);
qs[i] = {
lca(l, r)==l?f[l]:g[l], f[r], i};
// printf("qq: %d %d\n", qs[i].l, qs[i].r);
}
// printf("dfs xv:\n");
// for(int i=1; i<=index; ++i) printf("%d%c", id[i], i==index?'\n':' ');
sort(qs, qs+q);
int L, R;
L=1; R=0;
for(int i=0; i<q; ++i) {
while(L>qs[i].l) modify(id[--L]);
while(R<qs[i].r) modify(id[++R]);
while(L<qs[i].l) modify(id[L++]);
while(R>qs[i].r) modify(id[R--]);
int x = id[L], y = id[R];
int llca = lca(x, y);
if(llca==x||llca==y) {
modify(llca);
ans[qs[i].id] = query();
modify(llca);
} else {
ans[qs[i].id] = query();
}
// printf("ans[%d]=%d, lca=%d, L=%d, R=%d\n", qs[i].id, ans[qs[i].id], llca, L, R);
}
for(int i=0; i<q; ++i) {
printf("%d\n", ans[i]);
}
}
I :
首先,很容想到根据询问的右端点排序处理。然后维护一个非严格单调递减栈,就可以找出每个点作为山谷的右端点时,对应左端点的位置。我们维护一个单点修改区间最值的线段树,每次求得一个山谷区间时 [ L , R ] [L,R] [L,R] 时,将线段树的 L L L 位置更新为 R R R 。那么对于一个询问 [ L q , R q ] [L_q, R_q] [Lq,Rq] ,时,所有山谷区间的右端点小于 R q R_q Rq 的都应该更新到线段树里了。那么询问线段树 [ L q , R q ] [L_q, R_q] [Lq,Rq] 的最值即为答案。但这里出现了一个问题,因为每次更新线段树时,但是在山谷区间左端点 L L L 被弹出栈时进行更新的。但栈中可能有连续两个位置元素,它们对应值是相等的,虽然它们能形成山谷,但是并没有弹出栈,导致线段树并没有被更新。因此要对于这种情况特殊处理,观察栈 s s s 可以发现一个性质,就是 a [ s i ] ≥ a [ s i + 1 ] a[s_i] \geq a[s_{i+1}] a[si]≥a[si+1] ,对于没弹出栈的元素 s i s_i si ,它当前的山谷右端点是它距离它最右边且 a [ s j ] = a [ s i ] a[s_j]=a[s_i] a[sj]=a[si] 的第一个值。因此可以再开一个区间加,单调修改,区间最值得线段树维护这个答案。询问也一样。对于跨越了 L q L_q Lq 的连续相同值山谷,也需要特殊处理一下。(写完后想了想好像这里不需要特殊处理?待议。。。)
解决这个问题主要是要想到单调栈与每个点贡献的结合。。。
#include
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int INF = 1e9+7;
const int N = 5e5+7;
int a[N];
int rb[N];
int ans[N], l[N], r[N], p[N];
int mx[2][N<<2], lz[2][N<<2];
void push_down(int tid, int rt) {
if(lz[tid][rt]) {
lz[tid][lson] +=lz[tid][rt];
lz[tid][rson] +=lz[tid][rt];
mx[tid][lson] +=lz[tid][rt];
mx[tid][rson] +=lz[tid][rt];
lz[tid][rt] = 0;
}
}
void push_up(int tid, int rt) {
mx[tid][rt] = max(mx[tid][lson], mx[tid][rson]);
}
void update_add(int rt, int l, int r, int tid, int ql, int qr, int v) {
if(ql<=l&&qr>=r) {
lz[tid][rt]+=v;
mx[tid][rt]+=v;
return;
}
push_down(tid, rt);
int m = (l+r)/2;
if(ql<=m) update_add(lson, l, m, tid, ql, qr, v);
if(qr>m) update_add(rson, m+1, r, tid, ql, qr, v);
push_up(tid, rt);
}
void update_set(int rt, int l, int r, int tid, int p, int v) {
if(l==r) {
lz[tid][rt] = 0;
mx[tid][rt] = v;
return ;
}
push_down(tid, rt);
int m =(l+r)/2;
if(p<=m) update_set(lson, l, m, tid, p, v);
else update_set(rson, m+1, r, tid, p, v);
push_up(tid, rt);
}
int query(int rt, int l, int r, int tid, int ql, int qr) {
// printf("query tree: %d %d %d\n", l, r, mx[tid][rt]);
if(ql<=l&&qr>=r) return mx[tid][rt];
push_down(tid, rt);
int m = (l+r)/2;
int res = 0;
if(ql<=m) res = max(res, query(lson, l, m, tid, ql, qr));
if(qr>m) res = max(res, query(rson, m+1, r, tid, ql, qr));
push_up(tid, rt);
return res;
}
int stk[N];
struct Stack {
int tp;
Stack():tp(0){
}
int size() {
return tp; }
void push(int val) {
stk[tp++]=val; }
int top() {
return stk[tp-1]; }
void pop() {
--tp; }
bool empty() {
return tp==0; }
};
bool debug;
int main() {
// freopen("I.out", "w", stdout);
debug = false;
int n, m;
scanf("%d%d", &n, &m);
Stack s;
memset(rb, -1, sizeof(rb));
for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
for(int i=0; i<m; ++i) {
scanf("%d%d", &l[i], &r[i]);
p[i]=i;
}
sort(p, p+m, [](int x, int y) {
return r[x]<r[y];
});
int cur=0;
for(int i=1; i<=n; ++i) {
while(!s.empty()&&a[s.top()]<a[i]) {
int R = s.top();
while(!s.empty()&&a[s.top()]==a[R]) {
if(debug) printf("pop index=%d, value=%d, set segtree0 on point %d to %d\n", s.top(), a[s.top()], s.top(), R-s.top());
update_set(1, 1, n, 0, s.top(), R-s.top());
if(debug) printf("pop index=%d, value=%d, reset segtree1 on point %d to 0\n", s.top(), a[s.top()], s.size());
update_set(1, 1, n, 1, s.size(), 0);
s.pop();
}
}
int d = -1;
if(!s.empty()) d=s.top();
s.push(i);
int id = lower_bound(stk, stk+s.size(), s.top(), [](int x, int y) {
return a[x]>a[y];
}) - stk;
if(id+1<s.size()) {
// printf("stack content: ");
// for(int j=0; j
// puts("");
if(d!=-1) d=i-d;
else d=i;
if(debug) printf("push index=%d, value=%d, add segtree1 on range [%d, %d] by %d\n", i, a[i], id+1, s.size()-1, d);
update_add(1, 1, n, 1, id+1, s.size()-1, d); //加上新值
}
if(debug) printf("push index=%d, value=%d, set segtree1 on point %d to initial value 0\n", i, a[i], s.size());
update_set(1, 1, n, 1, s.size(), 0); //新入栈元素的答案置0
while(cur<m&&r[p[cur]]<=i) {
//对于左端点小于等于i的询问
int L = l[p[cur]], R=r[p[cur]];
if(debug) printf("process query[%d, %d] on segtree0\n", L, R);
int res = query(1, 1, n, 0, L, R); // 所有已出栈元素的贡献
int id = lower_bound(stk, stk+s.size(), L)-stk; //询问左端点右边的第一个在栈中的值。
int id2 = upper_bound(stk+id+1, stk+s.size(), stk[id], [](int x, int y) {
return a[x]>a[y];
})-stk;
if(id2!=s.size()) {
//如果存在id2
if(debug) printf("process query[%d, %d] on segtree1, query range=[%d, %d]\n", L, R, id2, s.size());
int tmp = query(1, 1, n, 1, id2, s.size()); //询问答案
res = max(res, tmp);
}
int tmp = stk[id2-1]-stk[id]; //跨越询问左端点的贡献。
res = max(res, tmp);
ans[p[cur]] = res;
++cur;
}
}
int k = 0;
for(int i=0; i<m; ++i) {
printf("%d\n", ans[i]);
}
}
J:
很容易看出是个树dp,维护当前点到叶子最多匹配的后缀和前缀。
但是一直WA,自闭了。