2022杭电多校第一场补题

显然还没补完

题目

A. String

KMP、Border树

题意

找字符串 S 的所有前缀子串 Si,所有 border 长度 b 满足 (2bi)%k=0border 数量。

数据范围

|S|106

解法一, 建 border 树硬搞 O(nlogn)

思路

  • 将题目要求经过简单的化简就是题意,先建立 border 树。
  • 对于 border 树中每个节点寻找满足要求的祖先节点的个数。
  • 考虑 dfs 同时开 k 个 vector 即 vector<int> cnt[K] 来记录 %k=i 的数字。
    • 每遍历一个点进行 cnt[2 * u % k].push_back(2 * u)
    • 然后因为 dfs 过程中通过回溯记录的是根到节点一条链上的信息,所以利用二分查询 cnt[u % k] 中大于 u 的个数记录为 ans[i] 为节点最终的答案。
  • 最后统计答案即可,然后因为SB杭电,恭喜我们 1e6 的递归层数也爆栈了,所以需要手搓一个递归栈。
  • 时间复杂度 O(nlogn),应该有别的解法,待补。

Solution

#include<bits/stdc++.h> #define fi first #define pb push_back #define se second #define arr(x) x.begin(),x.end() #define endl '\n' using namespace std; typedef std::pair<int, int> PII; typedef long long ll; const int mod = 998244353, N = 1e6 + 10; char s[N]; vector<int> edge[N], cnt[N]; int ne[N], k; ll ans[N]; void get_ne(const char* s, int len) { ne[1] = 0; edge[0].pb(1); for (int i = 2, j = 0; i <= len; i++) { while (j && s[j + 1] != s[i]) j = ne[j]; if (s[i] == s[j + 1]) j ++; ne[i] = j; edge[j].pb(i); } return ; } // 实际dfs void dfs(int u) { int val = 2 * u % k; cnt[val].pb(2 * u); int r = u % k; if(cnt[r].size()) ans[u] = cnt[r].size() - (upper_bound(arr(cnt[r]), u) - cnt[r].begin()); else ans[u] = 0; for (auto v: edge[u]) { dfs (v); } cnt[val].pop_back(); } // 手写栈,里面变量不能放多了,不然也炸 :( struct Data { int u, idx; }; stack<Data> stk; int top = -1; void solve(int x) { stk.push((Data){x, 0}); while (!stk.empty()) { auto now = stk.top(); stk.pop(); int u = now.u, idx = now.idx, sz = edge[u].size(); int val = 2 * u % k, r = u % k; if (!idx) { cnt[val].pb(2 * u); if(cnt[r].size()) ans[u] = cnt[r].size() - (upper_bound(arr(cnt[r]), u) - cnt[r].begin()); else ans[u] = 0; } if (idx < sz) { stk.push((Data){u, idx+1}); stk.push((Data){edge[u][idx], 0}); } else { cnt[val].pop_back(); } } } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int T; cin >> T; while (T--) { cin >> (s + 1); int n = strlen(s + 1); for (int i = 0; i <= n; i++) edge[i].clear(); get_ne(s, n); cin >> k; // dfs(0); solve(0); ll res = 1; for (int i = 1; i <= n; i++) { res = res * (ans[i] + 1) % mod; ans[i] = 0; } cout << res << endl; } return 0; }

B. Dragon slayer

二进制枚举、dfs/bfs、坐标放大

