2020牛客多校训练赛第二场合集


A、All with Pairs

题意:

给出$n$个串,定义$f(s,t)$是$s$的前缀和$t$的后缀的最长相同的长度,求$\sum \limits _{1\leq i\leq j\leq n}f(s_i,s_j)$。

题解:

看到前缀长度等于后缀长度,想到什么?对,就是$kmp$的$next$数组,但是这里只要最长的,所以对于比较短的相同前缀,就需要去重。但是我们暂时还不知道怎么去重,然后我们需要快速地找这些前缀后缀,使用哈希就很棒,为了防卡,使用双哈希就很棒。然后先把所有的字符串的后缀插到哈希表,然后对每一个串进行前缀查询,找到和这个串的前缀相同的哈希值的后缀的数量,然后考虑去重,比如,需要去重的是$"aba"$中的$"a"$子串,因为字符串枚举前缀的时候,是从大到小枚举,所以就考虑碰到一个前缀$j$,就减去这个串已经统计的部分。

AC代码:

 1 #include <bits/stdc++.h>
 2 #include <unordered_map>
 3 using namespace std;
 4 const int N = 1e5 + 5;
 5 const int M = 1e6 + 5;
 6 typedef long long ll;
 7 string str[N];
 8 int nxt[M];
 9 ll hmod[2] = {998244353, 1000000007}, base[2] = {131, 37};
10 ll hs[2][M], f[2][M];
11 ll vis[M];
12 struct p_hash
13 {
14     template <class T1, class T2>
15     size_t operator()(const pair<T1, T2> &p) const
16     {
17         return hash<T1>{}(p.first) ^ hash<T2>{}(p.second);
18     }
19 };
20 unordered_map<pair<ll, ll>, ll, p_hash> mp;
21 void init()
22 {
23     f[0][0] = f[1][0] = 1;
24     for (int i = 0; i <= 1; ++i)
25         for (int j = 1; j < M; ++j)
26             f[i][j] = f[i][j - 1] * base[i] % hmod[i];
27 }
28 ll get_hash(int l, int r, int j)
29 {
30     return (hs[j][r] - hs[j][l - 1] * f[j][r - l + 1] % hmod[j] + hmod[j]) % hmod[j];
31 }
32 void get_next(const string &str)
33 {
34     nxt[0] = -1;
35     int j = -1, k = 0;
36     while (k < str.size())
37     {
38         if (j == -1 || str[j] == str[k])
39             nxt[++k] = ++j;
40         else
41             j = nxt[j];
42     }
43 }
44 const ll mod = 998244353;
45 int main()
46 {
47     init();
48     ios::sync_with_stdio(0);
49     cin.tie(0);
50     int n;
51     cin >> n;
52     for (int i = 1; i <= n; ++i)
53         cin >> str[i];
54     for (int i = 1; i <= n; ++i)
55     {
56         pair<ll, ll> tmp;
57         int len = str[i].size();
58         for (int j = 0; j <= 1; ++j)
59             for (int k = 0; k < len; ++k)
60                 hs[j][k + 1] = (hs[j][k] * base[j] % hmod[j] + str[i][k] - 'a' + 1) % hmod[j];
61         for (int j = 1; j <= len; ++j)
62         {
63             tmp.first = get_hash(j, len, 0);
64             tmp.second = get_hash(j, len, 1);
65             ++mp[tmp];
66         }
67     }
68     ll ans = 0;
69     for (int i = 1; i <= n; ++i)
70     {
71         get_next(str[i]);
72         int len = str[i].size();
73         memset(vis, 0, sizeof(vis[0]) * (str[i].size() + 1));
74         for (int j = 0; j <= 1; ++j)
75             for (int k = 0; k < len; ++k)
76                 hs[j][k + 1] = (hs[j][k] * base[j] % hmod[j] + str[i][k] - 'a' + 1) % hmod[j];
77         pair<ll, ll> tmp;
78         for (int j = len; j; --j)
79         {
80             tmp.first = get_hash(1, j, 0);
81             tmp.second = get_hash(1, j, 1);
82             ans = (ans + (mp[tmp] - vis[j] + mod) % mod * j % mod * j % mod) % mod;
83             int tnxt = nxt[j];
84             vis[tnxt] = (vis[tnxt] + mp[tmp]) % mod;
85         }
86     }
87     cout << ans << endl;
88     return 0;
89 }
View Code

