NOIP2023-div2模拟赛24
2023.10.23
期望得分:\(100+100+60+8\)
实际得分:\(100+84+60+8\)
A. 公园
考虑从小到大枚举 \(X\),然后计算贡献。不难发现如果 \(X\) 不是广场 \(1\) 到某个广场的距离,这个 \(X\) 显然是无用的。也就是说我们只需要枚举 \(X\) 为 \(1\) 到每一个广场的 \(dis\) 就行了。假设当前将 \(x\) 号广场剔除,计算额外的贡献时枚举 \(x\) 的每一条边如果没有计算过贡献则累计边权。
时间复杂度 \(O(mlogm)\)。
B. 括号
沿用 csp-s 2023 的套路,计算以每一个 )
结尾的合法括号序列的方案数。对于每一个 )
,它最多只有一个与它配对的 (
,可以通过栈找到,所以每个能匹配的 )
均可以产生 \(1\) 的贡献。
如果要产生更多的贡献,只有可能在他前面接上一个合法的序列,且能接几个就能产生多少的贡献。我们定义这样配对完剩余左括号数量相同的前缀是同一层的。两个同一层的前缀能接起来当且仅当中间不存在层数少于当前层数的。因为字符串是一连串的左右括号交替出现。所以同层的情况只有可能出现在左括号用完了一整段或右括号用完了一整段。用栈维护层数的同时维护层数出现的次数,然后每次统计层数贡献时要将层数大于当前层数的弹出。
时间复杂度 \(O(n)\)。
code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <unordered_map>
using namespace std;
const int kmax = 2e7 + 3;
int c[kmax];
int p[2];
int n, X, Y, Z;
int stk[kmax], tp;
unsigned long long res;
long long stc[kmax], ct[kmax], tc;
long long cur;
void Init() {
cin >> n >> X >> Y >> Z >> p[0] >> p[1] >> c[0] >> c[1];
for(int i = 2; i < n; i++) {
c[i] = (1ll * c[i - 1] * X + 1ll * c[i - 2] * Y + 1ll * Z) % p[i & 1] + 1;
}
}
int main() {
freopen("brackets.in", "r", stdin);
freopen("brackets.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
Init();
// for(int i = 0; i < n; i++) {
// cout << c[i] << ' ';
// }
// cin >> n;
// for(int i = 0; i < n; i++) {
// cin >> c[i];
// }
for(int i = 0; i < n; i++) {
if(i & 1) {
for(; tp && c[i]; ) {
if(c[i] >= stk[tp]) {
// cout << "get 1\n";
cur -= stk[tp];
res += stk[tp];
// cout << res << '\n';
c[i] -= stk[tp];
for(tp--; tc && stc[tc] > cur; ct[tc--] = 0) {
}
// cout << cur << ' ' << ct[cur] << '\n';
if(tc && stc[tc] == cur) {
res += ct[tc];
ct[tc]++;
} else {
stc[++tc] = cur;
ct[tc] = 1;
}
// cout << res << ' ' << cur << "***" << ' ' << ct[cur] << '\n';
} else {
// cout << "get 2\n";
cur -= c[i];
res += c[i];
// cout << res << '\n';
stk[tp] -= c[i];
c[i] = 0;
for(; tc && stc[tc] > cur; ct[tc--] = 0) {
// cout << "ERROR" << cur << ' ' << stc[tc] << '\n';
}
// cout << cur << ' ' << ct[cur] << '\n';
if(tc && stc[tc] == cur) {
res += ct[tc];
ct[tc]++;
} else {
stc[++tc] = cur;
ct[tc] = 1;
}
// cout << res << ' ' << cur << "***" << ' ' << ct[cur] << '\n';
}
}
if(c[i]) {
for(; tc; ct[tc--] = 0) {
}
}
} else {
stk[++tp] = c[i];
cur += c[i];
}
// cout << i << ' ' << res << ' ' << cur << '\n';
}
cout << res << '\n';
return 0;
}
C. 学校
考虑朴素的dp。定义 \(f_{i,j,k,l}\) 表示最后四个数从后往前分别选择了 \(i,j,k,l\) 的方案数。显然 \(l\) 的那一维可以前缀和优化掉。时间复杂度 \(O(n^3)\)。
观察到 \(a_i\) 互不相同,那么对于子序列中连续的 \(5\) 个元素,如果前四个异
或和为 \(s\),那么后四个异或和一定不为 \(s\)。考虑重新定义状态 \(f_{i,j}\) 表示最后两位从后往前分别为 \(i,j\) 的方案数。有转移:
将式子拆开,相当于要求 \(\sum\limits_{k<j}f_{j,k}\) 和 \(\sum\limits_{l<k<j,a_i\oplus a_j\oplus a_k\oplus a_l=s}f_{k,l}\) 两个式子。枚举 \(j\),同时维护好上述两个式子即可。时间复杂度 \(O(n^2)\)。
code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int kmax = 4005;
const int kmaxM = 12;
const int Mod = 998244353;
int n, m, s, a[kmax];
long long f[kmax][kmax], res;
long long g[1 << kmaxM], tot;
int main() {
freopen("school.in", "r", stdin);
freopen("school.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> s;
for(int i = 1; i <= n; i++) {
cin >> a[i];
f[i][0] = 1;
}
for(int i = 1; i <= n; i++) {
tot = 0;
for(int j = 0; j < i; j++) tot = (tot + f[i][j]) % Mod;
res = (res + tot) % Mod;
for(int j = i + 1; j <= n; j++) {
f[j][i] = (tot - g[s ^ a[i] ^ a[j]] + Mod) % Mod;
}
for(int j = 1; j < i; j++) {
g[a[i] ^ a[j]] = (g[a[i] ^ a[j]] + f[i][j]) % Mod;
}
}
cout << res << '\n';
return 0;
}
D. 运算
不难发现,我们不在乎最后乘出来的数值是多少,只在乎它对 \(n\) 取模后的结果。类似同于最短路的想法,将每个余数向能到达余数建反向边。然后跑一遍反图的最短路就能求出结果。
但是这样的边太多了,考虑压缩边的数量。将每一条边挂在区间的左端点上,每次转移时就往左跳区间长度个位置就行了。这样只有 \(O(n)\) 条边。
但这样每个点会遍历多次,产生冗余。可以考虑并查集优化,将遍历过的点并在它后面的点上。最短路的边权都为 \(1\),可以 \(bfs\) 实现,时间复杂度 \(O(n)\)。
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int kmax = 1e6 + 3;
int t;
int n, d, l, r, m;
int dis[kmax];
bool b[kmax];
int f[kmax];
vector<int> e[kmax];
int F(int x) {
return f[x] == x ? x : f[x] = F(f[x]);
}
void Dijkstra() {
queue<int> q;
for(int i = 0; i <= n; i++) {
dis[i] = 1e9;
f[i] = i;
}
for(int z = F((n - r) % n); z <= (n - l) % n; z = F(z)) {
f[z] = z + 1, dis[z] = 0;
q.push(z);
}
for(; !q.empty(); q.pop()) {
int x = q.front();
for(int y : e[x]) {
if((y - r + n) % n <= (y - l + n) % n) {
// cout << (y - r + n) % n << ' ' << (y - l + n) % n << '\n';
for(int z = F((y - r + n) % n); z <= (y - l + n) % n; z = F(z)) {
// cout << "z = " << z << '\n';
f[z] = z + 1, dis[z] = dis[x] + 1;
q.push(z);
}
} else {
for(int z = F((y - r + n) % n); z < n; z = F(z)) {
f[z] = z + 1, dis[z] = dis[x] + 1;
q.push(z);
}
for(int z = F(0); z <= (y - l + n) % n; z = F(z)) {
f[z] = z + 1, dis[z] = dis[x] + 1;
q.push(z);
}
}
}
}
}
void Solve() {
cin >> n >> d >> l >> r >> m;
for(int i = 0; i < n; i++) {
e[i].clear();
}
for(int i = 0; i < n; i++) {
int x = 1ll * i * d % n;
// cout << (x + l) % n << "***\n";
e[x].push_back(i);
}
Dijkstra();
// for(int i = 0; i < n; i++) {
// cout << i << ' ' << dis[i] << '\n';
// }
for(int i = 1, x; i <= m; i++) {
cin >> x;
cout << (dis[x] == 1e9 ? -1 : dis[x]) << '\n';
}
}
int main() {
freopen("calculator.in", "r", stdin);
freopen("calculator.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> t;
while(t--) {
Solve();
}
return 0;
}