CSP-S模拟15
T1.网格图
3秒过3e9,我赛时只打了的暴力,极限时间复杂度应该是,但不知道为什么只跑了。
考虑枚举左上角时正方形的变化,只有左右两列发生了变化,那么实际上只需要扫这两列即可。注意到外部连通块的点可能会在内部存在,所以不能直接合并。分三步:与连通块相连的,正方形中1的个数,被正方形完全包含的连通块。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std;
const int Z = 520; typedef pair<int, int> paint;
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, m, k, ans;
char CH[Z];
int sqr[Z][Z], bel[Z][Z], block[Z * Z], num;
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
queue <paint> q;
void bfs(int x, int y)//扩展连通块
{
q.push(make_pair(x, y)); ++num;
while (!q.empty())
{
int x = q.front().first, y = q.front().second; q.pop();
if (bel[x][y]) continue;
bel[x][y] = num; block[num]++;
for (re i = 0; i < 4; i++)
{
int xx = x + dx[i], yy = y + dy[i];
if (sqr[xx][yy] && !bel[xx][yy]) q.push(make_pair(xx, yy));
}
}
}
int sum[Z][Z];
inline void init()//障碍物的前缀和
{
for (re i = 1; i <= n; i++)
for (re j = 1; j <= n; j++)
sum[i][j] = (!sqr[i][j]) + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
}
bool vs[Z * Z], in[Z * Z];
int clean[Z * Z], cs, res, tot;
inline void link(int x, int y)//新联通上的
{
if (sqr[x][y] && !vs[bel[x][y]])
{
res += block[bel[x][y]];
vs[bel[x][y]] = 1;
clean[++cs] = bel[x][y];
}
}
inline void cut(int x, int y)//左边需要出去的点
{
if (sqr[x][y] && !vs[bel[x][y]])
{
tot -= block[bel[x][y]];
vs[bel[x][y]] = 1;
clean[++cs] = bel[x][y];
}
}
inline void add(int x, int y)//右边新加入的点
{
if (sqr[x][y] && !vs[bel[x][y]] && !in[bel[x][y]])
{
tot += block[bel[x][y]];
in[bel[x][y]] = 1;
clean[++cs] = bel[x][y];
}
}
sandom main()
{
cin >> n >> k;
for (re i = 1; i <= n; i++)
{
scanf("%s", CH + 1);
for (re j = 1; j <= n; j++) sqr[i][j] = (CH[j] == '.');
}
for (re i = 1; i <= n; i++)//先找到所有的连通块
for (re j = 1; j <= n; j++)
if (sqr[i][j] && !bel[i][j]) bfs(i, j);
init();
for (re i = 1; i <= n; i++)//枚举子正方形的左上角
for (re j = 1; j <= n; j++)
{
int x = i + k - 1, y = j + k - 1;
if (x > n || y > n) continue;
res = sum[x][y] + sum[i - 1][j - 1] - sum[i - 1][y] - sum[x][j - 1];
for (re t = j; t <= y; t++) link(i - 1, t), link(x + 1, t);
for (re t = i; t <= x; t++) link(t, j - 1), link(t, y + 1);
if (j == 1)
{
tot = 0;//清空内部点
for (re t = i; t <= x; t++) for (re s = j; s <= y; s++) add(t, s);//第一块暴力扫
}
else for (re t = i; t <= x; t++) add(t, y);
ans = max(ans, res + tot);
for (re t = i; t <= x; t++) cut(t, j);
while (cs) vs[clean[cs]] = in[clean[cs]] = 0, cs--;
}
cout << ans;
return 0;
}
T2.保险箱
什么伞兵数论题,不知道群是什么,虽然没有影响罢了。代码前摇:
设为最终的群,即所有可能的正确密码的集合。
1.若,则。根据裴蜀定理,有解,当且仅当,并且因为取模的存在,一定可以取到。
2.若,则$gcd(a, n) \in G n \in G$,首先一定,那么把看作;也可以设,则,则,, ,(裴蜀定理),
3.设群的最小表示元为,则为群中所有数的,即能整除中的所有数。那么一个数如果,那么它的所有因数也。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long
using namespace std;
const int Z = 3e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
int n, k, d, m[Z], p[Z];
set <int> fac;
inline void divide(int x)//分解质因数(凑出x的所有因数)
{
int t = sqrt(x);
for (re i = 2; i <= t; i++)
if (x % i == 0)
{
p[++p[0]] = i;
while (x % i == 0) x /= i;
}
if (x != 1) p[++p[0]] = x;
}
inline void factor(int x)//x的所有因数(只有这些解有可能)
{
int t = sqrt(x);
for (re i = 1; i <= t; i++)
if (x % i == 0)
{
fac.insert(i);
if (i * i != x) fac.insert(x / i);
}
}
void del(int x)//递归删除
{
if (fac.find(x) == fac.end()) return;
fac.erase(x);
for (re i = 1; i <= p[0]; i++)//凡是x的因数的都删掉
if (x % p[i] == 0) del(x / p[i]);
}
sandom main()
{
n = read(), k = read();
for (re i = 1; i <= k; i++) m[i] = read();
d = gcd(m[k], n);//一定在这个范围里
divide(d), factor(d);
for (re i = 1; i < k; i++) del(gcd(m[i], d));
cout << n / (*fac.begin());
return 0;
}
T3.追逐
其实是一道比较板的换根,暴力的树形应该很简单,枚举每一个点作为起点,定义,表示以为出发点,使用个磁铁的最大贡献。转移也很基础,就是分选与不选。。之前在Accoders上做过一道换根,类似的,只需要记录每一个点的子树中的最大值和次大值。并维护一个子树外的数组,转移方式基本一致,不再赘述。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long
using namespace std; typedef long long ll;
const int Z = 1e5 + 2;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, m; ll ans;
struct edge { int v, ne; } e[Z << 1];
int head[Z], cnt;
inline void add(int x, int y) { e[++cnt] = edge{y, head[x]}; head[x] = cnt; }
int a[Z]; ll w[Z];
ll f[Z][102][2], g[Z][102][2];
int mx[Z][102][2], mn[Z][102][2];//最大值与次大值
inline void update(int rt, int son, int j, int op)
{
if (f[mx[rt][j][op]][j][op] <= f[son][j][op]) mn[rt][j][op] = mx[rt][j][op], mx[rt][j][op] = son;
else if (f[mn[rt][j][op]][j][op] < f[son][j][op]) mn[rt][j][op] = son;
}
inline int query(int rt, int son, int j, int op)
{
if (mx[rt][j][op] == son) return mn[rt][j][op];
else return mx[rt][j][op];
}
void dfs1(int rt, int fa)
{
w[rt] = 0;
for (re i = head[rt]; i; i = e[i].ne)
{
int son = e[i].v;
if (son == fa) continue;
dfs1(son, rt);
w[rt] += a[son];
}
for (re j = 0; j <= m; j++)
{
for (re i = head[rt]; i; i = e[i].ne)
{
int son = e[i].v;
if (son == fa) continue;
update(rt, son, j, 0), update(rt, son, j, 1);
f[rt][j][0] = max(f[rt][j][0], max(f[son][j][0], f[son][j][1]));
if(j) f[rt][j][1] = max(f[rt][j][1], max(f[son][j - 1][0], f[son][j - 1][1]) + w[rt]);
}
}
}
void dfs2(int rt, int fa)
{
ll mx0 = 0, mx1 = 0;
for (re j = 0; j <= m; j++)
{
mx0 = max(max(g[fa][j][0], g[fa][j][1]), max(f[mx[rt][j][0]][j][0], f[mx[rt][j][1]][j][1]));
if(j) mx1 = max(max(g[fa][j - 1][0], g[fa][j - 1][1]), max(f[mx[rt][j - 1][0]][j - 1][0], f[mx[rt][j - 1][1]][j - 1][1])) + w[rt] + a[fa];
ans = max(ans, max(mx0, mx1));
}
for (re i = head[rt]; i; i = e[i].ne)
{
int son = e[i].v;
if (son == fa) continue;
for (re j = 0; j <= m; j++)
{
mx0 = query(rt, son, j, 0), mx1 = query(rt, son, j, 1);
g[rt][j][0] = max(max(g[fa][j][0], g[fa][j][1]), max(f[mx0][j][0], f[mx1][j][1]));
if (j) mx0 = query(rt, son, j - 1, 0), mx1 = query(rt, son, j - 1, 1);
if (j) g[rt][j][1] = max(max(g[fa][j - 1][0], g[fa][j - 1][1]), max(f[mx0][j - 1][0], f[mx1][j - 1][1])) + w[rt] + a[fa] - a[son];
}
dfs2(son, rt);
}
}
sandom main()
{
n = read(), m = read();
for (re i = 1; i <= n; i++) a[i] = read();
for (re i = 1; i < n; i++)
{
int x = read(), y = read();
add(x, y), add(y, x);
}
dfs1(1, 0); dfs2(1, 0);
cout << ans;
return 0;
}
T4.字符串
把题意转换一下就变成了山海经??把分别看做,那么实际上最后的序列应该是所有前缀()和后缀()都是的。首先考虑前缀,发现删去的位置分别是第一次等于,这个结论很显然。那么对于前缀需要操作的次数显然就是,这时后缀发生了变化,记为,单独考虑这个位置的后缀什么时候发生变化:它前面的修改对它没有影响,而后面的每一次修改都会,所以我们得到,最后的答案就是,说白了就是求一个前缀与后缀加和的最小值,且不能覆盖,可以转化成求一个序列的最大子段和,总和减去最大子段和就是答案。因为有负号,取个相反数就行了。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 5e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, m, q;
char s[Z];
struct tree
{
int pre, suf, max, sum;
#define lk (rt << 1)
#define rk (rt << 1 | 1)
#define mid (L + R >> 1)
}; tree tr[Z << 2], ans;
inline tree pushup(tree lc, tree rc)
{
tree rt;
rt.sum = lc.sum + rc.sum;
rt.pre = lc.pre, rt.suf = rc.suf;
rt.pre = max(lc.pre, lc.sum + rc.pre);
rt.suf = max(rc.suf, rc.sum + lc.suf);
rt.max = max(max(lc.max, rc.max), max(rt.pre, rt.suf));
rt.max = max(rt.max, lc.suf + rc.pre);
return rt;
}
void build(int rt, int L, int R)
{
if (L == R)
{
tr[rt].pre = tr[rt].suf = tr[rt].max = tr[rt].sum = (s[L] == 'C') ? 1 : -1;
tr[rt].max = max(tr[rt].max, 0);
return;
}
build(lk, L, mid), build(rk, mid + 1, R);
tr[rt] = pushup(tr[lk], tr[rk]);
}
tree query(int rt, int L, int R, int l, int r)
{
if (l <= L && r >= R) return tr[rt];
if (r <= mid) return query(lk, L, mid, l, r);
else if (l > mid) return query(rk, mid + 1, R, l, r);
else return pushup(query(lk, L, mid, l, r), query(rk, mid + 1, R, l, r));
}
sandom main()
{
n = read(); scanf("%s", s + 1); q = read();
build(1, 1, n);
while (q--)
{
int l = read(), r = read();
ans = query(1, 1, n, l, r);
write(ans.max - ans.sum);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现