[赛记] 暑假集训CSP提高模拟22 23
连通块 66pts
老套路,删边改加边;
但改完以后不知道怎么求最长路径了,当时也想到了维护直径,但不知道咋干;
具体地,用并查集维护连通性,每次合并时需要维护新的直径,不难发现,新的直径的两个端点一定在原来的两个直径的四个端点中选;
于是只有六种情况,枚举一下即可;
我们要直径有啥用呢?当我们查询一个点在其连通块内的最长路径时,那个端点一定是直径的两个端点中的一个;
于是可以快速查询;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m;
int s[200005], a[200005], ans[200005], dep[200005], x[200005], y[200005];
int fa[200005], f[200005][25];
bool vis[200005];
int find(int x) {
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
struct sss{
int t, ne;
}e[5000005];
int h[5000005], cnt;
inline void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
void dfs(int x, int fat) {
f[x][0] = fat;
dep[x] = dep[fat] + 1;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fat) continue;
dfs(u, x);
}
}
int lca(int x, int y) {
if (x == y) return x;
if (dep[x] < dep[y]) swap(x, y);
for (int i = 19; i >= 0; i--) {
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if (x == y) return x;
for (int i = 19; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int ask_dis(int x, int y) {
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
}
pair<int, int> ma[200005];
struct sas{
int x, y, dis;
bool operator <(const sas &A) const {
return dis > A.dis;
}
}c[15];
inline void unionn(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
c[1] = {ma[x].first, ma[x].second, ask_dis(ma[x].first, ma[x].second)};
c[2] = {ma[x].first, ma[y].first, ask_dis(ma[x].first, ma[y].first)};
c[3] = {ma[x].first, ma[y].second, ask_dis(ma[x].first, ma[y].second)};
c[4] = {ma[x].second, ma[y].first, ask_dis(ma[x].second, ma[y].first)};
c[5] = {ma[x].second, ma[y].second, ask_dis(ma[x].second, ma[y].second)};
c[6] = {ma[y].first, ma[y].second, ask_dis(ma[y].first, ma[y].second)};
sort(c + 1, c + 1 + 7);
ma[x] = {c[1].x, c[1].y};
fa[y] = x;
}
int main() {
freopen("block.in", "r", stdin);
freopen("block.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n - 1; i++) {
cin >> x[i] >> y[i];
add(x[i], y[i]);
add(y[i], x[i]);
}
dfs(1, 0);
for (int j = 1; j <= 19; j++) {
for (int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
for (int i = 1; i <= n; i++) {
fa[i] = i;
ma[i] = {i, i};
}
for (int i = 1; i <= m; i++) {
cin >> s[i] >> a[i];
if (s[i] == 1) vis[a[i]] = true;
}
for (int i = 1; i <= n - 1; i++) {
if (!vis[i]) unionn(x[i], y[i]);
}
for (int i = m; i >= 1; i--) {
if (s[i] == 1) {
unionn(x[a[i]], y[a[i]]);
} else if (s[i] == 2) {
int x = find(a[i]);
ans[i] = max(ask_dis(ma[x].first, a[i]), ask_dis(ma[x].second, a[i]));
}
}
for (int i = 1; i <= m; i++) {
if (s[i] == 2) cout << ans[i] << '\n';
}
return 0;
}
军队 0pts
赛时几乎没看;
也是复习了一下扫描线;
看到矩形覆盖,很容易想到扫描线(但我忘了咋打了);
用扫描线处理出满足要求的数的个数,发现我们要求的乘积,两项和为定值,那么用一下基本不等式即可知道当这两项相等时(即都为
那么我们用数组存一下满足条件的数,最后判断一下即可;
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
long long n, m, c, k, q;
struct sss{
long long l, r, val;
};
vector<sss> line[500005];
long long b[500005];
long long sum[500005], sum1[500005];
namespace seg{
inline long long ls(long long x) {
return x << 1;
}
inline long long rs(long long x) {
return x << 1 | 1;
}
struct sas{
long long l, r, sum, ma;
}tr[5000005];
inline void push_up(long long id) {
long long t = min(tr[ls(id)].sum, tr[rs(id)].sum);
tr[ls(id)].sum -= t;
tr[rs(id)].sum -= t;
tr[ls(id)].ma -= t;
tr[rs(id)].ma -= t;
tr[id].sum += t;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma) + tr[id].sum;
}
void bt(long long id, long long l, long long r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
return;
}
long long mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(long long id, long long l, long long r, long long d) {
if (tr[id].l >= l && tr[id].r <= r) {
tr[id].sum += d;
tr[id].ma += d;
return;
}
long long mid = (tr[id].l + tr[id].r) >> 1;
if (l <= mid) add(ls(id), l, r, d);
if (r > mid) add(rs(id), l, r, d);
push_up(id);
}
long long ask(long long id, long long now) {
now += tr[id].sum;
if (now >= k) return (tr[id].r - tr[id].l + 1);
if (tr[id].l == tr[id].r) return 0;
long long ans = 0;
if (now + tr[ls(id)].ma >= k) ans += ask(ls(id), now);
if (now + tr[rs(id)].ma >= k) ans += ask(rs(id), now);
return ans;
}
}
using namespace seg;
int main() {
freopen("army.in", "r", stdin);
freopen("army.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> c >> k >> q;
long long x1, y1, x2, y2;
for (long long i = 1; i <= c; i++) {
cin >> x1 >> y1 >> x2 >> y2;
line[x1].push_back({y1, y2, 1});
line[x2 + 1].push_back({y1, y2, -1});
}
bt(1, 1, m);
for (long long i = 1; i <= n; i++) {
for (long long j = 0; j < line[i].size(); j++) {
sss x = line[i][j];
add(1, x.l, x.r, x.val);
}
long long t = ask(1, 0);
b[i] = min(t, m - t);
}
sort(b + 1, b + 1 + n);
for (long long i = 1; i <= n; i++) {
sum1[i] = sum1[i - 1] + b[i] * b[i];
sum[i] = sum[i - 1] + b[i];
}
long long x, y;
for (long long i = 1; i <= q; i++) {
cin >> x >> y;
long long yy = y / 2;
long long pos = upper_bound(b + 1, b + 1 + n, yy) - b;
if (pos == n + 1) {
cout << (long long)(sum[n] - sum[n - x]) * y - (sum1[n] - sum1[n - x]) << '\n';
} else if (pos <= n - x + 1) {
cout << (long long)x * (yy * y - yy * yy) << '\n';
} else {
cout << (long long)(sum[pos - 1] - sum[n - x]) * y - (sum1[pos - 1] - sum1[n - x]) + (yy * y - yy * yy) * (n - pos + 1) << '\n';
}
}
return 0;
}
进击的巨人 100pts
这题赛时10min打的 而且还是首A;
正解当然不是暴力,而是要推式子;
不难发现,每个
对于一个不包含
对其使用二项式定理(
可以把
注意到后面括号那一堆是可以使用前缀和求的,所以枚举
当然,也可以将
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const long long mod = 998244353;
long long n, k;
char c[200005];
long long cnt[200005];
long long sum[200005];
long long ans;
long long ksm(long long a, long long b) {
long long ans = 1;
while(b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
long long inv[200005];
long long p[200005];
long long fac[200005], fav[200005];
long long C(long long n, long long m) {
if (m == 0) return 1;
if (m == n) return 1;
if (n == 0) return 0;
if (m > n) return 0;
return fac[n] * fav[m] % mod * fav[n - m] % mod;
}
long long Lucas(long long n, long long m) {
if (m == 0) return 1;
return Lucas(n / mod, m / mod) * C(n % mod, m % mod) % mod;
}
int main() {
freopen("attack.in", "r", stdin);
freopen("attack.out", "w", stdout);
scanf("%lld %lld", &n, &k);
scanf("%s", c + 1);
n++;
c[n] = '0';
p[0] = 1;
fac[0] = 1;
inv[0] = 1;
fav[0] = 1;
for (long long i = 1; i <= n; i++) {
p[i] = p[i - 1] * 2 % mod;
inv[i] = ksm(p[i], mod - 2);
fac[i] = fac[i - 1] * i % mod;
fav[i] = ksm(fac[i], mod - 2);
}
long long ls = 1;
for (long long i = 1; i <= n; i++) {
cnt[i] = cnt[i - 1];
if (c[i] == '?') cnt[i]++;
if (c[i] == '0') {
cnt[i] = 0;
for (long long j = 0; j <= k; j++) {
if (ls > 1) sum[ls - 2] = 0;
for (long long l = ls - 1; l <= i - 1; l++) {
sum[l] = (sum[l - 1] + ksm(-l, j) * p[cnt[l]] % mod) % mod;
}
long long su = 0;
for (long long r = ls - 1; r <= i - 1; r++) {
su = (su + ksm(r, k - j) * inv[cnt[r]] % mod * sum[r - 1] % mod) % mod;
}
ans = (ans + su * Lucas(k, j) % mod + mod) % mod;
}
ls = i + 1;
}
}
printf("%lld", ans);
return 0;
}
Wallpaper Collection -pts
赛时根本没看;
像这种一眼望去没啥思路的题,一般就是DP;
首先转化题意:在每行都找一个不空的矩形,使它们互有交集;
考虑最暴力的DP;
设
很显然,这是过不了的;
其实我们会发现,找出满足题意的最大值,就相当于从上往下走一条路径(当然也可以向左或右走),经过的点的和的最大值;
那么我们考虑变一个状态设计;
设
我们用
- 当
时;
- 当
时;
这个是
考虑优化一下式子中的
另一种情况同理;
这个是
考虑省去
我们发现,式子中
将原式中包含
发现我们只需维护后面括号内的最大值即可,所以可以在枚举
时间复杂度:
细节看代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, m;
int a[2005][2005];
long long f[2005][2005];
long long sum[2005];
long long s[2005][2005], t[2005][2005];
long long mins[2005][2005], mint[2005][2005];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
sum[i] += a[i][j];
}
}
memset(mins, 0x3f, sizeof(mins));
memset(mint, 0x3f, sizeof(mint));
for (int i = 1; i <= n; i++) {
mins[i][0] = mins[i][m + 1] = mint[i][0] = mint[i][m + 1] = 0; //注意这里,最小值可以为0(不选);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i][j - 1] + a[i][j];
mins[i][j] = min(mins[i][j - 1], s[i][j]);
}
for (int j = m; j >= 1; j--) {
t[i][j] = t[i][j + 1] + a[i][j];
mint[i][j] = min(mint[i][j + 1], t[i][j]);
}
}
memset(f, 0xcf, sizeof(f));
for (int i = 1; i <= m; i++) f[0][i] = 0;
for (int i = 1; i <= n; i++) {
long long ma = -0x3f3f3f3f3f3f3f3f;
long long maa = -0x3f3f3f3f3f3f3f3f;
for (int j = 1; j <= m; j++) {
ma = max(ma, f[i - 1][j] - mins[i][j - 1]);
f[i][j] = max(f[i][j], sum[i] - mint[i][j + 1] + ma);
}
for (int j = m; j >= 1; j--) {
if (maa == -0x3f3f3f3f3f3f3f3f) {
maa = f[i - 1][j] - t[i][j + 1];
continue;
}
f[i][j] = max(f[i][j], sum[i] - mins[i][j - 1] + maa);
maa = max(maa, f[i - 1][j] - mint[i][j + 1]);
}
}
long long ans = -0x3f3f3f3f3f3f3f3f;
for (int j = 1; j <= m; j++) {
ans = max(ans, f[n][j]);
}
cout << ans;
return 0;
}
樱花庄的宠物女孩 30pts
树的特殊性质很好打,在此不再赘述;
考虑正解;
我们发现,如果人到了箱子旁边,那么他会一直拉着箱子走;
那么我们可以先用BFS(其实就是最短路)处理出人从起点到与1相邻的每个点的最小步数,并将这些值存储在一个数组中;
下一步,我们要让人带着箱子从这些点走;
我们发现,其实我们关心的是一个三元组
发现这其实是一条有向边在某一时刻的状态;
所以我们要将处理对象从点转换成边;
具体实现上,我们从1开始再进行一次BFS,每次去更新与1相邻的边的相邻的边的状态,最后从n处统计一下答案即可;
这里可以将边看成点,会方便一些;
注意更新时,我们要每次取出队列中
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
int n, m, x;
struct sss{
int f, t, ne;
}e[2000005];
int h[2000005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].f = u;
e[cnt].ne = h[u];
h[u] = cnt;
}
int dis[2000005], vis[2000005];
int rem[2000005], val[2000005];
void dij(int x) {
memset(dis, 0x3f, sizeof(dis));
dis[x] = 0;
queue<int> q;
q.push(x);
while(!q.empty()) {
int t = q.front();
q.pop();
for (int i = h[t]; i; i = e[i].ne) {
int u = e[i].t;
if (u != 1 && dis[u] > dis[t] + 1) {
dis[u] = dis[t] + 1;
q.push(u);
}
if (u == 1) {
rem[++rem[0]] = ((i & 1) ? (i + 1) : (i - 1)); //存反向边,因为下一步要从1开始BFS;
val[rem[0]] = dis[t];
}
}
}
}
void bfs() {
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
queue<int> q; //队列中存的是边的编号;
int now = 1;
for (int i = 1; i <= rem[0]; i++) {
dis[rem[i]] = val[i]; //先将已知的初始化;
}
while(!q.empty() || now <= rem[0]) {
if (q.empty()) {
if (now <= rem[0]) {
q.push(rem[now++]);
}
} else {
int t = q.front();
q.pop();
if (vis[e[t].t] == 2) continue;
vis[e[t].t]++;
while(now <= rem[0] && dis[rem[now]] == dis[t]) {
q.push(rem[now++]);
}
for (int i = h[e[t].t]; i; i = e[i].ne) {
int u = e[i].t;
if (u == e[t].f) continue;
if (dis[i] > dis[t] + 1) {
dis[i] = dis[t] + 1;
q.push(i);
}
}
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> x;
int xx, yy;
for (int i = 1; i <= m; i++) {
cin >> xx >> yy;
add(xx, yy);
add(yy, xx);
}
dij(x);
bfs();
int ans = 0x3f3f3f3f;
for (int i = h[n]; i; i = e[i].ne) { //注意这里枚举的是有向边;
ans = min(ans, dis[i]);
}
if (ans == 0x3f3f3f3f) {
cout << "No";
return 0;
}
cout << "Yes" << endl << ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具