B、Boundary

题意:

给出一些点,问最多有几个点和$(0,0)$共一个圆?$n\leq 2e3$。

题解:

显然不能$O(n^3)$暴力枚举点对。所以我们就只能枚举一个点,然后因为$(0,0)$也在圆上,然后对于第三个点,它们在一个圆上,圆周角就要一样。然后直接根据向量规则求夹角的余弦值就行,但是这个题余弦值的精度差很小,所以需要对这些数乘$1e14$变成整数再存进$map$里面。如果直接算出圆心的位置,对精度的要求就会小一些。

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 2e3 + 5;
 4 unordered_map<long long, int> mp;
 5 pair<int, int> p[N];
 6 pair<int, int> operator-(const pair<int, int> &a, const pair<int, int> &b)
 7 {
 8     return make_pair(a.first - b.first, a.second - b.second);
 9 }
10 double dist(const pair<int, int> &vec)
11 {
12     return sqrt(vec.first * vec.first + vec.second * vec.second);
13 }
14 double dotcos(const pair<int, int> &a, const pair<int, int> &b)
15 {
16     return (a.first * b.first + a.second * b.second) / (dist(a) * dist(b));
17 }
18 int cross(const pair<int, int> &a, const pair<int, int> &b)
19 {
20     return a.first * b.second - b.first * a.second;
21 }
22 int main()
23 {
24     int n;
25     scanf("%d", &n);
26     for (int i = 1; i <= n; ++i)
27         scanf("%d%d", &p[i].first, &p[i].second);
28     int maxn = 0;
29     for (int i = 1; i <= n; ++i)
30     {
31         mp.clear();
32         for (int j = 1; j <= n; ++j)
33             if (cross(p[i], p[j]) < 0)
34                 ++mp[dotcos(p[j] - make_pair(0, 0), p[j] - p[i]) * 1e14];
35         for (auto &j : mp)
36             maxn = max(maxn, j.second);
37     }
38     printf("%d\n", maxn + 1);
39     return 0;
40 }
View Code

C、Cover the Tree

题意:

给出一棵树,每次找两个点连路径,求最小需要连多少路径使得树上每条边都覆盖。

题解:

显然都连叶子最优。自己写的时候考虑了类似于树的重心,找到了叶节点的重心,但是实现太垃圾,一直不是$wa$就是$tle$。

看了正解才发现真的简单。直接看代码就行,正确性:设$s$是叶子节点个数,所有前$s/2$和后$s/2$个叶子结点往上的所有边都会被覆盖,这样子不可能找到一棵树,还有未被覆盖的边。

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 2e5 + 5;
 4 typedef long long ll;
 5 vector<int> G[N], lef;
 6 void dfs(int u, int fa)
 7 {
 8     if (G[u].size() == 1)
 9     {
10         lef.push_back(u);
11         return;
12     }
13     for (auto i : G[u])
14         if (i != fa)
15             dfs(i, u);
16 }
17 void solve()
18 {
19     int n;
20     scanf("%d", &n);
21     for (int i = 1; i < n; ++i)
22     {
23         int u, v;
24         scanf("%d%d", &u, &v);
25         G[u].push_back(v);
26         G[v].push_back(u);
27     }
28     if (n == 1)
29     {
30         printf("0\n");
31         return;
32     }
33     if (n == 2)
34     {
35         printf("1\n1 2\n");
36         return;
37     }
38     int rt = 0;
39     for (int i = 1; i <= n; ++i)
40         if (G[i].size() != 1)
41         {
42             rt = i;
43             break;
44         }
45     dfs(rt, 0);
46     printf("%d\n", (lef.size() + 1) / 2);
47     for (int i = 0; i < lef.size() / 2; ++i)
48         printf("%d %d\n", lef[i], lef[i + (lef.size() + 1) / 2]);
49     if (lef.size() % 2)
50         printf("%d %d\n", lef[lef.size() / 2], rt);
51 }
52 int main()
53 {
54     int T = 1;
55     //scanf("%d", &T);
56     while (T--)
57         solve();
58     return 0;
59 }
View Code

D、Duration

题意:

太简单了不说了。

题解:

随便减一下取个绝对值就行。

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5 + 5;
 4 typedef long long ll;
 5 char s[10];
 6 void solve()
 7 {
 8     scanf("%s", s);
 9     int hh = s[0] * 10 + s[1];
10     int mm = s[3] * 10 + s[4];
11     int ss = s[6] * 10 + s[7];
12     int tot = hh * 3600 + mm * 60 + ss;
13     scanf("%s", s);
14     hh = s[0] * 10 + s[1];
15     mm = s[3] * 10 + s[4];
16     ss = s[6] * 10 + s[7];
17     tot = hh * 3600 + mm * 60 + ss - tot;
18     printf("%d\n", abs(tot));
19 }
20 int main()
21 {
22     int T = 1;
23     //scanf("%d", &T);
24     while (T--)
25         solve();
26     return 0;
27 }
View Code

E、Exclusive OR

队友补。

F、Fake Maxpooling

题意:

设矩阵中$a_{ij} = lcm(i,j)$,求边长为$k$的子方阵最大值的和。$n\leq 5e3,m\leq 5e3$。

题解:

构造矩阵的时候需要利用线性筛的思路优化,虽然暴力求解卡常也能过。然后就套路题了,单调队列套单调队列。

坑点:不能$a[i][j]=a[j][i]=lcm(i,j)$,因为不保证一定是方阵,如果非要这样子,就要求完长度是$max(n,m)$的方阵。

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int maxn = 5100;
 5 int aa[maxn][maxn], mp[maxn][maxn], q[maxn];
 6 inline int gcd(int a, int b)
 7 {
 8     return !b ? a : gcd(b, a % b);
 9 }
10 inline int lcm(int a, int b)
11 {
12     return a * b / gcd(a, b);
13 }
14 int main()
15 {
16     int n, m, a, b, k;
17     scanf("%d%d%d", &n, &m, &k);
18     a = k, b = k;
19     for (int i = 1; i <= n; ++i)
20         for (int j = 1; j <= m; ++j)
21             aa[i][j] = lcm(i, j);
22     for (int i = 1; i <= n; i++)
23     {
24         int l = 1, r = 0;
25         for (int j = 1; j <= b; j++)
26         {
27             while (r && aa[i][q[r]] < aa[i][j])
28                 r--;
29             q[++r] = j;
30         }
31         mp[i][1] = aa[i][q[l]];
32         for (int j = 2; j + b - 1 <= m; j++)
33         {
34             if (q[l] == j - 1)
35                 l++;
36             while (l <= r && aa[i][q[r]] < aa[i][j + b - 1])
37                 r--;
38             q[++r] = j + b - 1;
39             mp[i][j] = aa[i][q[l]];
40         }
41     }
42     ll ans = 0;
43     for (int j = 1; j <= m; j++)
44     {
45         int l = 1, r = 0;
46         for (int i = 1; i <= a; i++)
47         {
48             while (r && mp[q[r]][j] < mp[i][j])
49                 r--;
50             q[++r] = i;
51         }
52         (ans += mp[q[l]][j]);
53         for (int i = 2; i + a - 1 <= n; i++)
54         {
55             if (q[l] == i - 1)
56                 l++;
57             while (l <= r && mp[q[r]][j] < mp[i + a - 1][j])
58                 r--;
59             q[++r] = i + a - 1;
60             (ans += mp[q[l]][j]);
61         }
62     }
63     printf("%lld\n", ans);
64     return 0;
65 }
View Code

G、Greater and Greater

题意:

给出$a$和$b$序列,问$a$当中有多少个子数组,对应位置的数大小都大于$b$。

题解:

看数据范围暴力可以卡常过,所以考虑怎么卡常,$bitset$是卡常利器。所以我们就想一下怎么用$bitset$卡常,然后把$bitset$的$a_i$大于$b_j$的$i$位设成$1$,最后右移$j$位,得到合法的数的数量。然后对于每个$b$序列都这么处理,最后取与即可。实现的时候,先降序排序然后用一个$bitset$记录就行了,因为前面枚举到的数一定大于后面枚举到的。

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1.5e5 + 5;
 4 bitset<N> f, g;
 5 struct node
 6 {
 7     int val, id;
 8 };
 9 node a[N], b[N];
