5.27 考试 + 总结
今天的题目感觉难度分度有点奇怪,第二题是水题, 其次是第一题,再然后是最恶心的第三题
T1:大新闻
bz原题看到就想吐槽,原来刷数位dp的时候以为是到神题并没有写(刚开始想过果断弃疗),考场上想了半个小时发现可做
只要对两种情况分别处理就好了
加密的话,对每一位分别考虑统计有多少对数异或之后这一位是1,把贡献累加起来再除以 n2就可以了
不加密,要确定每个数的最优解,我们还是考虑每一位,统计有多少个数在最优情况下异或该位为0 (详见代码)
#include <cstdio> #include <cmath> #define eps 1e-7 using namespace std; typedef long long ll; typedef long double ld; ld ans1, ans2; double p; ll n, f[110][2], num[110]; int tim, len, nw, bit[110], vis[110][2]; ll Dp(int dep, int fg) { if(dep==0) return 1ll; if(vis[dep][fg]==tim) return f[dep][fg]; vis[dep][fg] = tim; int l = (dep==len)?1:0, r = fg?bit[dep]:1; ll ret = 0; for(int i = l ; i <= r ; ++ i) { if(dep==nw&&i!=1) continue; ret += Dp(dep-1, fg&&(i>=r)); } return f[dep][fg] = ret; } int main() { scanf("%lld%lf", &n, &p); -- n; ll now_n = n; while(now_n) bit[++ len] = now_n&1ll, now_n >>= 1ll; for(int i = len ; i >= 1 ; -- i) { ++ tim; nw = i; num[i] = Dp(len, 1); if(i!=len&&len>=2) num[i] += 1ll<<(len-2); ld w = 2.*(ld)(n+1ll-num[i])*1.*num[i]*(ld)(1ll<<(i-1)); ans2 += w/((ld)(n+1ll)*(ld)(n+1ll)); } ll v = 1ll; ans1 = (ld)((1ll<<len)-1ll); for(int i = len ; i >= 1 ; -- i) { if(bit[i]==0) { ld w = (ld)v*((ld)(1ll<<i-1)*(ld)(1ll<<i-1))/(ld)(n+1ll); v <<= 1ll; ans1 -= w; } } printf("%.6lf", (double)(p*ans1+(1-p)*ans2)); return 0; }
T2:谈笑风生
仍是bz原题,但并没有看到过
对于每个询问,要考虑两种情况,一种是b是a的祖先, 用 a 的深度和k取一个 min 乘以 a 的子树大小 (并不包含 a 本身, 下同)
另一种, b 是 a 的子树中的一个节点,这样的话我们枚举 a 的子节点, 所有子节点的子树中的节点总和就是答案
所以我们对每个点记录它子树的 size ,再记录它子树的size 的和
我们只要用 a 的子树的子树 size 的 和减掉 不合法的子树的size, 我们可以把bfs 序 搞出来,维护 size 的和的前缀和
不合法的子树是 从 depth[a]+k+1 开始的 所有在 a 的子树中的 节点
把最开始不合法的区间找到并减去就可以了。
#define MAXN 300010UL #include <cstdio> #include <cstring> #define INF 1e9 using namespace std; typedef long long ll; int n, m, t, num, hd, tl, cnt, q[MAXN], bg[MAXN], tr[MAXN], ls[MAXN], rs[MAXN], d[MAXN], fa[MAXN], sn[MAXN], st[MAXN], ed[MAXN]; ll sum[MAXN], se[MAXN], pr[MAXN]; struct Edge { int hou, nt; } sg[MAXN<<1]; void Add(int x, int y) { sg[t] = (Edge){y, d[x]}, d[x] = t ++; sg[t] = (Edge){x, d[y]}, d[y] = t ++; return; } void Dfs(int x, int _fa, int dep) { st[x] = ++ num, sn[x] = dep, se[x] = 0, sum[x] = 0, fa[x] = _fa; for(int i = d[x] ; i != -1 ; i = sg[i].nt) { if(sg[i].hou==_fa) continue; Dfs(sg[i].hou, x, dep+1); se[x] += se[sg[i].hou]+1ll; sum[x] += sum[sg[i].hou]; } sum[x] += (ll)se[x]; ed[x] = num; return; } ll Solve(int dep, int L, int R) { if(ls[dep]==INF) return 0; int l = ls[dep], r = rs[dep], ans = -1; while(l<=r) { int mid = (l+r)>>1; if(st[tr[mid]]>=L) r = mid-1, ans = mid; else l = mid+1; } if(ans==-1) return 0; int ans1 = -1; l = ans, r = rs[dep]; while(l<=r) { int mid = (l+r)>>1; if(st[tr[mid]]<=R) l = mid+1, ans1 = mid; else r = mid-1; } if(ans1==-1) return 0; return pr[ans1]-pr[ans-1]; } int main() { // freopen("laugh.in", "r", stdin); // freopen("laugh.out", "w", stdout); memset(d, -1, sizeof(d)); int x, y; scanf("%d%d", &n, &m); for(int i = 2 ; i <= n ; ++ i) scanf("%d%d", &x, &y), Add(x, y); for(int i = 1 ; i <= n ; ++ i) ls[i] = INF, rs[i] = -INF; Dfs(1, 0, 1); hd = tl = 0; q[++ tl] = 1; while(hd<tl) { int op = q[++ hd]; bg[op] = ++ cnt, tr[cnt] = op; if(ls[sn[op]]>cnt) ls[sn[op]] = cnt; if(rs[sn[op]]<cnt) rs[sn[op]] = cnt; pr[cnt] = (ll)sum[op]+pr[cnt-1]; for(int i = d[op] ; i != -1 ; i = sg[i].nt) if(sg[i].hou!=fa[op]) q[++ tl] = sg[i].hou; } for(int i = 1, p, k ; i <= m ; ++ i) { scanf("%d%d", &p, &k); ll ans = sum[p]-(ll)se[p]; ans -= Solve(sn[p]+k+1, st[p], ed[p]); if(sn[p]-k>=1) ans += 1ll*k*se[p]; else ans += 1ll*(ll)(sn[p]-1)*se[p]; printf("%lld\n", ans); } return 0; }
T3:图样图森破
是道神题 直接粘题解
如果某个串本身就是回文串,那么答案必然为无穷大。直接特判这种情况。 对于一般的情况,我们枚举回文串的中心的
位置。如果以串 s 的某个位置为中心的最 长回文串“半径”超过了 s 的边界(不妨认为是左边界,右边界的情况同理),那么 s
的右端 一定剩下了一段没有被加入回文串。但这一部分是可能在回文串里面的,我们可以枚举另 一个串 t,使得 t 的后若干字
符和 s 右端剩下的部分回文,这样 t 的一部分和 s 剩下的部分 都可以加入回文串了。此时 t 的左侧还可能剩下一段没有加入
回文串的部分,那么我们可 以重复一遍上面的过程。 但直接这样做与暴力无异。我们用图论模型来描述这个过程。对每个位
置建两个点, 分别代表当前剩余的部分为这个位置以左和以右的部分。新设一个起点,向串内的每个越 过了边界的回文串的
另一侧的位置对应点连一条有向边,边权为回文串长度。在枚举串 t 的时候,如果发现一个可以匹配的 t,就从 s 的位置对应
点向匹配到的 t 的位置对应点连一 条有向边,边权为这一段的长度。如果 t 正好被完全匹配,就连向一个特殊的节点。
建出这个模型后,一个可以出现在 S 中的回文子串会对应从起点出发的一条路径。如 果从起点可以走到一个环,或者
可以到达特殊节点,就意味着答案是无穷大。否则答案就 是从起点出发的最长路。 这个拓扑图的点数为 O(m) 级别,边数为
O(nm) 级别,当然实际上不会有这么恐怖。 我们只需要一个可以快速查询两个串的 LCP 的数据结构,后缀数组 +ST 表足以
胜任。 总复杂度为 O(m log m + nm),可以通过全部数据。 如果使用 Hash 求 LCP,或者用单次查询 O(log m) 的 RMQ
数据结构,则复杂度为 O(nm log m),只能通过 60% 的数据。 另外,理论上答案可以达到 O(mL) 级别,不过这样的数据
几乎不可能构造出来。实际 测试数据中,n > 1 的测试点的答案均未超过 40000。
#define MAXN 220010UL #define MAXM 22000010UL #include <cstdio> #include <algorithm> #include <cstring> using namespace std; typedef long long ll; ll dis[MAXN]; int n, t, d[MAXN], cnt, bin[20], lg[MAXN], len[MAXN], slen[MAXN]; int m, sym, lc[MAXN], rc[MAXN], sa[MAXN], rk[MAXN], ht[MAXN], st[20][MAXN], s[MAXN]; int S, T, T_, wa[MAXN], wb[MAXN], wv[MAXN], ws[MAXN], du[MAXN], q[MAXN]; bool vis[MAXN], inq[MAXN]; char str[MAXN]; struct Edge { int hou, nt, zhi; } sg[MAXM]; bool Cmp(int *r, int a, int b, int l) { return (r[a]==r[b])&&(r[a+l]==r[b+l]); } void Get_sa(int *r, int n, int m) { int *x = wa, *y = wb, *t, i, j, p; for(i = 0 ; i < m ; ++ i) ws[i] = 0; for(i = 0 ; i < n ; ++ i) ++ ws[x[i] = r[i]]; for(i = 1 ; i < m ; ++ i) ws[i] += ws[i-1]; for(i = n-1 ; i >= 0 ; -- i) sa[-- ws[x[i]]] = i; for(j = 1, p = 1 ; p < n ; j <<= 1, m = p) { for(i = n-j, p = -1 ; i < n ; ++ i) y[++ p] = i; for(i = 0 ; i < n ; ++ i) if(sa[i]>=j) y[++ p] = sa[i]-j; for(i = 0 ; i < n ; ++ i) wv[i] = x[y[i]]; for(i = 0 ; i < m ; ++ i) ws[i] = 0; for(i = 0 ; i < n ; ++ i) ++ ws[wv[i]]; for(i = 1 ; i < m ; ++ i) ws[i] += ws[i-1]; for(i = n-1 ; i >= 0 ; -- i) sa[-- ws[wv[i]]] = y[i]; for(t = x, x = y, y = t, i = 1, p = 1, x[sa[0]] = 0 ; i < n ; ++ i) { x[sa[i]] = Cmp(t, sa[i-1], sa[i], j)?p-1:p ++; } } return; } void Get_height(int *r, int n) { int i, j, k = 0; for(i = 1 ; i <= n ; ++ i) rk[sa[i]] = i; for(i = 0 ; i < n ; ht[rk[i ++]] = k) { for(k?-- k:k = 0, j = sa[rk[i]-1] ; r[i+k]==r[j+k] ; ++ k); } return; } void Get_ST(int n) { for(int i = 1 ; i <= n ; ++ i) st[0][i] = ht[i]; for(int i = 1 ; i <= lg[n] ; ++ i) { for(int j = 1 ; j+bin[i]-1 <= n ; ++ j) { st[i][j] = min(st[i-1][j], st[i-1][j+bin[i-1]]); } } return; } int Get_id(int x, int y) { if(x==0) return S; if(x==-1) return T; if(y==0||y>len[x]) return T_; if(y>0) return slen[x-1]*2+y; return slen[x-1]*2+len[x]-y; } int Query_ST(int x, int y) { x = rk[x], y = rk[y]; if(x>y) swap(x, y); ++ x; int k = lg[y-x+1]; return min(st[k][x], st[k][y-bin[k]+1]); } int Query(int a, int x, int b, int y) { if(x>0) a = lc[a]+x-1; else a = rc[a]+(len[a]+x); if(y>0) b = lc[b]+y-1; else b = rc[b]+(len[b]+y); return Query_ST(a, b); } bool loop(int x) { vis[x] = inq[x] = 1; for(int i = d[x] ; i != -1 ; i = sg[i].nt) { ++ du[sg[i].hou]; if(inq[sg[i].hou]||(!vis[sg[i].hou]&&loop(sg[i].hou))) return true; } return inq[x] = 0; } void Add_edge(int x, int y, int z) { sg[t] = (Edge){y, d[x], z}, d[x] = t ++; return; } void Add(int a, int x, int b, int y, int w) { a = Get_id(a, x), b = Get_id(b, y); Add_edge(a, b, w); return; } int main() { bin[0] = 1; for(int i = 1 ; i < 20 ; ++ i) bin[i] = bin[i-1]<<1; lg[0] = -1; for(int i = 1 ; i < MAXN ; ++ i) lg[i] = lg[i>>1]+1; sym = 26; scanf("%d", &n); for(int i = 1 ; i <= n ; ++ i) { scanf("%s", str); len[i] = strlen(str); slen[i] = slen[i-1]+len[i]; s[m ++] = ++ sym, lc[i] = m; for(int j = 0 ; j < len[i] ; ++ j) s[m ++] = str[j]-'a'+1; s[m ++] = ++ sym, rc[i] =m; for(int j = 0 ; j < len[i] ; ++ j) s[m ++] = str[len[i]-j-1]-'a'+1; } Get_sa(s, m+1, 300); Get_height(s, m); Get_ST(m); S = 0, T = slen[n]*2+10, T_ = T+1; bool fg = true; for(int i = 1 ; i <= n ; ++ i) if(Query(i, 0, i, -(len[i]+1))==len[i]) fg = false; memset(d, -1, sizeof(d)); ll ans = 0; for(int i = 1 ; fg && i <= n ; ++ i) { for(int j = 1 ; j <= len[i] ; ++ j) { int r = Query(i, j, i, -j), l = min(j, len[i]-j+1); ans = max(ans, (ll)r*2-1); if(l==r) { if(j>len[i]-j+1) Add(0, 0, i, -(j-r), r*2-1); else Add(0, 0, i, j+r, r*2-1); } } for(int j = 2 ; j <= len[i] ; ++ j) { int r = Query(i, j, i, -(j-1)), l = min(j-1, len[i]-j+1); ans = max(ans, (ll)r*2); if(l==r) { if(j-1>len[i]-j+1) Add(0, 0, i, -(j-1-r), r*2); else Add(0, 0, i, j+r, r*2); } } Add(0, 0, i, 1, 0); Add(0, 0, i, -len[i], 0); for(int j = 1 ; j <= len[i] ; ++ j) { int wf = 0, wr = 0; for(int k = 1 ; k <= n ; ++ k) { int r = Query(i, j, k, -len[k]); if(r==len[i]-j+1) Add(i, j, k, -(len[k]-r), r*2); else if(r==len[k]) Add(i, j, i, j+r, r*2); else wf = max(wf, r*2); r = Query(i, -j, k, 1); if(r==j) Add(i, -j, k, r+1, r*2); else if(r==len[k]) Add(i, -j, i, -(j-r), r*2); else wr = max(wr, r*2); } if(wf>0) Add(i, j, -1, 0, wf); if(wr>0) Add(i, -j, -1, 0, wr); } } for(int i = 0 ; i < MAXN ; ++ i) du[i] = vis[i] = inq[i] = 0; if(!fg || loop(S) || vis[T_]) ans = -1; else { int hd = 0, tl = 0; q[++ tl] = S; for(int i = 0 ; i < MAXN ; ++ i) dis[i] = 0; while(hd<tl) { int op = q[++ hd]; for(int i = d[op] ; i != -1 ; i = sg[i].nt) { dis[sg[i].hou] = max(dis[op]+sg[i].zhi, dis[sg[i].hou]); ans = max(ans, dis[sg[i].hou]); -- du[sg[i].hou]; if(!du[sg[i].hou]) q[++ tl] = sg[i].hou; } } } if(!~ans) printf("Infinity"); else printf("%lld", ans); // while(1); return 0; }