2025.10.21 NOIP 模拟赛 题解
T1 码(code)
题意
给定 \(n\) 个字符串 \(s\),每个字符可以匹配任意自然数次,求出能匹配所有字符串的长度为 \(4\) 的字符串数量,字符集为 \(\text0\sim\text9\),\(n\le10^3,|s_i|\le10^4,\sum|s_i|\le10^6\)
分析
建立类似自序列自动机的结构,每个 \(s_i\) 暴力枚举所有字符串尝试匹配即可
时间复杂度 \(O(|\sum|\sum|s_i|+10^\omega\omega n)\),其中 \(\omega=4\)
代码:
#include <bits/stdc++.h>
using namespace std;
int n;
bool f[10000];
int main(){
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
fill(f, f + 10000, 1);
while (n--){
int l; string s;
cin >> l >> s;
vector<array<int, 10> > nx(l);
array<int, 10> f;
f.fill(l);
for (int i = l - 1; ~i; --i){
f[s[i] - '0'] = i;
nx[i] = f;
}
auto mtc = [&](string s){
int p = 0;
for (char c : s){
assert('0' <= c && c <= '9');
p = nx[p][c - '0'];
if (p >= l)return 0;
}
return 1;
};
for (int i = 0; i < 10000; ++i){
if (::f[i] && !mtc({char(i / 1000 + '0'), char(i / 100 % 10 + '0'), char(i / 10 % 10 + '0'), char(i % 10 + '0')}))
::f[i] = 0;
}
}
cout << count(f, f + 10000, 1) << endl;
return 0;
}
/*
g++ code.cpp -o code.exe -std=c++14 -Wall -O0 -g -fsanitize=address
g++ code.cpp -o code.exe -std=c++14 -Wall -O2
*/
T2 书(book)
题意
给定 \(w_{1\sim n}\),\(h_{1\sim n}\),将 \([1,n]\) 划分为若干段,要求每段的 \(\sum w\) 不超过 \(l\),求出每段的 \(\max h\) 之和的最小值,\(n\le10^5,l\ge \max w\)
分析
令 \(f_i\) 表示 \(1\sim i\) 的答案,容易线段树优化,时间复杂度 \(O(n\log n)\)
代码:
#include <bits/stdc++.h>
using namespace std;
int n, l, h[100010], w[100010];
long long sw[100010];
long long f[100010];
long long mnv[100010 << 2], adt[100010 << 2];
inline void ad(int k, long long v){
mnv[k] += v;
adt[k] += v;
}
inline void pu(int k){
mnv[k] = min(mnv[k << 1], mnv[k << 1 | 1]);
}
inline void pd(int k){
if (adt[k]){
ad(k << 1, adt[k]);
ad(k << 1 | 1, adt[k]);
adt[k] = 0;
}
}
inline void assg(int p, long long v, int l = 1, int r = n, int k = 1){
if (l == r){
mnv[k] = v;
return;
}
int M = (l + r) >> 1; pd(k);
if (p <= M)assg(p, v, l, M, k << 1);
else assg(p, v, M + 1, r, k << 1 | 1);
pu(k);
}
inline void add(int L, int R, long long v, int l = 1, int r = n, int k = 1){
if (L > R)return;
if (L <= l && r <= R)return ad(k, v);
int M = (l + r) >> 1; pd(k);
if (L <= M)add(L, R, v, l, M, k << 1);
if (R > M)add(L, R, v, M + 1, r, k << 1 | 1);
pu(k);
}
inline long long minv(int L, int R, int l = 1, int r = n, int k = 1){
if (L <= l && r <= R)return mnv[k];
int M = (l + r) >> 1; pd(k);
if (R <= M)return minv(L, R, l, M, k << 1);
if (L > M)return minv(L, R, M + 1, r, k << 1 | 1);
return min(minv(L, R, l, M, k << 1), minv(L, R, M + 1, r, k << 1 | 1));
}
int main(){
freopen("book.in", "r", stdin);
freopen("book.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> l;
for (int i = 1; i <= n; ++i)cin >> h[i] >> w[i], sw[i] = sw[i - 1] + w[i];
fill_n(f, n + 1, 1e18);
f[0] = 0;
static int s[100010], tp;
s[tp = 0] = 0;
for (int i = 1; i <= n; ++i){
while (tp && h[s[tp]] <= h[i])add(s[tp - 1] + 1, s[tp], h[i] - h[s[tp]]), --tp;
s[++tp] = i;
assg(i, f[i - 1] + h[i]);
// for (int j = i, mx = 0; j; --j)assert(minv(j, j) == f[j - 1] + (mx = max(mx, h[j])));
int p = lower_bound(sw, sw + i + 1, sw[i] - l) - sw + 1;
f[i] = minv(p, i);
// for (int x = i, mxv = 0; x >= p; --x){
// mxv = max(mxv, h[x]);
// f[i] = min(f[i], f[x - 1] + mxv);
// }
}
cout << f[n] << endl;
return 0;
}
/*
g++ book.cpp -o book.exe -std=c++14 -Wall -O0 -g -fsanitize=address
*/
T3 图(graph)
题意
给定一张 \(n\) 点 \(m\) 边的简单无向图,求出一组 \(P\subset [1,n],Q\subset [1,n]\),令 \(p\) 为 \(P\) 的导出子图中度数的最小值,\(q\) 为 \(|Q|\) 且 \(Q\) 为独立集,要求 \((p+1)(q+1)>n\),\(n\le10^4,m\le10^5\),多测 \(T\le32\)
分析
每次取出剩余部分中度数最小的点删去,则 \(p\) 为此过程中点被删去时的度数的最大值,构造 \(P\) 是容易的,按此顺序贪心取 \(Q\),显然每个点 \(u\) 至多导致另外 \(p\) 个点无法加入 \(Q\),因此 \(q=|Q|\ge \left\lceil\frac{n}{p+1}\right\rceil\),即 \(n\le q(p+1)<(q+1)(p+1)\)
容易用堆做到 \(O((n+m)\log n)\)
代码:
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<int> e[10010];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
void clr(){
decltype(pq)().swap(pq);
}
int dg[10010];
void work(){
cin >> n >> m;
fill_n(e + 1, n, vector<int>{});
while (m--){
int u, v;
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
int p = 0;
vector<int> pts;
{
clr();
for (int i = 1; i <= n; ++i)pq.emplace(dg[i] = e[i].size(), i);
static bool rm[10010];
fill_n(rm + 1, n, 0);
while (!pq.empty()){
auto du = pq.top();
pq.pop();
int d = du.first, u = du.second;
if (dg[u] != d)continue;
p = max(p, d);
rm[u] = 1;
for (int v : e[u])if (!rm[v])--dg[v], pq.emplace(dg[v], v);
}
clr();
for (int i = 1; i <= n; ++i)pq.emplace(dg[i] = e[i].size(), i);
fill_n(rm + 1, n, 0);
while (!pq.empty()){
auto du = pq.top();
int d = du.first, u = du.second;
if (d >= p)break;
pq.pop();
if (dg[u] != d)continue;
rm[u] = 1;
for (int v : e[u])if (!rm[v])--dg[v], pq.emplace(dg[v], v);
}
while (!pq.empty()){
auto du = pq.top();
int d = du.first, u = du.second;
pq.pop();
if (d == dg[u])pts.emplace_back(u);
}
}
cout << pts.size() << " ";
for (int u : pts)cout << u << " ";
cout << "\n";
int q = 0;
vector<int> qts;
{
clr();
for (int i = 1; i <= n; ++i)pq.emplace(dg[i] = e[i].size(), i);
static bool rm[10010], dl[10010];
fill_n(rm + 1, n, 0);
fill_n(dl + 1, n, 0);
while (!pq.empty()){
auto du = pq.top();
int d = du.first, u = du.second;
pq.pop();
if (dg[u] != d)continue;
dl[u] = 1;
for (int v : e[u])if (!dl[v])
--dg[v], pq.emplace(dg[v], v), !rm[u] && (rm[v] = 1);
}
for (int i = 1; i <= n; ++i)if (!rm[i])qts.emplace_back(i);
q = qts.size();
}
cout << q << " ";
for (int u : qts)cout << u << " ";
cout << "\n";
// clog << p << "*" << q << ":" << n << endl;
}
int main(){
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)work();
return 0;
}
T4 牌(card)
题意
定义 \(F(a_{1\sim 2n})=[a_1,a_{n+1},a_2,a_{n+2},a_3,a_{n+3},\cdots,a_n,a_{2n}]\),令 \(G(a_{1\sim 2n},b_{1\sim 2n})=\sum_{i=1}^{2n}[a_i\ne b_i]\),给定 \(a_{1\sim 2n}\) 和 \(b_{1\sim 2n}\),求出 \(\min_x G(F^x(a),b)\),\(n\le2\times10^5\),多测 \(\sum n\le5\times10^5\)
分析
假定下标从 \(0\) 开始
发现 \(F^k(a)_i=\begin{cases}a_i&i=0\lor i=2n-1\\ a_{n^i\bmod (2n-1)}&\text{otherwise} \end{cases}\)
令 \(ct>0\) 为满足 \(n^{ct}\equiv 1\pmod (2n-1)\) 的最小值(\(n=1\) 时特判,其它情况下一定存在),则 \(F^{ct}(a)=a\)
重排 \(a,b\),使得 \(a_i=i\)
转化为求
令 \(ad_{n^x\bmod (2n-1)}=\sum_{j=1}^{2n-2}[b_j=j\times n^x\bmod (2n-1)]\),求出 \(ad\) 后容易得到答案
枚举 \(j\),则 \(b_j\) 贡献到 \(ad\) 的位置为一个等差树列,容易用扩展欧几里德求出范围,数量为 \(\gcd(j,n)\) 的,因此暴力加的复杂度为 \(O(\sum_j \gcd(j,n))=O(d(n)n)\) 的
总时间复杂度 \(O(\sum (d(n)+\log n)n)\)
代码:
#include <bits/stdc++.h>
using namespace std;
int n, a[400010], b[400010];
int exgcd(int a, int b, int &x, int &y){
if (!b){
x = 1, y = 0;
return a;
}
int e = exgcd(b, a % b, x, y);
tie(x, y) = make_pair(y, x - a / b * y);
return e;
}
pair<int, int> dv(int a, int b, int p){//(r,t) | kt+r
if (a == p)return {-1, 0};
// xb+yp=a
// clog << a << "/" << b << "%" << p << endl;
int x, y;
int e = exgcd(b, p, x, y);
// clog << b << "*" << x << "+" << p << "*" << y << "=" << e << endl;
if (a % e)return {-1, 0};
int ret = 1ll * (x % p + p) % p * (a / e) % p;
// clog << "=" << ret << endl;
assert(1ll * ret * b % p == a);
// clog << "=" << ret << "+" << p / e << "k" << endl;
return {ret, p / e};
}
int sn = 0;
void work(){
cin >> n;
for (int i = 0; i < n + n; ++i)cin >> a[i], --a[i];
for (int i = 0; i < n + n; ++i)cin >> b[i], --b[i];
int c = (a[0] != b[0]) + (a[n + n - 1] != b[n + n - 1]);
if (n == 1){cout << c << "\n";return;}
{
static int p[400010];
for (int i = 0; i < n + n; ++i)p[a[i]] = i;
for (int i = 0; i < n + n; ++i)b[i] = p[b[i]];
}
sn += n;
// if (n <= 2000 && (sn <= 5e4)){
// int ct = 0;
// for (int v = 1; ; ){
// v = (v + v) % (n + n - 1); ++ct;
// if (v == 1)break;
// }
// int rs = c;
// for (int i = 1; i < n + n - 1; ++i)rs += i != b[i];
// for (int i = 1, of = n; i <= ct; ++i, of = 1ll * of * n % (n + n - 1)){
// int s = c + i + n + n - 2;
// for (int j = 1; j < n + n - 1; ++j)s -= 1ll * j * of % (n + n - 1) == b[j];
// rs = min(rs, s);
// }
// cout << rs << "\n";
// return;
// }
int ct = 0;
for (int v = 1; ; ){
v = (v + v) % (n + n - 1); ++ct;
if (v == 1)break;
}
int rs = c;
for (int i = 1; i < n + n - 1; ++i)rs += i != b[i];
static int ad[400010];
fill_n(ad, n + n, 0);
for (int i = 1; i < n + n - 1; ++i){
unsigned p = n + n - 1;
auto rt = dv(b[i], i, p);
int r = rt.first, t = rt.second;
if (r == -1)continue;
for (unsigned x = r, c = p / t; c; --c){
++ad[x];
x += t;
if (x >= p)x -= p;
}
// for (int x = (r + t) % (n + n - 1), c = x == r; c < 1; x = (x + t) % (n + n - 1), c += x == r)++ad[x];
}
for (int i = 1, of = n; i <= ct; ++i, of = 1ll * of * n % (n + n - 1)){
int s = c + i + n + n - 2 - ad[of];
rs = min(rs, s);
}
cout << rs << "\n";
}
int main(){
freopen("card.in", "r", stdin);
freopen("card.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)work();
return 0;
}
比赛结果
\(100+100+100+100\),\(\text{rk}1\)

浙公网安备 33010602011771号