[关闭]
@2368860385 2020-11-07T03:05:00.000000Z 字数 6781 阅读 166

day2

正睿提高

2018.9.1


T1

感觉T1难不了,也没往难处想,一直推结论,找性质,中途刘高吉告诉了我正解,居然用超哥线段树,也没写,凸壳好像能做,但也没写。。。
感觉考试状态不对。
正解:,然后就是一些直线了,求一个凸壳,在上面二分,或者直接处理出来。

  1. /*
  2. 让b保留符号,x始终为正
  3. 如果是正数,那么axx+bx,就是x(ax+b),
  4. 如果是负数,那么axx-bx,就是x(ax-b)
  5. 所以正数就是对所有的ax+b直线做一下凸壳
  6. 负数就是对所有的ax-b直线做一下凸壳
  7. 然后查询时都带入x的绝对值。
  8. 可以在凸壳上二分(判断交点),但是发现点数不多,所以可以直接处理出来。
  9. */
  10. #include<cstdio>
  11. #include<algorithm>
  12. #include<cstring>
  13. #include<iostream>
  14. #include<cctype>
  15. #include<cmath>
  16. #include<set>
  17. #include<map>
  18. #include<queue>
  19. #include<vector>
  20. using namespace std;
  21. typedef long long LL;
  22. inline int read() {
  23. int x = 0, f = 1; char ch = getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f = -1;
  24. for (; isdigit(ch); ch=getchar()) x = x * 10 + ch - '0'; return x * f;
  25. }
  26. const int N = 500100;
  27. const int M = 32323;
  28. struct Line{
  29. int a,b;
  30. bool operator < (const Line &A) const {
  31. return a == A.a ? b > A.b : a < A.a;
  32. }
  33. }A[N], q1[N], q2[N];
  34. int n;
  35. LL ans[M + M + 100];
  36. double cross(Line A,Line B) {
  37. return (double)(B.b - A.b) / (double)(A.a - B.a);
  38. }
  39. void work(Line *q,int &Top) {
  40. q[++Top] = A[1];
  41. for (int i=2; i<=n; ++i) {
  42. if (A[i].a == A[i - 1].a) continue; // 斜率相同的保留b最大的
  43. while (Top >= 2 && cross(A[i], q[Top - 1]) <= cross(q[Top], q[Top - 1])) Top--;
  44. q[++Top] = A[i];
  45. }
  46. }
  47. int main() {
  48. n = read(); int Q = read();
  49. for (int i=1; i<=n; ++i) {
  50. A[i].a = read(), A[i].b = read();
  51. }
  52. int Top1 = 0, Top2 = 0;
  53. sort(A + 1, A + n + 1);
  54. work(q1, Top1);
  55. for (int i=1; i<=n; ++i) A[i].b = -A[i].b;
  56. sort(A + 1, A + n + 1);
  57. work(q2, Top2);
  58. for (int i=1,cur=1; i<=M; ++i) {
  59. while (cur < Top1 && cross(q1[cur], q1[cur + 1]) <= i) cur ++;
  60. ans[M + i] = 1ll * q1[cur].a * i * i + 1ll * q1[cur].b * i;
  61. }
  62. for (int i=1,cur=1; i<=M; ++i) {
  63. while (cur < Top2 && cross(q2[cur], q2[cur + 1]) <= i) cur ++;
  64. ans[M - i] = 1ll * q2[cur].a * i * i + 1ll * q2[cur].b * i;
  65. }
  66. while (Q--) {
  67. int x = read();
  68. printf("%lld\n",ans[x + M]);
  69. }
  70. return 0;
  71. }
  1. //二分代码
  2. void solve(int x,Line *q,int n) {
  3. int L = 1, R = n - 1, ans = -1;
  4. while (L <= R) {
  5. int mid = (L + R) >> 1;
  6. if (cross(q[mid], q[mid + 1]) <= x) ans = mid, L = mid + 1;
  7. else R = mid - 1;
  8. }
  9. LL Ans;
  10. if (ans == -1) Ans = 1ll * q[1].a * x * x + 1ll * q[1].b * x;
  11. else Ans = 1ll * q[ans + 1].a * x * x + 1ll * q[ans + 1].b * x;
  12. printf("%lld\n",Ans);
  13. }
  14. //询问时这样写
  15. while (Q--) {
  16. int x = read();
  17. if (x < 0) solve(-x, q2, Top2);
  18. else solve(x, q1, Top1);
  19. }

