TJOI2016 && HEOI2016 解题报告
好吧我来写一波题解骗访问量QAQ
题目可以在cogs提交
bzoj4551~4456
D1T1 tree
树剖可做,然而有更简单的做法,10min搞定
维护一个并查集,时光倒流,如果当前点没有标记就把并查集合并到父亲上,查询就是并查集就可以了
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 200010 using namespace std; int n; struct opt{int tp, x;}q[maxn]; struct Edge{ int to, nxt; }edge[maxn << 1]; int h[maxn], cnt; void add(int u, int v){ cnt ++; edge[cnt].to = v; edge[cnt].nxt = h[u]; h[u] = cnt; } int mark[maxn]; int par[maxn]; int find(int x){return x == par[x] ? x : par[x] = find(par[x]);} int fa[maxn]; void dfs(int u){ if(mark[u])par[u] = u; else par[u] = find(fa[u]); for(int i = h[u]; i; i = edge[i].nxt){ int v = edge[i].to; if(v == fa[u])continue; fa[v] = u; dfs(v); } } int ans[maxn]; int main(){ freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout); int test, u, v; scanf("%d%d", &n, &test); for(int i = 1; i < n; i ++){ scanf("%d%d", &u, &v); add(u, v), add(v, u); } mark[1] ++; char cmd[5]; for(int i= 1; i <= test; i ++){ scanf("%s%d", cmd, &q[i].x); if(cmd[0] == 'C'){ q[i].tp = 1; mark[q[i].x] ++; } else q[i].tp = 0; } dfs(1); int x; for(int i = test; i >= 1; i --){ x = q[i].x; if(q[i].tp){ mark[x] --; if(mark[x] == 0) par[x] = find(fa[x]); } else ans[i] = find(x); } for(int i = 1; i <= test; i ++) if(q[i].tp == 0) printf("%d\n", ans[i]); fclose(stdin); fclose(stdout); return 0; }
D1T2 sort
(其实这道题目是BC原题。。)
首先题目的瓶颈在排序
考虑如果枚举a[q]的答案x,那么可以无视其他的变量的值,将序列转化为0,1,2,其中0代表比x小,1代表等于x,2代表大于x,这样答案就可以变成判定q这个位置是不是1。排序操作变得很好搞,是线段树的区间覆盖和查询。
然而是O(n^2logn)的。。
考虑二分答案x。。0代表比x小,1代表大于等于x
排序如上。如果a[q]为0代表答案大于正确答案,继续向下二分,如果为1代表答案小于等于当前答案,继续向上二分。
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 100010 using namespace std; int n, m; struct opt{ int tp, l, r; }q[maxn]; int a[maxn], Q; int t[maxn << 2], lazy[maxn << 2]; int Lc[maxn << 2], Rc[maxn << 2]; #define lc id << 1 #define rc id << 1 | 1 void build(int id, int l, int r){ int mid = l + r >> 1; Lc[id] = l, Rc[id] = r; if(l == r)return; build(lc, l, mid); build(rc, mid+1, r); } void pushdown(int id){ if(lazy[id] > 0){ t[lc] = Rc[lc] - Lc[lc] + 1; t[rc] = Rc[rc] - Lc[rc] + 1; lazy[lc] = 1; lazy[rc] = 1; } else if(lazy[id] < 0){ t[lc] = 0; t[rc] = 0; lazy[lc] = -1; lazy[rc] = -1; } lazy[id] = 0; } void update(int id, int L, int R){ if(L > R)return; if(Lc[id] == L && Rc[id] == R){ lazy[id] = 1; t[id] = Rc[id] - Lc[id] + 1; return; } pushdown(id); int mid = Lc[id] + Rc[id] >> 1; if(R <= mid)update(lc, L, R); else if(L > mid)update(rc, L, R); else update(lc, L, mid), update(rc, mid+1, R); t[id] = t[lc] + t[rc]; } int ask(int id, int L, int R){ if(Lc[id] == L && Rc[id] == R){ int ret = t[id]; t[id] = 0, lazy[id] = -1; return ret; } pushdown(id); int mid = Lc[id] + Rc[id] >> 1, ret = 0; if(R <= mid)ret = ask(lc, L, R); else if(L > mid)ret = ask(rc, L, R); else ret = ask(lc, L, mid) + ask(rc, mid+1, R); t[id] = t[lc] + t[rc]; return ret; } void Modify1(int l, int r){ int nw = ask(1, l, r); update(1, r - nw + 1, r); } void Modify2(int l, int r){ int nw = ask(1, l, r); update(1, l, l + nw - 1); } int ask(int id, int p){ if(Lc[id] == Rc[id]) return t[id]; pushdown(id); int mid = Lc[id] + Rc[id] >> 1, ret = 0; if(p <= mid)ret = ask(lc, p); else ret = ask(rc, p); t[id] = t[lc] + t[rc]; return ret; } bool check(int nw){ lazy[1] = -1; t[1] = 0; for(int i = 1; i <= n; i ++) if(a[i] >= nw) update(1, i, i); for(int i = 1; i <= m; i ++){ if(q[i].tp == 0)Modify1(q[i].l, q[i].r); else Modify2(q[i].l, q[i].r); } return ask(1, Q); } int main(){ freopen("sort.in", "r", stdin); freopen("sort.out", "w", stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) scanf("%d", &a[i]); for(int i = 1; i <= m; i ++) scanf("%d%d%d", &q[i].tp, &q[i].l, &q[i].r); scanf("%d", &Q); build(1, 1, n); int l = 1, r = n; while(l < r){ int mid = l + (r - l + 1) / 2; if(check(mid))l = mid; else r = mid - 1; } printf("%d\n", l); return 0; }
D1T3 seq
感觉10W就是可以看出来用CDQ来搞DP吧。。
用max[i]代表i位置变化最大值,min[i]代表i位置变化最小值
搞出偏序关系,j<=i, max[j] <=a[i], a[j] <= min[i]
然后用CDQ搞就可以了
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 100010 using namespace std; int n, m; int a[maxn], mn[maxn], mx[maxn]; struct opt{ int p, mx, mn, a, f; }q[maxn]; bool cmpp(const opt& a, const opt& b){ return a.p < b.p; } bool cmpmx(const opt& x, const opt& y){ if(x.mx != y.mx)return x.mx < y.mx; return x.p < y.p; } bool cmpa(const opt& x, const opt& y){ if(x.a != y.a)return x.a < y.a; return x.p < y.p; } namespace BIT{ int tim, n, t[maxn], vis[maxn]; #define lowbit(x) (x & -x) void update(int pos, int val){ for(int i = pos; i <= n; i += lowbit(i)) if(vis[i] == tim)t[i] = max(t[i], val); else vis[i] = tim, t[i] = val; } int ask(int pos){ int ret = 0; for(int i = pos; i; i -= lowbit(i)) if(vis[i] == tim) ret = max(ret, t[i]); return ret; } } void solve(int l, int r){ if(l == r)return; int mid = (l + r) >> 1; solve(l, mid); sort(q + l, q + mid + 1, cmpmx); sort(q + mid + 1, q + r + 1, cmpa); BIT::tim ++; int j = l; for(int i = mid+1; i <= r; i ++){ for(; j <= mid && q[j].mx <= q[i].a; j ++) BIT::update(q[j].a, q[j].f); q[i].f = max(q[i].f, BIT::ask(q[i].mn) + 1); } sort(q + l, q + r + 1, cmpp); solve(mid+1, r); } int main(){ freopen("seq.in", "r", stdin); freopen("seq.out", "w", stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++){ scanf("%d", &a[i]); mn[i] = mx[i] = a[i]; } int x, y; for(int i = 1; i <= m; i ++){ scanf("%d%d", &x, &y); mn[x] = min(mn[x], y); mx[x] = max(mx[x], y); } int Mx = 0; for(int i = 1; i <= n; i ++){ q[i].p = i; q[i].mx = mx[i]; q[i].mn = mn[i]; q[i].a = a[i]; q[i].f = 1; Mx = max(Mx, mx[i]); } BIT::n = Mx; solve(1, n); int ans = 0; for(int i = 1; i <= n; i ++) ans = max(ans, q[i].f); printf("%d\n", ans); fclose(stdin); fclose(stdout); return 0; }
D2T1 game
每次DAY2第一题都挂是个什么Flag啊。。
此题和ZOJ1654放置机器人同题。。
样例都差不多好嘛QAQ!
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 52 using namespace std; int n, m; char s[maxn][maxn]; int H[maxn][maxn], L[maxn][maxn], size; struct Edge{ int to, nxt; }edge[maxn * maxn]; int girl[maxn * maxn]; int h[maxn * maxn], cnt, mark[maxn * maxn]; void add(int u, int v){ cnt ++; edge[cnt].to = v; edge[cnt].nxt = h[u]; h[u] = cnt; } int vis[maxn * maxn], tim; int find(int u){ vis[u] = tim; for(int i = h[u]; i; i = edge[i].nxt){ int v = edge[i].to; if(vis[v] != tim){ vis[v] = tim; if(girl[v] == 0 || find(girl[v])){ girl[v] = u; return true; } } }return false; } int main(){ freopen("game.in", "r", stdin); freopen("game.out", "w", stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) scanf("%s", s[i] + 1); for(int i = 1; i <= n; i ++){ for(int j = 1; j <= m; j ++){ if(s[i][j] == '#')continue; if(H[i][j-1])H[i][j] = H[i][j-1]; else{ H[i][j] = ++ size; mark[size] = true; } if(L[i-1][j])L[i][j] = L[i-1][j]; else L[i][j] = ++ size; } } for(int i = 1; i <= n; i ++){ for(int j = 1; j <= m; j ++){ if(s[i][j] == '*') add(H[i][j], L[i][j]); } } int ans = 0; for(int i = 1; i <= size; i ++) if(mark[i] && !girl[i]) tim ++, ans += find(i); printf("%d\n", ans); return 0; }
D2T2 sum
考虑这个式子的意义。
Bell数代表的意义是将n个物品随意划分成几个非空集合的划分数。
这里多了一个阶乘代表的是这几个集合有序,多了2^j可以直接分配到递推式中。。
其实我也并不能解释清楚啦。。意会一下~
因为只求F(n)那么我们可以设一个G(n)
令答案ans = sigma(G[i]) [i = 1 .. n]
就有G[i]的递推式G[i] = 2 * sigma{C(j, i) * G[i - j]} j = [1 .. i]
将组合数拆开是卷积形式,i的阶乘可以除到G函数下面
然后NTT
这样做是n^2logn的,所以外面套一层CDQ就可以了。
UPD:据说直接上NTT就可以了?求教..
upd:感谢大神们,从分治转化为多项式求逆,需要把f函数移到一边,化成一个卷积的形式,右边是一个函数,那么就有f * g = h
多项式求逆就可以了
upd2:其实这道题目还有stirling反演代入的式子,可以直接化成卷积,具体见Owaski犇的blog
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 500010 using namespace std; typedef long long ll; const ll md = 998244353, G = 3; int n; ll g[maxn], f[maxn], h[maxn]; ll power_mod(ll a, ll b = md - 2){ ll ret = 1; while(b > 0){ if(b & 1)ret = ret * a % md; b >>= 1; a = a * a % md; }return ret; } void NTT(ll A[], int n, int type){ for(int i = 0, j = 0; i < n; i ++){ if(i > j)swap(A[i], A[j]); for(int t = n >> 1; (j ^= t) < t; t >>= 1); } for(int k = 2; k <= n; k <<= 1){ ll wn = power_mod(G, type > 0 ? (md-1)/k : md-1-(md-1)/k); for(int i = 0; i < n; i += k){ ll w = 1; for(int j = 0; j < k >> 1; j ++){ ll T = w * A[i+j+(k>>1)] % md; A[i+j+(k>>1)] = (A[i+j] - T + md) % md; A[i+j] = (A[i+j] + T) % md; w = w * wn % md; } } } if(type < 0){ ll inv = power_mod(n); for(int i = 0; i < n; i ++) (A[i] *= inv) %= md; } } ll inv[maxn], fac[maxn]; void solve(int l, int r){ if(l == r)return; int mid = l + r >> 1; solve(l, mid); int len = r - l + 1, n; for(n = 1; n <= len; n <<= 1); for(int i = 0; i < n; i ++)f[i] = h[i] = 0; for(int i = 0; i < n; i ++)f[i] = inv[i]; for(int i = l; i <= mid; i ++)h[i - l] = g[i]; NTT(f, n, 1), NTT(h, n, 1); for(int i = 0; i < n; i ++) h[i] = f[i] * h[i] % md; NTT(h, n, -1); for(int i = mid + 1; i <= r; i ++) (g[i] += 2 * h[i - l] % md) %= md; solve(mid + 1, r); } int main(){ freopen("sum.in", "r", stdin); freopen("sum.out", "w", stdout); scanf("%d", &n); g[0] = inv[0] = fac[0] = 1; for(int i = 1; i <= n; i ++) fac[i] = fac[i-1] * i % md; inv[n] = power_mod(fac[n]); for(int i = n - 1; i; i --) inv[i] = inv[i+1] * (i+1) % md; solve(0, n); ll ans = 0; for(int i = 0; i <= n; i ++) (ans += g[i] * fac[i] % md) %= md; (ans += md) %= md; printf("%lld\n", ans); return 0; }
D2T3 str
标解是后缀数组
我写的是后缀自动机
字符串的每一个子串都是一个后缀的前缀。将原串反建后缀自动机,每一个点的link指向它的最长前缀。
考虑二分答案L,那么可以在后缀自动机上倍增出c~d的前缀节点p
我们需要找到只有[a~b-L+1]的叶子节点是否在p的子树内,这样一定能保证答案的正确性。
然后对parent树的dfs序搞一个主席树就可以了
然后有各种杂技可以加快速度,不过我觉得这样比较好写。
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 200010 using namespace std; int n, m; //--------------------------------------------------// char str[maxn]; int whe[maxn], anc[maxn][20]; struct Node{ int nxt[26], link, len; }st[maxn]; int Root, size, last; void init(){ Root = size = last = 0; st[Root].len = 0; st[Root].link = -1; } void Extend(int c){ int p = last, cur = ++ size; st[cur].len = st[p].len + 1; for(; ~p && st[p].nxt[c] == 0; p = st[p].link) st[p].nxt[c] = cur; if(p == -1) st[cur].link = Root; else{ int q = st[p].nxt[c]; if(st[q].len == st[p].len + 1) st[cur].link = q; else{ int clone = ++ size; st[clone] = st[q]; st[clone].len = st[p].len + 1; for(; ~p && st[p].nxt[c] == q; p = st[p].link) st[p].nxt[c] = clone; st[q].link = st[cur].link = clone; } }last = cur; } //--------------------------------------------------// int dfs_clock, In[maxn], Out[maxn]; namespace DFS{ struct Edge{int to, nxt;}edge[maxn]; int h[maxn], cnt; void add(int u, int v){ cnt ++; edge[cnt].to = v; edge[cnt].nxt = h[u]; h[u] = cnt; } void dfs(int u){ In[u] = ++ dfs_clock; for(int i = h[u]; i; i = edge[i].nxt) dfs(edge[i].to); Out[u] = dfs_clock; } } //--------------------------------------------------// #define M 5000010 int lc[M], rc[M], v[M], root[maxn], Cnt; int Insert(int rt, int l, int r, int p){ int cur = ++ Cnt; lc[cur] = lc[rt], rc[cur] = rc[rt], v[cur] = v[rt] + 1; if(l == r)return cur; int mid = l + r >> 1; if(p <= mid)lc[cur] = Insert(lc[rt], l, mid, p); else rc[cur] = Insert(rc[rt], mid+1, r, p); return cur; } int ask(int rt, int l, int r, int L, int R){ if(rt == 0)return 0; if(l == L && R == r) return v[rt]; int mid = l + r >> 1; if(R <= mid)return ask(lc[rt], l, mid, L, R); if(L > mid) return ask(rc[rt], mid+1, r, L, R); return ask(lc[rt], l, mid, L, mid) + ask(rc[rt], mid+1, r, mid+1, R); } int Find(int r, int len){ int t = whe[r]; for(int i = 18; i >= 0; i --){ int v = anc[t][i]; if(st[v].len >= len) t = v; }return t; } int a, b, c, d; bool check(int len){ int u = Find(c, len); int A = ask(root[b - len + 1], 1, dfs_clock, In[u], Out[u]); int B = ask(root[a - 1], 1, dfs_clock, In[u], Out[u]); return A > B; } int main(){ freopen("str.in", "r", stdin); freopen("str.out", "w", stdout); init(); scanf("%d%d", &n, &m); scanf("%s", str + 1); for(int i = n; i >= 1; i --) Extend(str[i] - 'a'), whe[i] = last; for(int i = 1; i <= size; i ++) DFS::add(st[i].link, i); DFS::dfs(0); for(int i = 1; i <= size; i ++) anc[i][0] = st[i].link; for(int j = 1; 1 << j <= size; j ++) for(int i = 0; i <= size; i ++) anc[i][j] = anc[anc[i][j-1]][j-1]; for(int i = 1; i <= n; i ++) root[i] = Insert(root[i-1], 1, dfs_clock, In[whe[i]]); while(m --){ scanf("%d%d%d%d", &a, &b, &c, &d); int l = 0, r = min(b - a + 1, d - c + 1); while(l < r){ int mid = l + (r - l + 1) / 2; if(check(mid))l = mid; else r = mid - 1; } printf("%d\n", l); } return 0; }