CSP-S 游寄
初赛。
本来以为上午要愉快地周测,但是伟大的虎哥让我们在四楼接着练习
然后就目睹了一个万能头+return 0编译 1min30sec 的奇迹 Win7
打了第二类斯特林数 列
未果,寻病终。
下午开始考试
然后 锅锅f 又锅了
很奇妙地等了 20min 才能登陆
然后一个半小时写完了
就在那摆
改错了好几道题
然后考完了
想看一眼沈队 也没看到
但是后来就经常看到了
82.5pts.
复赛。
考前七天在宾馆隔离。
模拟赛成绩一次比一次差,到最后似乎是放弃了场上打正解的尝试
于是CSP时盯着傻瓜题的部分分就是拿
出场后发现除了我都切了
考试策略大概是每道题给一个小时,如果没有思路就赶紧打暴力滚蛋
然后四道题四个暴力
不知道能不能上 200pts
T1 感觉可以直接 sort 后取后 20 个 但是连大样例都过不去 40pts
T2 冲了 n + 4 个 st表后拿了 85pts,以为自己肯定想不出来这种nb题了就跳了
出场发现 T2 签到 wssb
T3 想到内向基环树森林后打了单次 的sb拓扑排序 40pts
明明拿个set乱搞就能 60pts
正解思路不会 sum hash 从没见过
T4 打了暴力和 k = 1 的部分分 以为能有四十多分
但是出场一测代码发现nmd前向星没开双倍边
然后2e5的k=1都寄了 以后用vector(
最后看infoj的自测挂成了205pts
这次总体来说很寄,我是寄寄大王
希望 noip rp++
对了ioj自测里rk1是apj
赛后当天晚上在洗澡
10min 拿到了 350pts
行吧
update : 40+85+65+36=226
不愧是 ccf
白鸟过河滩好听的
link
好像这题解有点意识流?
T1 假期计划
小熊的地图上有 个点,其中编号为 的是它的家、编号为 的都是景点。部分点对之间有双向直达的公交线路。如果点 与 、 与 、……、 与 、 与 之间均有直达的线路,那么我们称 与 之间的行程可转车 次通达;特别地,如果点 与 之间有直达的线路,则称可转车 次通达。
很快就要放假了,小熊计划从家出发去 个不同的景点游玩,完成 段行程后回家:家 景点 A 景点 B 景点 C 景点 D 家且每段行程最多转车 次。转车时经过的点没有任何限制,既可以是家、也可以是景点,还可以重复经过相同的点。例如,在景点 A 景点 B 的这段行程中,转车时经过的点可以是家、也可以是景点 C,还可以是景点 D 家这段行程转车时经过的点。
假设每个景点都有一个分数,请帮小熊规划一个行程,使得小熊访问的四个不同景点的分数之和最大。
,,。
由于是 的无向图上环,考虑拆成 的两条线路后拼合。
考虑枚举 ,选择最优的 作为答案。由于 ,需要对每个 维护前三大的答案。
首先在 的复杂度内处理任意两点间连通性,bfs 即可。
随后考虑 的路径。
枚举 ,枚举每个点 。若这两点可以拼合成路径,则将 插入 的答案集合中。
这部分的复杂度 。
然后考虑枚举 ,使用两个点的答案集合拼出答案。这时只需要保证四个点彼此不同, 可行即可。
这部分的复杂度 ,带 的常数。
于是我们在 的复杂度内得到答案。
当然你也可以记录 节点在拓展 次后前几长的路径,最后直接得到答案。时间复杂度相同。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
template<typename T> void get(T & x) {
x = 0; char ch = getchar(); bool f = false;
while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x);
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = s, i##_ = t + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = s, i##_ = t - 1; i > i##_; -- i)
const int N = 2500 + 10;
int n, m, k, a[N], t1, t2, gp[N][N], ans;
vector <int> g[N];
bool diff(const vector<int> a) {
rep(i,0,a.size()-1) rep(j,i+1,a.size()-1) if (a[i] == a[j]) return false;
return true;
}
struct info {
int self, pos[4], cnt;
void insert(int p) {
if (p == self) return;
if (cnt < 3) pos[++cnt] = p, sort(pos+1, pos+1+cnt, [&](int x, int y){ return a[x] > a[y]; });
else if (a[pos[3]] < a[p]) pos[3] = p, sort(pos+1, pos+1+cnt, [&](int x, int y){ return a[x] > a[y]; });
}
int operator + (const info & b) const {
int ret = 0;
rep(i,1,cnt) rep(j,1,b.cnt) {
if (gp[self][b.self] and diff( {self, b.self, pos[i], b.pos[j]} )) {
ret = max(ret, a[self] + a[b.self] + a[pos[i]] + a[b.pos[j]]);
}
}
return ret;
}
} f[N];
int dis[N];
queue <int> que;
void bfs(int st) {
rep(i,1,n) gp[st][i] = false, dis[i] = 1e9;
gp[st][st] = true; dis[st] = 0; que.push(st);
while (que.size()) {
int u = que.front(); que.pop();
if (dis[u] > k) continue;
for (int v : g[u]) if (dis[v] == 1e9) {
dis[v] = dis[u] + 1; gp[st][v] = 1;
que.push(v);
}
}
}
signed main() {
get(n, m, k);
rep(i,2,n) get(a[i]);
rep(i,1,m) get(t1, t2), g[t1].push_back(t2), g[t2].push_back(t1);
rep(i,1,n) bfs(i), f[i].self = i;
rep(i,2,n) rep(j,2,n) if (i != j and gp[1][i] and gp[i][j]) f[j].insert(i);
rep(i,2,n) rep(j,2,n) if (i != j) ans = max(ans, f[i] + f[j]);
cout << ans << endl;
return 0;
}
T2 策略游戏
有一个长度为 的数组 和一个长度为 的数组 ,在此基础上定义一个大小为 的矩阵 ,满足 。所有下标均从 开始。
游戏一共会进行 轮,在每一轮游戏中,会事先给出 个参数 ,满足 、。
游戏中,小 L 先选择一个 之间的下标 ,然后小 Q 选择一个 之间的下标 。定义这一轮游戏中二人的得分是 。
小 L 的目标是使得这个得分尽可能大,小 Q 的目标是使得这个得分尽可能小。同时两人都是足够聪明的玩家,每次都会采用最优的策略。
请问:按照二人的最优策略,每轮游戏的得分分别是多少?
,。
以为很nb 实际上是傻瓜题
我们开st表维护一个区间负数最值/区间正数最值
分类讨论模拟即可。
为什么 st表 比 线段树 慢一倍以上啊 明明query时少了个 log
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
template<typename T> void get(T & x) {
x = 0; char ch = getchar(); bool f = false;
while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x);
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = s, i##_ = t + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = s, i##_ = t - 1; i > i##_; -- i)
const int N = 1e5 + 10;
const int mx = 1e18;
int n, m, q, a[2][N], l1, r1, l2, r2;
int st[2][4][N][20]; // 0 : 正最大 1 : 负最大 2 : 正最小 3 : 负最小
int lgv[N];
void init(const int & id, const int & n) {
rep(i,1,n) {
st[id][0][i][0] = (a[id][i] >= 0 ? a[id][i] : mx);
st[id][1][i][0] = (a[id][i] <= 0 ? a[id][i] : mx);
st[id][2][i][0] = (a[id][i] >= 0 ? a[id][i] : mx);
st[id][3][i][0] = (a[id][i] <= 0 ? a[id][i] : mx);
}
rep(j,1,lgv[n]) for (int i = 1; i + (1 << j) - 1 <= n; ++ i) {
rep(typ,0,1) {
int x = st[id][typ][i][j-1], y = st[id][typ][i + (1 << j-1)][j - 1];
if (x != mx and y != mx) st[id][typ][i][j] = max(x, y);
else st[id][typ][i][j] = (x == mx ? y : x);
} rep(typ,2,3) {
int x = st[id][typ][i][j-1], y = st[id][typ][i + (1 << j-1)][j - 1];
if (x != mx and y != mx) st[id][typ][i][j] = min(x, y);
else st[id][typ][i][j] = (x == mx ? y : x);
}
}
}
int qry(int id, int typ, int l, int r) {
int d = lgv[r - l + 1];
if (typ < 2) {
int x = st[id][typ][l][d], y = st[id][typ][r - (1 << d) + 1][d];
if (x != mx and y != mx) return max(x, y);
else return x == mx ? y : x;
} else {
int x = st[id][typ][l][d], y = st[id][typ][r - (1 << d) + 1][d];
if (x != mx and y != mx) return min(x, y);
else return x == mx ? y : x;
}
}
struct ans {
int max1, max2, min1, min2;
};
signed main() {
ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
get(n, m, q);
rep(i,1,n) get(a[0][i]); rep(i,1,m) get(a[1][i]);
rep(i,2,max(n, m)) lgv[i] = lgv[i >> 1] + 1;
init(0, n); init(1, m);
rep(i,1,q) {
get(l1, r1, l2, r2);
ans x = { qry(0, 0, l1, r1), qry(0, 1, l1, r1), qry(0, 2, l1, r1), qry(0, 3, l1, r1) },
y = { qry(1, 0, l2, r2), qry(1, 1, l2, r2), qry(1, 2, l2, r2), qry(1, 3, l2, r2) };
if(y.max2 == mx){
if(x.max1 != mx) cout << 1ll * x.max1 * y.min1 << '\n';
else cout << x.max2 * y.max1 << '\n';
}
else if(y.max1 == mx){
if(x.max2 != mx) cout << x.min2 * y.max2 << '\n';
else cout << x.min1 * y.min2 << '\n';
}
else {
int res = -mx;
if(x.max1 != mx) res = max(res, x.min1 * y.min2);
if(x.max2 != mx) res = max(res, x.max2 * y.max1);
cout << res << '\n';
}
}
return 0;
}
T3 星战
给定一张 个点 条边的有向图。一条边 属于 。
四种操作:
- 摧毁一条边,保证边存在且未被摧毁。
- 重建一条边,保证边存在且已被摧毁。
- 摧毁属于指定点的所有未被摧毁的边。
- 重建属于指定点的所有已被摧毁的边。
每次操作后询问当前未被摧毁的边能否形成内向基环树森林。
。
原题面真nm长
考虑为 YES 当且仅当每个点的出度为 。这启发我们记录当前所有边的始点的信息。
考虑一个 sum hash,我们为每个点随机一个大权值,为 YES 只需所有边始点权值加和等于所有点权加和。
然而 2. 4. 操作仍然没法处理。发现 2. 4. 操作是批量改,而考虑 1. 3. 操作更改的边只有 条,如果我们只修改被 1. 3. 操作更改的边就能做到 的复杂度。
对每个点开一个 set,其中存的值有两种可能:所有属于该点且被摧毁的边或属于该点且未被摧毁的边。
然后每次 2. 4. 操作暴力修改的复杂度就对了。
复杂度 。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
template<typename T> void get(T & x) {
x = 0; char ch = getchar(); bool f = false;
while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x);
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = s, i##_ = t + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = s, i##_ = t - 1; i > i##_; -- i)
const int N = 5e5 + 10, mod = 1e9 + 9e1;
int n, m, q, typ, u, v, t1, t2, w1[N], w2[N], w1sum[N], w2sum[N], stat[N], sum1, sum2, hsh1, hsh2;
// stat[i] := i 对应 set 里保存的信息 为 1 则保存被删除的边的始点,为 0 则保存仍存在的边的始点
set<int> st[N];
mt19937 rnd(random_device{}());
int add(int a, int b) { return (a + b) % mod; }
signed main() {
ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
get(n, m);
rep(i,1,n) {
w1[i] = rnd() % 100000000, sum1 = add(sum1, w1[i]);
w2[i] = rnd() % 100000000, sum2 = add(sum2, w2[i]);
stat[i] = 1;
}
rep(i,1,m) {
get(u, v);
w1sum[v] = add(w1sum[v], w1[u]);
w2sum[v] = add(w2sum[v], w2[u]);
hsh1 = add(hsh1, w1[u]);
hsh2 = add(hsh2, w2[u]);
}
get(q);
while ( q-- ) {
get(typ);
if (typ == 1) {
get(u, v);
hsh1 = add(hsh1, mod - w1[u]), hsh2 = add(hsh2, mod - w2[u]);
if (stat[v]) st[v].insert(u);
else st[v].erase(u);
} else if (typ == 2) {
get(u); t1 = t2 = 0;
for (int x : st[u]) {
t1 = add(t1, w1[x]);
t2 = add(t2, w2[x]);
}
if (stat[u] == 0) hsh1 = add(hsh1, - t1 + mod), hsh2 = add(hsh2, - t2 + mod);
else hsh1 = add(hsh1, t1 - w1sum[u] + mod), hsh2 = add(hsh2, t2 - w2sum[u] + mod) % mod;
st[u].clear();
stat[u] = 0;
} else if (typ == 3) {
get(u, v);
hsh1 = add(hsh1, w1[u]), hsh2 = add(hsh2, w2[u]);
if (stat[v] == 0) st[v].insert(u);
else st[v].erase(u);
} else {
get(u); t1 = t2 = 0;
for (int x : st[u]) {
t1 = add(t1, w1[x]);
t2 = add(t2, w2[x]);
}
if (stat[u]) hsh1 = add(hsh1, t1), hsh2 = add(hsh2, t2);
else hsh1 = add(hsh1, w1sum[u] - t1 + mod), hsh2 = add(hsh2, w2sum[u] - t2 + mod) % mod;
st[u].clear();
stat[u] = 1;
}
puts((hsh1 == sum1 and hsh2 == sum2) ? "YES" : "NO");
}
return 0;
}
T4 数据传输
小 C 正在设计计算机网络中的路由系统。
测试用的网络总共有 台主机,依次编号为 。这 台主机之间由 根网线连接,第 条网线连接个主机 和 。保证任意两台主机可以通过有限根网线直接或者间接地相连。受制于信息发送的功率,主机 能够直接将信息传输给主机 当且仅当两个主机在可以通过不超过 根网线直接或者间接的相连。在计算机网络中,数据的传输往往需要通过若干次转发。假定小 C 需要将数据从主机 传输到主机 (),则其会选择出若干台用于传输的主机 ,并按照如下规则转发:对于所有的 ,主机 将信息直接发送给 。
每台主机处理信息都需要一定的时间,第 台主机处理信息需要 单位的时间。数据在网络中的传输非常迅速,因此传输的时间可以忽略不计。据此,上述传输过程花费的时间为 。
现在总共有 次数据发送请求,第 次请求会从主机 发送数据到主机 。小 C 想要知道,对于每一次请求至少需要花费多少单位时间才能完成传输。
,,,,。
场上看到 感觉是个 sb 数据点分治题
最后发现不是
但是部分分肯定得分治(
k=1 打个树上前缀和就行,k=2 我们发现跳出原链肯定不优因此剖后矩阵乘维护 dp 数组即可。
然后是 k=3.
我们发现 k=3 的转移点都在某个点周围长为 1 的范围内,因此考虑对每个点扫一遍找到它的转移点即可类似 k=2 的情况转移了。
然后剖。剖完维护一个向上的矩乘和向下的矩乘,顺着链扫出需要的矩阵,再把第二段 reverse 一下就能得到需要的 个矩阵。顺着乘起来就行了。
时间复杂度 ,带个 的常数。
当然可以维护每条链从链顶到任意链上位置的矩阵,然后复杂度剪掉一个 。但是常数大得多,时空复杂度都不如两个 的做法。可能是我写挂了?
这里放两个 的做法。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
template<typename T> void get(T & x) {
x = 0; char ch = getchar(); bool f = false;
while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x);
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = s, i##_ = t + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = s, i##_ = t - 1; i > i##_; -- i)
const int N = 2e5 + 10;
int n, q, k, t1, t2, valk[N];
int dep[N], fa[N], siz[N], son[N], dfn[N], idfn[N], stp, top[N];
vector <int> g[N];
struct mat {
int a[3][3], n, m;
mat(int _n, int _m) : n(_n), m(_m) { memset(a, 0x3f, sizeof a); }
mat(int _n = 0) : n(_n), m(_n) { memset(a, 0x3f, sizeof a); rep(i,0,n-1) a[i][i] = 0; }
int* operator[] (const int & p) { return a[p]; }
const int* operator[] (const int & p) const { return a[p]; }
mat operator* (const mat & b) const {
mat ret(n, b.m);
rep(i,0,n-1) rep(j,0,b.m-1) rep(k,0,m-1) {
ret[i][j] = min(ret[i][j], a[i][k] + b[k][j]);
} return ret;
}
};
struct SegmentBeats {
mat seg[N << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
void upd(int p, int l, int r, int pos, mat val) {
if (l == r) { seg[p] = val; return; }
int mid = l + r >> 1;
if (pos <= mid) upd(ls, l, mid, pos, val);
else upd(rs, mid+1, r, pos, val);
seg[p] = seg[ls] * seg[rs];
}
mat qry(int p, int l, int r, int L, int R) {
if (L > R) return mat(k);
if (L <= l and r <= R) return seg[p];
int mid = l + r >> 1;
if (R <= mid) return qry(ls, l, mid, L, R);
else if (mid < L) return qry(rs, mid+1, r, L, R);
else return qry(ls, l, mid, L, R) * qry(rs, mid+1, r, L, R);
}
#undef ls
#undef rs
} tr1, tr2;
void find_hc(int u, int f) {
dep[u] = dep[f] + 1, fa[u] = f; siz[u] = 1;
for (int v : g[u]) if (v != f) {
find_hc(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
}
void con_hc(int u, int ctop) {
dfn[u] = ++stp, idfn[stp] = u; top[u] = ctop;
if (son[u]) con_hc(son[u], ctop);
for (int v : g[u]) if (v != fa[u] and v != son[u]) {
con_hc(v, v);
}
}
int find(int u) {
int ans = 2e9;
for (int v : g[u]) ans = min(ans, valk[v]);
return ans;
}
mat getv(int u) {
mat ret(k, k);
rep(i,0,k-1) ret[i][0] = valk[u];
rep(i,1,k-1) ret[i-1][i] = 0;
if (k == 3) ret[1][1] = find(u);
return ret;
}
void upd(int pos, mat val) {
tr1.upd(1, 1, n, dfn[pos], val);
tr2.upd(1, 1, n, n - dfn[pos] + 1, val);
}
int query(int s, int t) {
int S = s;
static vector<mat> a, b; a.clear(), b.clear();
bool ck = true;
while (top[s] != top[t]) {
if (dep[top[s]] > dep[top[t]]) {
if (ck) a.push_back(tr2.qry(1, 1, n, n - dfn[s] + 2, n - dfn[top[s]] + 1)), ck = false;
else a.push_back(tr2.qry(1, 1, n, n - dfn[s] + 1, n - dfn[top[s]] + 1));
s = fa[top[s]];
} else {
b.push_back(tr1.qry(1, 1, n, dfn[top[t]], dfn[t]));
t = fa[top[t]];
}
} if (dfn[s] >= dfn[t]) {
if (ck) a.push_back(tr2.qry(1, 1, n, n - dfn[s] + 2, n - dfn[t] + 1)), ck = false;
else a.push_back(tr2.qry(1, 1, n, n - dfn[s] + 1, n - dfn[t] + 1));
} else {
if (ck) b.push_back(tr1.qry(1, 1, n, dfn[s] + 1, dfn[t])), ck = false;
else b.push_back(tr1.qry(1, 1, n, dfn[s], dfn[t]));
}
reverse(b.begin(), b.end());
mat w(1, k);
w[0][0] = valk[S];
for (mat tmp : a) w = w * tmp;
for (mat tmp : b) w = w * tmp;
return w[0][0];
}
signed main() {
get(n, q, k);
rep(i,1,n) get(valk[i]);
rep(i,2,n) get(t1, t2), g[t1].push_back(t2), g[t2].push_back(t1);
find_hc(1, 0); con_hc(1, 1);
rep(i,1,n) upd(i, getv(i));
while ( q-- ) {
get(t1, t2);
cout << query(t1, t2) << '\n';
}
return 0;
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/csp-s-jile.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)