[考试记录] 2024.8.17 csp-s模拟赛21
T1 Set
解析
思考 + 组合题
场上只能想到暴力01背包再加上bitset优化,很好打。本应该有 60pts(?或者更多),不曾想由于 spj 的一些未知原因喜提 system error,全部 cancelled。喜提 0pts。😓
正解
鸽巢原理。考虑先给所有的 \(a_i\) 模一遍,然后维护前缀和。考虑到这个前缀和总共有 \(n-1\) 种取值(不为 \(0\) 不为 \(n\)),而总共有 \(n\) 个数。那么至少有两个数的前缀和是相等的。找到这两个数,输出他们之间的数即可。复杂度 \(\mathcal{O}(n)\)。
$\text{code}$
#include<bits/stdc++.h>
using namespace std;
constexpr int B = 1 << 23;
char buf[B], *p1 = buf, *p2 = buf;
#define gt() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1 == p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x){
x = 0; int f = 0; char c = gt();
for(; !isdigit(c); c = gt()) f ^= c == '-';
for(; isdigit(c); c = gt()) x = (x<<1) + (x<<3) + (c^'0');
x = f ? -x : x;
}
char obuf[B], *O = obuf;
#define pt(c) (O - obuf == B && (fwrite(obuf, 1, B, stdout), O = obuf), *O++ = (c))
template <typename T> void wt(T x){
if(x < 0) x = -x, pt('-');
if(x >= 10) wt(x / 10);
pt(x % 10 ^ '0');
}
constexpr int N = 1e6 + 5;
int n, a[N], s[N], pos[N];
int main(){
rd(n);
for(int i=1; i<=n; ++i){
rd(a[i]);
s[i] = (s[i-1] + a[i] % n) % n;
if(!pos[s[i]]) pos[s[i]] = i;
else{
wt(i - pos[s[i]]), pt('\n');
for(int j=pos[s[i]]+1; j<=i; ++j)
wt(j), pt(' ');
return fwrite(obuf, 1, O - obuf, stdout), 0;
}
}
}
T2 Read
解析
卡空卡快读题
警惕这道题的 16M 的空间!对于起手 fread
的同志有着血脉压制的威慑力。char[1<<23]
直接炸掉!!!
考场上以为生成的 \(a_i\) 的位置是不能变的,所以以为统计连续出现的相同 \(a\) 即可。然后就没了……还是太 🥬。
正解
仔细想想,若拿走的书本数不为 \(0\),当且仅当整个序列中没有任何一本书的数量大于 \(\frac{n+1}{2}\)。也就是说,如果出现了数量大于那个数的书,他通过合理的排列一定能把其他所有书隔开。
因此,可以 \(\mathcal{O}(n)\) 的复杂度扫一遍,用两个变量 \(id\), \(cnt\), \(cnt\) 初始为 \(0\)。
然后生成每一个 \(A_i\) , 如果 \(cnt=0\) , 那么就令 \(id=A[i],cnt=1\) , 否则如果 \(id==A[i]\) , 则 \(cnt++\) , 如果不等于, \(cnt−−\) 。最后拿到这个 \(id\) 再跑一遍统计 \(id\) 的个数,设为 \(tot\) 。
如果 \(tot\le \frac{n+1}{2}\), 那直接输 \(0\)。反之,每一个这种种类的书,都可以和其他的书(\(n-tot\))配对,再除去边界的那一本,剩下的就是要拿走的最小数目了 \(tot-(n-tot)-1\)。
$\text{code}$
#include<bits/stdc++.h>
using namespace std;
constexpr int B = 1 << 13;
char buf[B], *p1 = buf, *p2 = buf;
#define gt() (p1==p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x){
x = 0; int f = 0; char ch = gt();
for(; !isdigit(ch); ch = gt()) f ^= ch == '-';
for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^'0');
x = f ? -x : x;
}
char obuf[B], *O = obuf;
#define pt(ch) (O - obuf == B && (fwrite(obuf, 1, B, stdout), O = obuf), *O++ = (ch))
template <typename T> inline void wt(T x){
if(x < 0) x = -x, pt('-');
if(x >= 10) wt(x / 10); pt(x % 10 ^ '0');
}
constexpr int M = 1e3 + 5;
int x[M], y[M], cnt[M], z[M], m, k, tot, id;
int main(){
rd(m), rd(k);
for(int i=1; i<=m; ++i) rd(cnt[i]);
for(int i=1; i<=m; ++i) rd(x[i]);
for(int i=1; i<=m; ++i) rd(y[i]);
for(int i=1; i<=m; ++i) rd(z[i]);
int s = (1 << k) - 1, n = 0;
for(int i=1; i<=m; ++i){
if(tot) tot += ((id != x[i]) ? -1 : 1);
if(!tot) id = x[i], tot = 1;
long long last = x[i];
for(int j=1; j<cnt[i]; ++j){
last = (last * y[i] + z[i]) & s;
if(tot) tot += ((id != last) ? -1 : 1);
if(!tot) id = last, tot = 1;
} n += cnt[i];
} tot = 0;
for(int i=1; i<=m; ++i){
tot += (x[i] == id);
long long last = x[i];
for(int j=1; j<cnt[i]; ++j){
last = (last * y[i] + z[i]) & s;
tot += (last == id);
}
}
wt(tot <= ((n+1)>>1) ? 0 : ((tot<<1) - n - 1));
return fwrite(obuf, 1, O - obuf, stdout), 0;
}
T3 题目交流通道
解析
图论 + 组合题
真的很好的一道容斥题,部分分送的也很到位。
60pts
看这贴心的 \(N\le 400\),一看就是 \(\mathcal{O}(N^3)\) 的算法。
考虑当前两点之间的最短距离为 \(d(i,j)\) ,为了保证这一距离,要么在两点之间连一条边,让其长度为 \(d(i,j)\),要么让其它边的长度和为 \(d(i,j)\)。那么就可以跑一遍 \(\text{Floyed}\),如果存在某一点,使得 \(dis(i,z)+dis(z,j)=d(i,j)\),那么 \(i\sim j\) 这条边就可以设置为 \([dis(i,j),k]\),在答案里乘上 \(k-dis(i,j)+1\)。可以获得 60pts 的好成绩。
$\text{code}$
#include<bits/stdc++.h>
using namespace std;
#define B 1 << 23
char buf[B], *p1 = buf, *p2 = buf;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1 == p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x) {
x = 0; int f = 0; char c = gc();
for (; c < '0' || c > '9'; c = gc())
f ^= c == '-';
for (; c >= '0' && c <= '9'; c = gc())
x = (x << 3) + (x << 1) + (c ^ '0');
x = f ? -x : x;
}
char obuf[B], *O = obuf;
#define pt(c) (O - obuf == B && (fwrite(obuf, 1, B, stdout), O = obuf), *O++ = (c))
template <typename T> void wt(T x) {
if (x < 0) x = -x, pt('-');
if (x >= 10) wt(x / 10);
pt(x % 10 ^ '0');
}
#define ll long long
constexpr int N = 405, M = 998244353;
int n, k, d[N][N], dis[N][N], ans = 1;
bitset<N> ok[N];
int main(){
rd(n), rd(k); memset(dis, 0x3f, sizeof dis);
for(int i=1; i<=n; ++i) for(int j=1; j<=n; ++j) rd(d[i][j]), dis[i][j] = d[i][j];
for(int k=1; k<=n; ++k) for(int i=1; i<=n; ++i) for(int j=1; j<=n; ++j){
if(i != k && i != j && k != j){
if(dis[i][k] + dis[k][j] == d[i][j]) ok[i][j] = 1;
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
for(int i=1; i<=n; ++i) for(int j=1; j<=n; ++j){
if(i != j && dis[i][j] < d[i][j]){
pt('0'); return fwrite(obuf, 1, O - obuf, stdout), 0;
}
}
for(int i=1; i<=n; ++i) for(int j=ok[i]._Find_first(); j!=ok[i].size(); j=ok[i]._Find_next(j)){
if(j <= i) continue;
ans = (ll)ans * (k - d[i][j] + 1) % M;
} wt(ans);
return fwrite(obuf, 1, O - obuf, stdout), 0;
}
100pts
显然,如果最短距离等于 0,那么上述结论不成立。因为只要有足够的零边保证联通块内点之间距离为 0,那么我可以不管其它边权值是啥,但是如果通过上述结论的话,那么不能保证零边和其他不为零边的关系,也就是说,可能全都是非零边在联通块里面。
为了解决上述问题,不妨将所有由零边构成的联通块缩成团,在团和团之间使用上述结论,在团内部另加考虑。
对于团和团的之间的情况,假设两个团分别为 \(a\)、\(b\)。那么 \(a\) 和 \(b\) 之间的最短距离是一定为 \(a\) 内的点和 \(b\) 内的点之间的最短距离。如果不是,那么无解。最短距离知道了,但是会存在重边,重边有 \(num=siz_a\times siz_b\) 条。同样,为了保证两联通块之间的距离,要让这之间的边至少一个长度为 \(d(a,b)\),但不能都大于这个值。那么考虑容斥原理,让所有边长度都大于等于 \(d(a,b)\) 的方案数减去所有变长度都大于 \(d(a,b)\) 方案数即可,为 \((k-d(a,b)+1)^{num}-(k-d(a,b))^{num}\)。假设存在一联通块 \(c\),使得 \(d(a,c)+d(c,b)=d(a,b)\),那么他俩之间的所有边都可以长度大于等于 \(d(a,b)\),方案数为 \((k-d(a,b)+1)^{num}\)。
对于团内部的情况。设 \(f[i]\) 表示 \(i\) 个点的集合之间两两最短距离为 \(0\) 的方案数,\(g[i]\) 表示 \(i\) 个点的集合之间所有情况的方案数。如果直接想好像不太好想转移方程,但每一个 \(g[i]\) 是已知的,狮子为:
其中 \(\binom{i}{2}=\frac{i(i-1)}{2}\) 表示点数为 \(i\) 的图内选边的方案数,又每个边的取值都可以为 \(0\sim k\),所以是 \(k+1\)。
现在考虑 \(f[i]\) 的情况。考虑正难则烦算法,看看什么情况不合法。如果这个图的 \(size<i\),那么这个图显然不合法。所以可以枚举 \(size=j\),找到 \(f[j]\),但还需要再填入 \(i-j\) 个点,那么我们就要在 \(i\) 个点里选出 \(i-j\) 填充进去,就是 \(g[i-j]\)。但这样会产生重复,不妨固定一个点 \(p\),那么就要在 \(i-1\) 个点里选出 \(i-j-1\) 个点。另外,我们必须保证这个图与外部的点相连的边权不为 \(0\),所以还要乘上 \(k^{(n-i)i}\)。综上,狮子为:
\(\mathcal{O}(n)\) 递推即可。
$\text{code}$
#include<bits/stdc++.h>
using namespace std;
constexpr int B = 1 << 23;
char buf[B], *p1 = buf, *p2 = buf;
#define gt() (p1==p2 && (p2=(p1=buf) + fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x){
x = 0; int f = 0; char ch = gt();
for(; !isdigit(ch); ch = gt()) f ^= ch == '-';
for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48);
x = f ? -x : x;
}
char obuf[B], *O = obuf;
#define pt(ch) (O - obuf == B && (fwrite(obuf, 1, B, stdout), O = obuf), *O++ = (ch))
template <typename T> inline void wt(T x){
if(x < 0) pt('-'), x = -x;
if(x >= 10) wt(x / 10); pt(x % 10 ^ '0');
}
#define ll long long
#define fr(i, x, y) for(int i=x; i<=y; ++i)
#define fw fwrite(obuf, 1, O - obuf, stdout)
constexpr int M = 998244353, N = 405;
inline int qpow(int a, int k){
int as = 1;
while(k){
if(k & 1) as = (ll)as * a % M;
a = (ll)a * a % M; k >>= 1;
} return as;
}
int n, k, d[N][N], dis[N][N], f[N], siz[N], p[N], inv[N], t[N], g[N], ans = 1, bl[N], cnt;
bitset<N> vis;
inline int find(int x){
if(!f[x]) return x;
return f[x] = find(f[x]);
}
inline int C(int a, int b){ return (ll)p[a] * inv[b] % M * inv[a-b] % M; }
int main(){
rd(n), rd(k); memset(dis, 0x7f, sizeof dis);
fr(i, 1, n) siz[i] = 1;
p[0] = 1; fr(i, 1, n) p[i] = (ll)p[i-1] * i % M;
inv[n] = qpow(p[n], M - 2); for(int i=n-1; i>=0; --i) inv[i] = (ll)inv[i+1] * (i+1) % M;
fr(i, 1, n) fr(j, 1, n) rd(d[i][j]);
fr(i, 1, n) if(d[i][i]) { pt('0'); return fw, 0; }
fr(i, 1, n) fr(j, 1, i-1) if(d[i][j] != d[j][i]) { pt('0'); return fw, 0; }
fr(z, 1, n) fr(i, 1, n) fr(j, 1, n) if(i != j && i != z && j != z)
if(d[i][z] + d[z][j] < d[i][j]) { pt('0'); return fw, 0; }
fr(i, 1, n) fr(j, 1, i-1){
int fa = find(i), fb = find(j);
if(d[i][j] || fa == fb) continue;
f[fb] = fa; siz[fa] += siz[fb];
}
fr(i, 1, n) if(!vis[find(i)]) vis[find(i)] = 1, bl[++cnt] = find(i);
fr(i, 1, n) fr(j, 1, n) if(find(i) != find(j)) dis[find(i)][find(j)] = d[i][j];
fr(i, 1, cnt) fr(j, 1, i-1) {
bool ok = 0; int a = bl[i], b = bl[j];
fr(z, 1, cnt) {
int c = bl[z];
if(z == i || z == j) continue;
if(dis[a][c] + dis[c][b] == dis[a][b]) ok = 1;
}
if(ok) ans = (ll)ans * qpow(k-dis[a][b]+1, siz[a] * siz[b]) % M;
else ans = (ll)ans * ((qpow(k-dis[a][b]+1, siz[a] * siz[b]) - qpow(k-dis[a][b], siz[a] * siz[b])) % M + M) % M;
}
fr(i, 1, n){
g[i] = t[i] = qpow(k+1, C(i, 2));
fr(j, 1, i-1) t[i] = (((ll)t[i] - (ll)t[j] * g[i-j] % M * C(i-1, j-1) % M * qpow(k, j*(i-j)) % M) % M + M) % M;
}
fr(i, 1, cnt) ans = (ll)ans * t[siz[bl[i]]] % M;
wt(ans); return fw, 0;
}
T4 题目难度飙升
难度飙升题(?)感觉T2难度。虽然我不一定能做出来😶
解析
对顶堆 + mutiset
如果已经知道了这个序列的一部分,那么每次新加入的数一定是大于等于中位数的数。
首先考虑没有重复元素应该怎么做。
在没有重复元素的时候,新加入一个数想要保证中位数不下降就必须满足这个数大于等于前面的中位数。
所以选择加入某个数的时候的判断条件就是加入这个数后,剩下的数比中位数大。
具体实现的时候,可以先取出这个最小的数。如果前面的元素数量是奇数,那么加入它以后中位数是中间两个数的平均值,所以需要讨论这个最小数和之前中位数的关系。如果大于之前的中位数,那么我们可以直接选择剩下的数里面最大的,因为不管加进去谁,以后的中位数都没有这个最小的数大,所以可以直接放心地加入最大的;否则,假设之前的中位数为 pp,新加入的数为 x,之前取出的最小数为 v,那么之后的中位数 \(\frac{pp+x}{2}\) 要 \(\ge v\)。所以 x 可以取 \(\le 2v-pp\) 的最大的数。
如果之前的元素数量是偶数,那么加入以后它的中位数就是一个固定的数,不随加入了哪个数改变。如果最小的数大于等于这个数,那么直接加入最大的都不会有影响;否则加入最小的数。
维护中位数可以使用堆。
考虑有重复元素的做法。
如果重复的元素在整个集合的 mid 的后面,那么不会影响。
如果经过 mid 那么可以直接以经过 mid 的这个重复元素的值的最后一位开始,先输出这个数,然后输出小于等于这个数的最大数,然后输出大于这个数的最小数,并删除;重复这个过程,直到后面被删光了。然后把剩下的从大到小输出即可。
如果不仅过,那么我们找到小于 mid 的最大的重复元素,先把小于这个元素的全部按照一个小于等于这个数的最大数、一个大于这个数的最大数的方案输出完。下面应该只剩下大于这个重复元素的了,可以按照上面没有重复元素的做法。
$\text{code}$
#include<bits/stdc++.h>
using namespace std;
constexpr int b = 1 << 23;
char buf[b], *p1 = buf, *p2 = buf;
#define gt() (p1==p2 && (p2=(p1=buf) + fread(buf, 1, b, stdin), p1==p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x){
x = 0; int f = 0; char ch = gt();
for(; !isdigit(ch); ch = gt()) f ^= ch == '-';
for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48);
x = f ? -x : x;
}
char obuf[b], *O = obuf;
#define pt(ch) (O - obuf == b && (fwrite(obuf, 1, b, stdout), O = obuf), *O++ = (ch))
template <typename T> inline void wt(T x){
if(x < 0) pt('-'), x = -x;
if(x >= 10) wt(x / 10); pt(x % 10 ^ 48);
}
#define fw fwrite(obuf, 1, O - obuf, stdout)
constexpr int N = 1e5 + 5;
int n, a[N]; bitset<N> vis;
multiset<int> s;
priority_queue<int> A, B;
inline void push(int x){
if(A.empty() || x <= A.top()) A.push(x);
else B.push(-x);
if(A.size() < B.size()) A.push(-B.top()), B.pop();
if(A.size() > B.size() + 1) B.push(-A.top()), A.pop();
}
int main(){
rd(n); for(int i=1; i<=n; ++i) rd(a[i]);
sort(a + 1, a + 1 + n); int mid = n >> 1;
if(a[mid] == a[mid + 1]){
while(mid < n && a[mid] == a[mid + 1]) ++mid;
wt(a[mid]), pt(' '); int i = mid - 1, j = n;
while(i || j > mid){
if(i) wt(a[i--]), pt(' ');
if(j > mid) wt(a[j--]), pt(' ');
} return fw, 0;
}
while(mid > 1 && a[mid] != a[mid - 1]) --mid;
wt(a[mid]), pt(' '); vis[mid] = 1; int p = mid - 1, q = n;
while(p && q > mid) wt(a[p--]), pt(' '), vis[p--] = 1, wt(a[q--]), pt(' '), vis[q--] = 1;
for(int i=1; i<=n; ++i){
if(vis[i]) push(a[i]);
else s.insert(a[i]);
}
while(!s.empty()){
p = *s.begin();
if(A.size() == B.size()){
if(p >= -B.top()) q = *--s.end();
else q = *s.begin();
} else {
if(!B.empty() && (p<<1) >= A.top() - B.top()) q = *--s.end();
else q = *--s.upper_bound((p<<1) - A.top());
} wt(q), pt(' '), s.erase(s.find(q)), push(q);
} return fw, 0;
}