T2

感觉很不可做,直接没怎么看。后悔。
赛后感觉有60分暴力分。

1:暴力转移。
2:记录当前还有多少个1。f[i]表示当期有i个1的期望:
f[0] = 0, f[1] = 1;
然后
设初始局面的1的个数为x,逆推,求出f[x]的期望。
3478:只有两个点,那么要不给1号点-1,要不给二号点-1。
然后暴力统计每种答案*它出现的概率,即求它出现的次数。
设a = a[1], b = a[2]。那么答案区间就是a~a+b,因为最后a必须为0
考虑一个答案a+c(a减了a次,b减了c次)出现的次数。
1 1 2 2 1 2 1 1 1 1 这是操作序列,减a的就是1,减b就是2,那么就是将c个2放到n个空隙中,(最后一个一定是1,所以是n个,而不是n+1个),n个空隙,每个空隙放入c个球,然后从所有的n*c个球里面选c个。就是答案的方案数。
正解
利用期望的线性性,就是求出每一位期望选多少次,然后加起来。
计算每一位期望选多少次,就是和1号一起,1号选完了为止,可以利用3478的做法。复杂度(n*a)。其实这一步计算是可以推出来的。
可以看成在坐标轴的第一象限有一个点(a1,ai),求走到坐标轴的期望。


第一步是走到了x轴上的某一个点(不能是0),表示ai没有取完,但是a1去完了,所以就结束了。
方案数就是假设走到了往下走了i步,最后走到了(0,ai-i),对答案的贡献就是i(这一个数选的次数,然后乘上概率,就是期望次数了),然后有多少次i的贡献呢?因为最后只能从走到(0,i),所以不能在以前走到y轴,只能从(1,i)走到(0,i),所以就是从(a1,ai)走到(1,ai-i)的走法。就是长为a1-1,高为i的矩阵。一共走(a1-1+i)步,其中往下走i步,所以从中选出i步来的方案数就是总的方案数。除以就是概率,在乘以贡献i。
后面的就是ai选完了的贡献了,其概率就是1-ai没选完的概率,贡献就是ai。
发现从i到i+1的时候,就是只增加了一项,所以可以快速求出来。


