2022杭电多校第一场补题

显然还没补完

题目

A. String

KMP、Border树

题意

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

数据范围

\(|S|\leq 10^6\)

解法一, 建 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\times m\) 的方格图上,有 \(k\) 个横或竖的墙,起始坐标 \((x_s+0.5,y_s+0.5)\),结尾坐标 \((x_t+0.5,y_t+0.5\) 。问至少破坏多少面墙才能从起点到终点。

数据范围
\(1\leq n,m,k\leq 15\)
\(0\leq x_s,x_t\leq n, 0\leq y_s,y_t\leq m\)
每面墙坐标: \(0\leq x_1,x_2\leq n, 0\leq y_1,y_2\leq m, x_1=x_2\; or\; y_1=y_2\)

思路

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

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

题意

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

数据范围
\(T\leq 10\)
\(1\leq n,m<2^{10}\)
\(1\leq v_i,w_i < 2^{10}\)

思路

  • 首先要考虑暴力dp如何设计:\(f[i][j][k]\) 表示到物品 i,异或值为 j,体积为 k 的方案是否存在。
    • \(f[i][j][k] = f[i-1][j][k] + f[i-1][j\bigoplus w][k - v]\)
  • 如果正常来做,空间可以滚动,但是时间复杂度好像炸了是 \(O(2^{30})\)
  • 如果有经验的话,可以用 bitset 来优化体积的转移,因为是加减。整体复杂度除 \(64\) 。最后算下来是 \(O(2^{30}/64)\),可以通过。
  • 转移就是
    • \[f_{i,j,k} = f_{i-1,j,k}\;|\;f_{i-1,j\bigoplus w_i,k<<v_i} \]

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\) 个点的坐标和大小为 \(m*m\) 的方格上,任意三元组之间 3 个曼哈顿距离的中位数是质数的三元组个数。

数据范围
\(1\leq N\leq 2000\)
\(1\leq M\leq 10^5\)

思路

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

签到直接输入 \((n-m)/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\) 的个数

数据范围
\(\Sigma a_i \leq 1e6\)
\(n \leq 1e6\)

思路

  • 多模拟一下可以发现,对于某个值 \(x\)\(2^x\) 个这个数的话,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] \geq 2^i\) 大于就赢。否则 sum = 2 * (sum + a[i]) 。这样做一定能行,但是做不了,因为存不下。
  • 有更 smart 的方式是倒着做,这其实实际上是对每一位赋予权重 \(1/{2^x}\) ,最后权重之和大于 \(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 然后考虑真正的算法问题。

团队

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

posted @ 2022-07-19 23:29  Roshin  阅读(458)  评论(0编辑  收藏  举报
-->