2022“杭电杯”中国大学生算法设计超级联赛(1) 题解
A. String
首先通过KMP建立失配树,节点u的所有祖先都为其border,满足第二个条件。
对于条件三,我们需要满足 ① 必须有相交部分,② 相交部分长度被 \(k\) 整除。
对于①,我们在失配树上DFS,并利用双指针维护当前链,保证当前链的长度\(len > \lfloor \frac{u}{2} \rfloor\),其中 \(u\) 是当前DFS到的节点。
对于②,满足条件的长度 \(v\) 有 \((2v - u) \% k == 0\) ,即 \(2v \% k == u \% k\)。我们在双指针的过程中维护一个桶记录查询即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5;
const int MOD = 998244353;
vector<int> G[MAXN], dfs;
char S[MAXN];
int n, k, pmt[MAXN], ans, cnt[MAXN], s[MAXN], L, R, dfssize, st[MAXN], cur[MAXN];
void Solve() {
cin >> S + 1 >> k;
n = strlen(S + 1); L = 1; R = 0;
int p = 0; ans = 1;
for(int i = 0; i <= n; i++) G[i].clear();
for(int i = 0; i <= k; i++) cnt[i] = 0;
G[0].push_back(1);
for(int i = 2; i <= n; i++) {
while(p && S[p + 1] != S[i]) p = pmt[p];
if(S[p + 1] == S[i]) p++;
pmt[i] = p;
G[ pmt[i] ].push_back(i);
}
//for(int i = 1; i <= n; i++) cout << pmt[i] << " "; cout << "pmt\n";
dfs.clear(); dfs.push_back(0); dfssize = 1;
while(dfssize) {
int u = dfs.back();
if(!st[u]) {
cur[u] = L;
s[++R] = u, cnt[2 * u % k]++;
while(L <= R && 2 * s[L] <= u) cnt[2 * s[L] % k]--, L++;
ans = ans * (cnt[u % k] + 1) % MOD;
for(auto v : G[u]) dfs.push_back(v), dfssize++;
st[u] ^= 1;
} else {
while(L > cur[u]) L--, cnt[2 * s[L] % k]++;
R--, cnt[2 * u % k]--;
dfs.pop_back(); dfssize--;
st[u] ^= 1;
}
}
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
B. Dragon slayer
发现墙最多只有15个,状压每一个墙的状态(是否被消除),直接爆搜即可。
时间复杂度\(O(n m 2^k)\),略微卡常,需要注意写法。
#pragma GCC optimize(2)
//#define int long long
#include<bits/stdc++.h>
#define x1 x123456789
#define y1 y123456789
#define x2 x12345678
#define y2 y12345678
#define fi first
#define se second
#define pii pair<int, int>
#define pir make_pair
using namespace std;
const int MAXN = 30 + 5;
int dx[5] = {0, 1, 0, -1}, dy[5] = {1, 0, -1, 0};
int X1[MAXN], X2[MAXN], Y1[MAXN], Y2[MAXN];
int n, m, K, xs, ys, xt, yt, vis[MAXN][MAXN], dist[MAXN][MAXN], wall[MAXN][MAXN];
queue<pii> Q;
int Hash(int x) { return x << 1; }
int Solve(int st) {
for(int i = 0; i <= 2 * n; i++) for(int j = 0; j <= 2 * m; j++) vis[i][j] = wall[i][j] = 0;
for(int i = 1; i <= K; i++) {
if((st >> i - 1) & 1) continue;
int x1 = X1[i], x2 = X2[i], y1 = Y1[i], y2 = Y2[i];
if(x1 == x2) {
if(y1 > y2) swap(y1, y2);
for(int j = y1; j <= y2; j++) wall[x1][j] = 1;
} else {
if(x1 > x2) swap(x1, x2);
for(int j = x1; j <= x2; j++) wall[j][y1] = 1;
}
}
while(!Q.empty()) Q.pop();
Q.push( pir(xs, ys) );
//cout << st << " st:\n";
while(!Q.empty()) {
pii cur = Q.front(); Q.pop();
int x = cur.fi, y = cur.se;
if(vis[x][y]) continue;
//cout << x << " " << y << " " << w << " Q\n";
vis[x][y] = 1;
if(x == xt && y == yt) {
int cnt = 0;
for(int i = 0; i < K; i++) cnt += ((st >> i) & 1);
//cout << st << " " << cnt << " res\n";
return cnt;
}
for(int k = 0; k < 4; k++) {
int cx = x + dx[k], cy = y + dy[k];
if(cx < 0 || cy < 0 || cx > 2 * n || cy > 2 * m || vis[cx][cy] || wall[cx][cy]) continue;
if(cx % 2 == 0 && cy % 2 == 0) continue;
cx += dx[k], cy += dy[k];
if(cx < 0 || cy < 0 || cx > 2 * n || cy > 2 * m || vis[cx][cy] || wall[cx][cy]) continue;
Q.push( pir(cx, cy) );
}
}
return 0x3f3f3f3f;
}
void Solve() {
cin >> n >> m >> K >> xs >> ys >> xt >> yt;
xs = Hash(xs) + 1, ys = Hash(ys) + 1, xt = Hash(xt) + 1, yt = Hash(yt) + 1;
for(int i = 1; i <= K; i++) {
int x1, x2, y1, y2; cin >> x1 >> y1 >> x2 >> y2;
x1 = Hash(x1), y1 = Hash(y1), x2 = Hash(x2), y2 = Hash(y2);
X1[i] = x1, X2[i] = x2, Y1[i] = y1, Y2[i] = y2;
}
int ans = 0x3f3f3f3f;
for(int st = 0; st < (1 << K); st++) ans = min(ans, Solve(st) );
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
C. Backpack
设\(f[i][j][k]\)为是否有合法的方案满足取前 \(i\) 个物品,异或和为 \(j\),当前容量为 \(k\) 。
有\(f[i][j][k] = f[i - 1][j \oplus w][k - v] \ | \ f[i - 1][j][k]\),其中\(\oplus\)代表异或符号。
用滚动数组简化第一维,bitset维护第三维,有\(f[1][j]=(f[0][j \oplus w] << v) \ | \ f[0][j]\)。
时间复杂度\(O(\frac{n^3}{32})。\)
#pragma GCC optimize(2)
//#define int long long
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1024 + 5;
int n, m;
bitset<1024> f[2][1024];
void Solve() {
cin >> n >> m;
memset(f, 0, sizeof f); f[0][0][0] = 1;
for(int i = 1; i <= n; i++) {
int v, w; cin >> v >> w;
for(int k = 0; k < 1024; k++) f[1][k] = f[1 - 1][k] | (f[1 - 1][k ^ w] << v);
for(int k = 0; k < 1024; k++) f[0][k] = f[1][k], f[1][k] = 0;
}
int ans = -1;
for(int k = 0; k < 1024; k++) if(f[0][k][m]) ans = max(ans, k);
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
D. Ball
先筛出质数,由于数据上界为2e5,复杂度上界为\(\Theta(nlogn)\)的筛法都能过。
直接枚举三个点会超时,考虑只枚举两个点,其之间的距离作为median distance,设其为 \(mid\),
我们需要找出第三个点满足与之相连的两条边,其中一条边小于等于 \(mid\),另一条边大于等于 \(mid\)。
我们把点对按长度从小到大排序,并且在遍历的过程中用 bitset 维护之前被遍历过的边。这样即可求出枚举两个点时,有多少个点符合要求
时间复杂度\(O(\frac{n^3}{32})\)。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2000 + 5;
struct Node {
int u, v, w;
bool operator < (const Node &a) const { return w < a.w; }
};
int n, m, X[MAXN], Y[MAXN], cnt, prime[200005], isprime[200005];
vector<Node> vec;
bitset<MAXN> Set[MAXN];
int Dist(int i, int j) {
return abs(X[i] - X[j]) + abs(Y[i] - Y[j]);
}
void Solve() {
scanf("%d%d", &n, &m); vec.clear();
for(int i = 0; i < n; i++) {
scanf("%d%d", &X[i], &Y[i]);
for(int j = 0; j < i; j++) vec.push_back( (Node){j, i, Dist(i, j)} );
Set[i].reset();
}
sort(vec.begin(), vec.end());
long long ans = 0;
for(auto i : vec) {
int u = i.u, v = i.v, w = i.w;
if(!isprime[w]) ans += (Set[u] ^ Set[v]).count();
Set[u][v] = Set[v][u] = 1;
}
printf("%lld\n", ans);
}
void Init() {
n = 2e5;
isprime[0] = isprime[1] = 1;
for(int i = 2; i <= n; i++) {
if(!isprime[i]) prime[++cnt] = i;
for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
isprime[ i * prime[j] ] = 1;
if(i % prime[j] == 0) break;
}
}
}
signed main()
{
Init();
int T; scanf("%d", &T);
while(T--) Solve();
return 0;
}
I. Laser
考虑其中一个点被4条直线覆盖的情况,此时有两种情况:
①所有点都被这条直线覆盖,直接返回YES。
②有一个点没被这条直线覆盖,再枚举这个点被另外3条直线覆盖的情况。此时我们可以通过这两条线求出交点,得到交点之后直接判断即可。共需要判断12种情况。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define pii pair<int, int>
#define x first
#define y second
#define pir make_pair
using namespace std;
const int MAXN = 5e5 + 5;
unordered_map<int, vector<int> > L[5];
int Map[5][MAXN], n, x[MAXN], y[MAXN];
int dx[5] = {0, 1, 1, 0, 1}, dy[5] = {0, -1, 1, 1, 0};
pii Find(int px, int py, int i, int j) {
pii res; int k1, Dx, Dy;
if(dy[j] == 0) {
k1 = (y[py] - y[px]) / dy[i];
} else {
Dx = x[px] - x[py], Dy = y[px] - y[py];
k1 = (Dx * dy[j] - Dy * dx[j]) / (dy[i] * dx[j] - dx[i] * dy[j]);
}
res = pir(x[px] + dx[i] * k1, y[px] + dy[i] * k1);
return res;
}
bool Check(pii p) {
for(int i = 1; i <= n; i++) {
bool flag = 0;
for(int k = 1; k <= 4; k++) {
int cur;
if(k == 1) cur = p.x + p.y;
if(k == 2) cur = p.x - p.y;
if(k == 3) cur = p.x;
if(k == 4) cur = p.y;
if(Map[k][i] == cur) flag = 1;
}
if(!flag) return 0;
}
return 1;
}
void Solve() {
cin >> n;
for(int i = 1; i <= 4; i++) L[i].clear();
for(int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
L[1][ x[i] + y[i] ].push_back(i); // (x + k, y - k)
L[2][ x[i] - y[i] ].push_back(i); // (x + k, y + k)
L[3][ x[i] ].push_back(i); // (x, y + k)
L[4][ y[i] ].push_back(i); // (x + k, y)
Map[1][i] = x[i] + y[i];
Map[2][i] = x[i] - y[i];
Map[3][i] = x[i];
Map[4][i] = y[i];
}
for(int i = 1; i <= 4; i++) {
for(int j = 1; j <= 4; j++) {
if(i == j) continue;
//for(int k = 1; k <= n; k++) vis[k] = 0;
int px = 1, py = 2;
for(; py <= n; py++) if(Map[i][px] != Map[i][py]) break;
if(py > n) return void(cout << "YES\n");
pii p = Find(px, py, i, j);
if(Check(p)) return void(cout << "YES\n");
}
}
return void(cout << "NO\n");
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}