10 int main()
11 {
12     int n, m;
13     scanf("%d%d", &n, &m);
14     for (int i = 1; i <= n; ++i)
15         scanf("%d", &a[i].val), a[i].id = i;
16     for (int i = 1; i <= m; ++i)
17         scanf("%d", &b[i].val), b[i].id = i;
18     sort(a + 1, a + n + 1, [](const node &a, const node &b) -> bool { return a.val > b.val; });
19     sort(b + 1, b + m + 1, [](const node &a, const node &b) -> bool { return a.val > b.val; });
20     f.set();
21     g.reset();
22     for (int i = 1, j = 1; i <= m; ++i)
23     {
24         while (j <= n && a[j].val >= b[i].val)
25             g.set(a[j++].id);
26         //cout << "g=" << g << endl;
27         f &= g >> b[i].id;
28         //cout << "f=" << f << endl;
29     }
30     printf("%d\n", f.count());
31     return 0;
32 }
View Code

H、Happy Triangle

题意:

维护一个集合$s$,给出以下指令:

$1$、添加一个数

$2$、删除一个数

$3$、给出一个数,询问里面有没有两个数和这个数组成非退化三角形。

题解:

操作$1$和$2$都是小意思,康康第三个怎么整。如果$x$是最大边,这样子找两个前驱就行了,如果不是,就找两个$a$,$b$,且$a$要大于等于$x$和$b$,$a-b$小于$x$。关键是维护这个有序序列的相邻差值和求区间最小值,使用权值线段树。线段树维护三个值,区间最小值,区间最大值和区间最小的差值。对于长度是$1$的区间,如果有几个数,则差值是$0$,如果只有一个数,视为无穷大,差值的维护就是,对于一段区间,最小差值来自子区间和跨区间取得(就是右区间最小减左区间最大),取最小上推。查询同理。