题意
n×m 的方格图上,有 k 个横或竖的墙,起始坐标 (xs+0.5,ys+0.5),结尾坐标 (xt+0.5,yt+0.5 。问至少破坏多少面墙才能从起点到终点。

数据范围
1n,m,k15
0xs,xtn,0ys,ytm
每面墙坐标: 0x1,x2n,0y1,y2m,x1=x2ory1=y2

思路

  • k 范围很小,先枚举剩下了那些墙不能撞, O(2k)
  • 因为起始坐标很奇怪,不妨将坐标全部放大两倍,每次移动距离为 2
  • 然后进行 dfs/bfs 判断能否达到终点,每次移动判断起点到对应终点的中点是否是不能被破坏的墙,如果不是就讲继续搜下一个点。
  • 然后就做完了,时间复杂度 O(2kknm)

Solution

#include<bits/stdc++.h> #define fi first #define se second #define arr(x) x.begin(),x.end() #define endl '\n' using namespace std; typedef std::pair<int, int> PII; typedef long long ll; const int mod = 1e9 + 7; #define y1 sajdakwjdiasjidawij int _read() { static int ans, c, p; for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar()); if (c == '-') p = false, c = getchar(); else p = true; for (ans = 0; c <= '9' && c >= '0'; c = getchar()) ans = ans * 10 + c - '0'; return p ? ans : -ans; } void _write(int ans) { static int a[20], n; if (ans < 0) { putchar('-'); ans = -ans; } if (ans == 0) { putchar('0'); return; } for (n = 0; ans; ans /= 10) a[n++] = ans % 10; for (n--; n >= 0; n--) putchar(a[n] + '0'); return; } int n, m, k, xs, ys, xt, yt, wall; int x1[16], x2[16], y1[16], y2[16]; void init() { n = _read() * 2; m = _read() * 2; k = _read(); xs = _read() * 2 + 1; ys = _read() * 2 + 1; xt = _read() * 2 + 1; yt = _read() * 2 + 1; for (int i = 0; i < k; i++) { x1[i] = _read() * 2; y1[i] = _read() * 2; x2[i] = _read() * 2; y2[i] = _read() * 2; if (x1[i] > x2[i]) swap(x1[i], x2[i]); if (y1[i] > y2[i]) swap(y1[i], y2[i]); } } bool p[16], col[40][40]; const int dx[4] = {0, 2, 0, -2}; const int dy[4] = {2, 0, -2, 0}; void dfs(int x, int y) { col[x][y] = true; for (int i = 0, tx, ty; i < 4; i++) { tx = x + dx[i]; ty = y + dy[i]; int midx = x + dx[i] / 2, midy = y + dy[i] / 2; if (tx >= n || tx < 0 || ty < 0 || ty >= m || col[tx][ty]) continue; bool fl = false; for (int j = 0; j < k; j++) { if (!(wall >> j & 1)) continue; if (x1[j] == x2[j]) { if (midx == x1[j] && midy >= y1[j] && midy <= y2[j]) { fl = true; break; } } else { if (midy == y1[j] && midx >= x1[j] && midx <= x2[j]) { fl = true; break; } } } if (!fl) dfs(tx, ty); } return; } int search() { int ans = k, tn = 1 << k; for (int i = 0; i < tn; i++) { if (k - __builtin_popcount(i) > ans) continue; memset(col, 0, sizeof col); wall = i; // cout << "wall:"<<wall<<endl; dfs(xs, ys); if (col[xt][yt]) { if (k - __builtin_popcount(i) < ans) ans = k - __builtin_popcount(i); } } return ans; } int main() { for (int T = _read(); T; T--) { init(); _write(search()); putchar('\n'); } return 0; }

C. backpack

bitset优化背包DP

题意

求恰好装满背包的时候, 异或值最大值。

数据范围
T10
1n,m<210
1vi,wi<210

思路

  • 首先要考虑暴力dp如何设计:f[i][j][k] 表示到物品 i,异或值为 j,体积为 k 的方案是否存在。
    • f[i][j][k]=f[i1][j][k]+f[i1][jw][kv]
  • 如果正常来做,空间可以滚动,但是时间复杂度好像炸了是 O(230)
  • 如果有经验的话,可以用 bitset 来优化体积的转移,因为是加减。整体复杂度除 64 。最后算下来是 O(230/64),可以通过。
  • 转移就是
    • fi,j,k=fi1,j,k|fi1,jwi,k<<vi

Solution

#include<bits/stdc++.h> typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> PII; typedef std::pair<ll, ll> PLL; typedef double db; #define arr(x) (x).begin(),(x).end() #define fi first #define se second #define pb push_back #define endl "\n" using namespace std; /*----------------------------------------------------------------------------------------------------*/ const int N = 1030; bitset<1030> f[N], g[N]; int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int T; cin >> T; while (T--) { int n, m; cin >> n >> m; f[0][0] = 1; for (int i = 1; i <= n; i++) { int v, w; cin >> v >> w; for (int j = 0; j < 1024; j++) { g[j] = f[j]; g[j] <<= v; } for (int j = 0; j < 1024; j++) { f[j] |= g[j ^ w]; } } int ans = -1; for (int j = 0; j < 1024; j++) { if (f[j][m]) { ans = j; } } cout << ans << endl; for (int i = 0; i <= 1024; i++) f[i].reset(), g[i].reset(); } return 0; }

D. Ball

bitset, 枚举

题意

给了 n 个点的坐标和大小为 mm 的方格上,任意三元组之间 3 个曼哈顿距离的中位数是质数的三元组个数。

数据范围
1N2000
1M105

思路

  • 题意翻译成人话,对三元组 (a,b,c) 假设 laclablbclab 是质数的个数。
  • 先筛掉 2e5 内的质数,N2000,显然可以考虑枚举,n3 的枚举显然寄了,考虑怎么优化呢?
  • 我们可以在 O(n2) 复杂度求得,每个点之间的距离,得到邻接矩阵。
  • 对于每条长度为素数的边,我们考虑如何找对应的答案,需要找一条比它小的和比它大的。
    • 对于比较乱的序列,一个习惯是先排序,从小到大枚举,先出现的边显然比当前边小,怎么找比当前边大的并且端点符合要求呢?
    • 这其实有一点像 bitset 优化找三元环的过程,这里给出一道题目提供练习,ABC258_G
    • 然而这里的长度是大于 1 的整数,但我们可以通过从小到大的枚举边的方式,把长度变为 0 或者 1,代表比当前边大还是小。方式其实无所谓,但初始化为 0 要方便一些。
    • 所以开 n 个长度为 n bitset bt 替代邻接矩阵,bt[i][j]=1 代表 lij 比当前边小,否则大,枚举完一条边后更新邻接矩阵。
    • 对于每个长度为素数的边 lab 的答案就是 bt[a]^bt[b] 中 1 的数量。而因为从小到大枚举答案记录是不重不漏的,可以考虑 laclablbc 三条边相同连续被遍历,但是我们统计答案并不会有重复
  • 时间复杂度 O(n3/64)64 是 bitset 的分母常数根据设备而定。

Solution

#include<bits/stdc++.h> typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> PII; typedef std::pair<ll, ll> PLL; typedef double db; #define arr(x) (x).begin(),(x).end() #define get_sz(v) (int)v.size() #define fi first #define se second #define pb push_back #define endl "\n" using namespace std; /*----------------------------------------------------------------------------------------------------*/ const int N = 2010, M = 2e5 + 10; int primes[M], cnt; // primes[]存储所有素数 bool st[M]; // st[x]存储x是否被筛掉 bitset<2010> bt[N]; struct P { int a, b, c; bool operator < (const P& o) const { return c < o.c; } }v[N * N]; void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (st[i]) continue; primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i) st[j] = true; } } int main() { int T; scanf("%d", &T); st[1] = true; get_primes(M - 9); while (T--) { int n, m; scanf("%d%d", &n, &m); vector<PII> a(n); for (int i = 0; i <= n; i++) bt[i].reset(); for (auto & x : a) scanf("%d%d", &x.fi, &x.se); int cnt = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { int dist = abs(a[i].fi - a[j].fi) + abs(a[i].se - a[j].se); v[cnt++] = (P){i + 1, j + 1, dist}; } } sort(v, v + cnt); ll ans = 0; for (int i = 0; i < cnt; i++) { auto t = v[i]; if (!st[t.c]) ans += (bt[t.a] ^ bt[t.b]).count(); bt[t.a][t.b] = 1, bt[t.b][t.a] = 1; } printf("%lld\n", ans); } return 0; }

H. Path (未补)

I. Laser

思维枚举、计算几何、放缩坐标

题意

K. Random

签到直接输入 (nm)/2 即可,需要求下 2 的逆元。

Solution

#include<bits/stdc++.h> typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> PII; typedef std::pair<ll, ll> PLL; typedef double db; #define arr(x) (x).begin(),(x).end() #define fi first #define se second #define pb push_back #define endl "\n" using namespace std; /*----------------------------------------------------------------------------------------------------*/ const int mod = 1e9 + 7; ll qmi(ll a, ll k, int mod) { ll res = 1; while (k) { if (k & 1) res = res * a % mod; a = a * a % mod; k >>= 1; } return res; } int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int T; cin >> T; while (T--) { int n, m; cin >> n >> m; cout << (n - m) * qmi(2, mod - 2, mod) % mod << endl; } return 0; }

L. Alice and Bob

博弈、思维、按位赋权

题意

Alice 每次将所有数分成两个集合, Bob 每次删除一个集合,剩下的集合整体减 1。如果任何时候集合中的数有 0 Alice 赢, 如果 Bob 把数删光了,则 Bob 赢。

输入是 n+1 个值为 i 的个数

数据范围
Σai1e6
n1e6

思路

  • 多模拟一下可以发现,对于某个值 x2x 个这个数的话,Alice 是必赢的。比如 1 个 0 或 2 个 1 或 4 个 2。从 Alice 的角度思考问题。
  • 其实 1 个 1 和 2 个 2 也能赢,先把 1 给分开就赢了。
  • 再试试又发现从 1 开始连续的 n 个数,其中任意一个数有 2 个就能赢。
  • 有更正面的考虑方法。考虑问题时,如果一边有一个 1 ,那这个 1 是必删的,如果有 2 个 2 那这两个 2 Bob 是必删的,如果有 4 个 3 那也是必删的。剩余的数会减 1 ,那其实意味着要赢的话,他们所需要的个数减少了一半。
  • 如果从前往后做,记录 sum ,到每一位 i 判断 sum+a[i]2i 大于就赢。否则 sum = 2 * (sum + a[i]) 。这样做一定能行,但是做不了,因为存不下。
  • 有更 smart 的方式是倒着做,这其实实际上是对每一位赋予权重 1/2x ,最后权重之和大于 1 Alice 就能赢。具体见代码。
  • 时间复杂度 O(n)

Solution

#include<bits/stdc++.h> #define fi first #define se second #define arr(x) x.begin(),x.end() #define endl '\n' using namespace std; typedef std::pair<int, int> PII; typedef long long ll; const int mod = 1e9 + 7; int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int T; cin >> T; while (T--) { int n; cin >> n; vector<ll> a(n + 1, 0); for (int i = 0; i <= n; i++) cin >> a[i]; bool fl = false; for (int i = n - 1; i >= 0; i--) a[i] += a[i + 1] / 2; (a[0] > 0) ? cout << "Alice\n" : cout << "Bob\n"; } return 0; }

总结

榜单情况

官方榜

校内榜

就是那个 1012 WA 5 的

Problem A

被 sb 杭电卡递归栈空间,改手写栈过了。。。

Problem B

队友读错题,最后一小时没改回来,主要还是博弈题没过,不然一起看B还是能过的,半小时不到就补完了。

Problem C

赛时想了有半个钟头,实在没啥思路,std是bitset优化,目前还不是很理解。

Problem I

Problem K

签到,不解释。

Problem L

博弈题,比赛十分钟出了思路给翔哥讲了下,感觉很正确,一直以为是对的,最后两小时交发现WA了,然后在犹豫改A题单调栈,一直徘徊,结果两个题都没过,蚌。

ALL

个人

经此一役,以后考虑问题可以先从暴力入手,先想一下能不能奇淫技巧化暴力比如 bitset 然后考虑真正的算法问题。

团队

第一场测试赛,做的比较佛系,总体节奏还是两人一题比较靠谱,一人单开容易寄。


__EOF__

本文作者Roshin
本文链接https://www.cnblogs.com/Roshin/p/HDU_multiTranning_Game1.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Roshin  阅读(472)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
-->
点击右上角即可分享
微信分享提示