其实就是a/b,x就是a/b,在模意义下就是a*b的逆元

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #include<iostream>
  5. #include<cctype>
  6. #include<cmath>
  7. #include<set>
  8. #include<map>
  9. #include<queue>
  10. #include<vector>
  11. using namespace std;
  12. typedef long long LL;
  13. inline int read() {
  14. int x = 0, f = 1; char ch = getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f = -1;
  15. for (; isdigit(ch); ch=getchar()) x = x * 10 + ch - '0'; return x * f;
  16. }
  17. const int N = 500100;
  18. const LL mod = 323232323;
  19. LL fac[N + N], ifac[N + N], imi[N + N];
  20. LL p[N], pi[N];
  21. int a[N];
  22. LL ksm(int a,int b) {
  23. LL ans = 1;
  24. while (b) {
  25. if (b & 1) ans = 1ll * ans * a % mod;
  26. a = 1ll * a * a % mod;
  27. b >>= 1;
  28. }
  29. return ans % mod;
  30. }
  31. void init(int n,int lim) {
  32. fac[0] = 1;
  33. for (int i=1; i<=lim; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
  34. ifac[lim] = ksm(fac[lim], mod - 2);
  35. for (int i=lim; i>=1; --i) ifac[i - 1] = 1ll * ifac[i] * i % mod;
  36. imi[lim] = ksm(ksm(2, lim), mod - 2);
  37. for (int i=lim; i>=1; --i) imi[i - 1] = 1ll * imi[i] * 2 % mod;
  38. }
  39. LL C(int n,int m) {
  40. LL t1 = fac[n], t2 = ifac[m], t3 = ifac[n - m];
  41. return t1 * t2 % mod * t3 % mod;
  42. }
  43. int main() {
  44. int n = read(), mx = 0;
  45. for (int i=1; i<=n; ++i) {
  46. a[i] = read(), mx = max(mx, a[i]);
  47. }
  48. init(n, mx + a[1]);
  49. for (int i=0; i<=mx; ++i) {
  50. p[i] = 1ll * C(a[1] + i - 1, i) * imi[a[1] + i] % mod;
  51. pi[i] = 1ll * p[i] * i % mod; // 这两行居然顺序写反了!!!
  52. if (i) p[i] = (p[i] + p[i - 1]) % mod; //
  53. if (i) pi[i] = (pi[i] + pi[i - 1]) % mod;
  54. }
  55. LL ans = a[1] % mod;
  56. for (int i=2; i<=n; ++i)
  57. ans = (ans + pi[a[i] - 1] + 1ll * (1 - p[a[i] - 1] + mod) * a[i] % mod) % mod;
  58. cout << ans % mod;
  59. return 0;
  60. }

T3

考试后半场,由于前两题没写出,就来写这个了。
想到了一个倍增的思路,fu[i][j]表示以i为起点,往上跳步的和,和为每个点到i的距离或上a。发现两段区间合并时,只是将一个区间的距离前面加上,然后统计这个区间上,第j位有多少个0,就是加上多少个
我想的是树剖统计,统计的复杂度就是(树剖一个,线段树/bit一个,(30棵线段树,每一位一个))。然后统计答案的时候,就可以倍增了。写着写着发现了问题,不能统计从一个点向下的答案。(太激动了,一想到就写了,时间也不算很多了,没想到不对就写了,以后注意证明一下思路,看一下是否真的可以处理)。于是就没写出来。

正解
首先可以在树上差分求一条路径上某一位0的个数可以O(1),代替树链剖分和线段树那两个log。
然后向上的就是上面写的那样,从lca向下,fd[i][j]表示从i往上
步,然后每个点距离f[i][j]的距离或上a,相当于反过来了,然后因为要使i是距离是dis(u,v),然后依次往上距离减小,所以让i,往上走dis步,然后减去让lca往上走dis(lca,u)步。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #include<iostream>
  5. #include<cctype>
  6. #include<cmath>
  7. #include<set>
  8. #include<map>
  9. #include<queue>
  10. #include<vector>
  11. using namespace std;
  12. typedef long long LL;
  13. inline int read() {
  14. int x = 0, f = 1; char ch = getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f = -1;
  15. for (; isdigit(ch); ch=getchar()) x = x * 10 + ch - '0'; return x * f;
  16. }
  17. const int N = 300100;
  18. int f[N][21], cnt[N][21], deth[N], a[N];
  19. LL fu[N][21], fd[N][21];
  20. // 因为f[i][j]是往上走2^j步,所以包含了2^j+1个点,所以只保留前面2^j个点,最后一个点不算。
  21. // fu[i][j]为i往上2^j方的点和,对于这所有的2^j个点中的一个k,贡献为dis(k,i)|a[k],将2^j个点的贡献加起来
  22. // fd[i][j]为i往上2^j方的点和,对于这所有的2^j个点中的一个k,贡献为(dis(k,f[i][j])-1)|a[k]
  23. // 就是将上面的距离反过来,从i开始,距离分别为2^j-1, 2^j-2...0
  24. vector<int> e[N];
  25. void dfs(int u,int fa) {
  26. deth[u] = deth[fa] + 1;
  27. f[u][0] = fa;
  28. for (int i=0; i<=20; ++i)
  29. cnt[u][i] = cnt[fa][i] + (!(a[u] & (1 << i))); // 每一位的前缀和
  30. for (int sz=e[u].size(),i=0; i<sz; ++i) {
  31. int v = e[u][i];
  32. if (v == fa) continue;
  33. dfs(v, u);
  34. }
  35. }
  36. int Lca(int u,int v) {
  37. if (deth[u] < deth[v]) swap(u, v);
  38. int d = deth[u] - deth[v];
  39. for (int i=20; i>=0; --i)
  40. if (d & (1 << i)) u = f[u][i];
  41. if (u == v) return u;
  42. for (int i=20; i>=0; --i)
  43. if (f[u][i] != f[v][i])
  44. u = f[u][i], v = f[v][i];
  45. return f[u][0];
  46. }
  47. LL query1(int u,int v) {
  48. LL res = 0;
  49. int d = deth[u] - deth[v];
  50. for (int i=20; i>=0; --i) {
  51. if (d & (1 << i)) {
  52. res += fu[u][i];
  53. u = f[u][i];
  54. res += 1ll * (1 << i) * (cnt[u][i] - cnt[v][i]); // 上一步的距离增加了2^j,所以多了j位的贡献
  55. }
  56. }
  57. return res;
  58. }
  59. LL query2(int u,int d) {
  60. LL res = 0;
  61. int v = u;
  62. for (int i=0; i<=20; ++i) { // 正序枚举 先走小的,再走大的,因为实际上是从上面往下走的
  63. if (d & (1 << i)) {
  64. res += fd[v][i];
  65. res += 1ll * (1 << i) * (cnt[u][i] - cnt[v][i]); // 越往上2^j步,其实下面的也有第j位的贡献,模拟一下
  66. v = f[v][i];
  67. }
  68. }
  69. return res;
  70. }
  71. int main() {
  72. int n = read(), Q = read();
  73. for (int i=1; i<=n; ++i) a[i] = read();
  74. for (int i=1; i<n; ++i) {
  75. int u = read(), v = read();
  76. e[u].push_back(v), e[v].push_back(u);
  77. }
  78. dfs(1, 0);
  79. for (int i=1; i<=n; ++i)
  80. fu[i][0] = fd[i][0] = a[i];
  81. for (int j=1; j<=20; ++j)
  82. for (int i=1; i<=n; ++i) {
  83. f[i][j] = f[f[i][j-1]][j-1];
  84. int t1 = f[i][j-1], t2 = f[i][j];
  85. fu[i][j] = fu[i][j-1] + fu[t1][j-1] + 1ll * (1<<(j-1)) * (cnt[t1][j-1] - cnt[t2][j-1]);
  86. // 两段区间合并时,上面的一段区间会在二进制下第j-1位上增加一个1,统计这一位多少个0,新增加的这一位的贡献
  87. // 以前这位是0,然后距离这位也是0,现在距离这位成了1,就是新加的贡献
  88. fd[i][j] = fd[i][j-1] + fd[t1][j-1] + 1ll * (1<<(j-1)) * (cnt[i][j-1] - cnt[t1][j-1]);
  89. // 同理,像上面一样,只不过是下面的一段区间更新。
  90. }
  91. while (Q--) {
  92. int u = read(), v = read();
  93. int lca = Lca(u, v), dis = deth[u] + deth[v] - deth[lca] * 2;
  94. LL Ans = query1(u, f[lca][0]) + query2(v, dis + 1) - query2(lca, deth[u] - deth[lca] + 1);
  95. // 由于fu[i][j]是跳到了f[i][j],但是答案却少了f[i][j]的,所以多跳一步。
  96. printf("%lld\n",Ans);
  97. }
  98. return 0;
  99. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注