AC代码:

  1 #include <bits/stdc++.h>
  2 using namespace std;
  3 const int N = 4e5 + 5;
  4 const int inf = 0x7fffffff;
  5 int mp[N];
  6 int b[N], top;
  7 struct segtree
  8 {
  9 #define ls (k << 1)
 10 #define rs ((k << 1) + 1)
 11     struct node
 12     {
 13         int l, r, maxn, minn, gap;
 14     };
 15     node tr[N << 1];
 16     inline void pushup(int k)
 17     {
 18         tr[k].maxn = max(tr[ls].maxn, tr[rs].maxn);
 19         tr[k].minn = min(tr[ls].minn, tr[rs].minn);
 20         tr[k].gap = min(tr[ls].gap, tr[rs].gap);
 21         tr[k].gap = min(tr[k].gap, tr[rs].minn - tr[ls].maxn);
 22     }
 23     void build(int l, int r, int k)
 24     {
 25         tr[k].l = l;
 26         tr[k].r = r;
 27         if (l == r)
 28         {
 29             tr[k].maxn = 0;
 30             tr[k].minn = inf;
 31             tr[k].gap = inf;
 32             return;
 33         }
 34         int m = (l + r) >> 1;
 35         build(l, m, ls);
 36         build(m + 1, r, rs);
 37         pushup(k);
 38     }
 39     void update(int k, int pos, int val)
 40     {
 41         if (tr[k].l == tr[k].r)
 42         {
 43             mp[pos] += val;
 44             tr[k].gap = inf;
 45             if (mp[pos] == 0)
 46             {
 47                 tr[k].maxn = 0;
 48                 tr[k].minn = inf;
 49             }
 50             else
 51             {
 52                 tr[k].maxn = tr[k].minn = b[pos];
 53                 if (mp[pos] >= 2)
 54                     tr[k].gap = 0;
 55             }
 56             return;
 57         }
 58         int m = (tr[k].l + tr[k].r) >> 1;
 59         if (pos <= m)
 60             update(ls, pos, val);
 61         else
 62             update(rs, pos, val);
 63         pushup(k);
 64     }
 65     int querym(int l, int r, int k, int op)
 66     {
 67         if (l > r)
 68             return op ? 0 : inf;
 69         if (l <= tr[k].l && tr[k].r <= r)
 70             return op ? tr[k].maxn : tr[k].minn;
 71         int m = (tr[k].l + tr[k].r) >> 1;
 72         int ans1 = 0, ans2 = inf, tmp;
 73         if (l <= m)
 74         {
 75             tmp = querym(l, r, ls, op);
 76             ans1 = max(ans1, tmp);
 77             ans2 = min(ans2, tmp);
 78         }
 79         if (r > m)
 80         {
 81             tmp = querym(l, r, rs, op);
 82             ans1 = max(ans1, tmp);
 83             ans2 = min(ans2, tmp);
 84         }
 85         return op ? ans1 : ans2;
 86     }
 87     int queryg(int l, int r, int k)
 88     {
 89         if (l > r)
 90             return inf;
 91         if (l <= tr[k].l && tr[k].r <= r)
 92             return tr[k].gap;
 93         int m = (tr[k].l + tr[k].r) >> 1;
 94         int ans = inf;
 95         int f = 0;
 96         if (l <= m)
 97             ans = min(ans, queryg(l, r, ls)), ++f;
 98         if (r > m)
 99             ans = min(ans, queryg(l, r, rs)), ++f;
100         if (f == 2)
101             ans = min(ans, tr[rs].minn - tr[ls].maxn);
102         return ans;
103     }
104 #undef ls
105 #undef rs
106 };
107 pair<int, int> q[N];
108 #define x first
109 #define y second
110 segtree tr;
111 bool check(int op, int pos, int val)
112 {
113     if (mp[pos] >= 2)
114         return 1;
115     else if (mp[pos] == 1)
116     {
117         int maxn = tr.querym(1, pos - 1, 1, 1);
118         if (maxn != 0)
119             return 1;
120         int ming = tr.queryg(pos, top, 1);
121         if (ming < val)
122             return 1;
123         return 0;
124     }
125     int maxn = tr.querym(1, pos, 1, 1);
126     int minn = tr.querym(pos, top, 1, 0);
127     if (maxn != 0 && minn != inf && maxn + val > minn)
128         return 1;
129     int mpos = lower_bound(b + 1, b + top + 1, maxn) - b;
130     if (maxn)
131     {
132         tr.update(1, mpos, -1);
133         int smaxn = tr.querym(1, pos, 1, 1);
134         tr.update(1, mpos, 1);
135         if (smaxn && smaxn + maxn > val)
136             return 1;
137     }
138     int ming = tr.queryg(pos, top, 1);
139     if (ming < val)
140         return 1;
141     return 0;
142 }
143 int main()
144 {
145     int n;
146     scanf("%d", &n);
147     for (int i = 1; i <= n; ++i)
148     {
149         scanf("%d%d", &q[i].x, &q[i].y);
150         b[++top] = q[i].y;
151     }
152     sort(b + 1, b + top + 1);
153     top = unique(b + 1, b + top + 1) - b - 1;
154     for (int i = 1; i <= n; ++i)
155         q[i].y = lower_bound(b + 1, b + top + 1, q[i].y) - b;
156     tr.build(1, top, 1);
157     for (int i = 1; i <= n; ++i)
158     {
159         if (q[i].x == 1)
160             tr.update(1, q[i].y, 1);
161         else if (q[i].x == 2)
162             tr.update(1, q[i].y, -1);
163         else
164             printf("%s", check(q[i].x, q[i].y, b[q[i].y]) ? "Yes\n" : "No\n");
165     }
166     return 0;
167 }
View Code

I、Interval

队友补。

J、Just Shuffle

队友补

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5 + 5;
 4 int a[N], b[N];
 5 vector<int> v;
 6 bool vis[N];
 7 int main()
 8 {
 9     int n, m;
10     scanf("%d%d", &n, &m);
11     for (int i = 1; i <= n; ++i)
12         scanf("%d", &a[i]);
13     for (int i = 1; i <= n; ++i)
14         if (!vis[i])
15         {
16             v.clear();
17             int x = a[i];
18             while (!vis[x])
19             {
20                 vis[x] = 1;
21                 v.push_back(x);
22                 x = a[x];
23             }
24             int inv, r = v.size();
25             for (int i = 0; i < r; ++i)
26                 if (1ll * m * i % r == 1)
27                     inv = i;
28             for (int i = 0; i < r; ++i)
29                 b[v[i]] = v[(i + inv) % r];
30         }
31     for (int i = 1; i <= n; ++i)
32         printf("%d%c", b[i], " \n"[i == n]);
33     return 0;
34 }
View Code

K、Keyboard Free

队友补。

posted @ 2020-07-17 23:47  Aya_Uchida  阅读(313)  评论(0编辑  收藏  举报