蔚来杯2022牛客暑期多校训练营9 题解
A. Car Show
双指针+桶统计每辆车出现的次数,直接计算即可。
#include<bits/stdc++.h>
#define int long long
#pragma GCC optimize(2)
using namespace std;
const int MAXN = 2e5 + 5;
int num[MAXN], a[MAXN], n, m, cnt, ans;
void Del(int x) {
num[ a[x] ]--;
if(num[ a[x] ] == 0) cnt--;
}
void Add(int x) {
if(num[ a[x] ] == 0) cnt++;
num[ a[x] ]++;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int l = 1, r = 1; l <= n; Del(l++) ) {
while(r <= n && cnt < m) Add(r), r++;
//cout << l << " " << r << " lr\n";
if(cnt == m) ans += n - (r - 1) + 1;
}
cout << ans << "\n";
return 0;
}
E. Longest Increasing Subsequence
先将序列设为 \(2,1,4,3,...,2k,2k-1\),此时答案数为 \(2^k\),lis 长度为 \(k\)。
将 \(m\) 转为二进制后,令k为最高位的1出现的位置,之后删去最高位。然后从高位到低位枚举,枚举到1时代表还需要加 \(2^{pos}\) 个lis。在原先序列的第 \(pos\) 个位置后面加长度为 \(k-pos\) ,所有数都大于 \(2k\) 的递增序列即可。为了控制答案上界,需要保证加入进去的数尽量重复利用多次。
答案上界大概在 \(3\lfloor logn \rfloor\) 左右。
#include<bits/stdc++.h>
using namespace std;
int m, n, sze, cnt1, cnt2, ans[105], add[105];
vector<int> nums;
void Solve() {
memset(ans, 0, sizeof ans);
memset(add, 0, sizeof add);
nums.clear();
cin >> m;
if(m == 1) { cout << "1\n1\n"; return ; }
while(m) nums.push_back(m & 1), m >>= 1;
nums.pop_back(); n = sze = nums.size();
while( nums.size() ) {
int d = nums.back(); nums.pop_back(); sze--;
if(!d) continue;
add[sze] = n - sze;
}
for(int i = 0; i <= n; i++) {
if(!add[i]) continue;
for(int j = i + 1; j <= n; j++) {
if(add[j]) { add[i] -= add[j]; break; }
}
}
sze = n, n = cnt1 = 0, cnt2 = sze * 2;
for(int i = 0; i <= sze; i++) {
if(i) cnt1 += 2, ans[++n] = cnt1, ans[++n] = cnt1 - 1;
for(int j = 1; j <= add[i]; j++) ans[++n] = ++cnt2;
}
cout << n << "\n";
for(int i = 1; i <= n; i++) cout << ans[i] << " ";
cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
F. Matrix and GCD
大致思路是先枚举所有 \(i \in [1,n*m]\),对所有 \(i\) 的倍数的位置设为 \(1\),其他位置设为 \(0\),首先利用悬线法求出全1子矩阵个数,这是一个经典例题。进而得到 gcd 为 \(i\) 的倍数的子矩阵个数 \(g[i]\) 。再利用容斥原理求得 gcd 等于 \(i\) 的子矩阵个数 \(f[i]\) 。
容斥时直接倒序枚举,有 \(f[i]=g[i]-f[k \cdot i], k > 1 \ \& \ k \cdot i \leq nm\)
时间复杂度 \(O(nm\log{nm})\)
#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 1000 + 5;
int s[MAXN], top, h[MAXN][MAXN], val[MAXN], n, m, M[MAXN][MAXN];
long long g[MAXN * MAXN];
pii pos[MAXN * MAXN];
vector<pii> vec;
long long Calc(int x, int l, int r) {
int res = 0; s[top = 0] = l - 1;
for(int i = l; i <= r; i++) {
while(top && h[x][ s[top] ] >= h[x][i]) --top;
val[i] = s[top];
s[++top] = i;
}
s[top = 0] = r + 1;
for(int i = r; i >= l; i--) {
while(top && h[x][ s[top] ] > h[x][i]) --top;
res += 1ll * (i - val[i]) * (s[top] - i) * h[x][i];
s[++top] = i;
}
return res;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) cin >> M[i][j], pos[ M[i][j] ] = {i, j};
}
for(int k = 1; k <= n * m; k++) {
vec.clear();
for(int i = k; i <= n * m; i += k) vec.push_back(pos[i]);
int sze = vec.size();
long long cnt = 0;
sort(vec.begin(), vec.end());
for(int l = 0, r = 0; l < sze; ) {
r = l + 1;
while(r < sze && vec[r].fi == vec[l].fi) r++;
for(int i = l; i < r; i++) h[ vec[i].fi ][ vec[i].se ] = h[ vec[i].fi - 1 ][ vec[i].se ] + 1;
for(int i = l, j = l; i < r; ) {
j = i + 1;
while(j < r && vec[j].se == vec[j - 1].se + 1) j++;
cnt += Calc(vec[l].fi, vec[i].se, vec[j - 1].se);
i = j;
}
l = r;
}
g[k] = cnt;
for(auto &i : vec) h[i.fi][i.se] = 0;
}
long long ans = 0;
for(int i = n * m; i >= 1; i--) {
for(int j = i + i; j <= n * m; j += i) g[i] -= g[j];
ans += 1ll * i * g[i];
}
cout << ans << "\n";
return 0;
}
G. Magic Spells
考虑Manacher算法的执行过程,可得对于一个串 \(S\),其本质不同的回文串数量级在 \(O(|S|)\)。直接跑个Manacher,用map+哈希存此过程中出现的所有回文串,最后统计每个回文串的在不同串中的出现次数即可。
需要用双哈希以防止哈希冲突。
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
#pragma GCC optimize(2)
using namespace std;
const int MAXN = 6e5 + 10;
const int INF = 0x3f3f3f3f;
pii Pow[MAXN], Inv[MAXN];
pii h[15][MAXN];
namespace TwoHash {
const int BASE1 = 31;
const int BASE2 = 29;
const int MOD1 = 1e9 + 7;
const int MOD2 = 998244353;
int qpow(int a, int p, int MOD) {
a %= MOD, p %= MOD;
int res = 1;
while(p) {
if(p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
pii Hash(int c, int p) { return {c * Pow[p].fi % MOD1, c * Pow[p].se % MOD2}; }
pii Add(pii x, pii y) {
pii res = {0, 0};
res.fi = (x.fi + y.fi) % MOD1;
res.se = (x.se + y.se) % MOD2;
return res;
}
pii Sub(pii x, pii y) {
pii res = {0, 0};
res.fi = (x.fi - y.fi + MOD1) % MOD1;
res.se = (x.se - y.se + MOD2) % MOD2;
return res;
}
pii Mul(pii x, pii y) {
pii res = {0, 0};
res.fi = x.fi * y.fi % MOD1;
res.se = x.se * y.se % MOD2;
return res;
}
pii Div(pii x, pii y) {
pii res = {0, 0};
res.fi = x.fi * qpow(y.fi, MOD1 - 2, MOD1) % MOD1;
res.se = x.se * qpow(y.se, MOD1 - 2, MOD1) % MOD1;
return res;
}
void Init() {
Pow[0] = Inv[0] = {1, 1}; Inv[1] = {qpow(BASE1, MOD1 - 2, MOD1), qpow(BASE2, MOD2 - 2, MOD2)};
for(int i = 1; i <= MAXN - 5; i++) Pow[i] = Mul(Pow[i - 1], {BASE1, BASE2});
for(int i = 2; i <= MAXN - 5; i++) Inv[i] = Mul(Inv[i - 1], Inv[1]);
}
}
char T[15][MAXN], tmp[MAXN];
int n, m, K, len[MAXN], f[15][MAXN], g[MAXN];
map<pii, int> Exist[15];
pii Hash(int id, int l, int r) { return TwoHash::Mul( TwoHash::Sub(h[id][r], h[id][l - 1]), Inv[l - 1]); }
void Manacher(int id, int n) {
tmp[0] = 'a' + 29; tmp[1] = 'a' + 28;
for(int i = 1; i <= n; i++) tmp[i * 2] = T[id][i], tmp[i * 2 + 1] = 'a' + 28;
len[id] = n = n * 2 + 1; for(int i = 1; i <= n; i++) T[id][i] = tmp[i];
for(int j = 1; j <= len[id]; j++) h[id][j] = TwoHash::Add(h[id][j - 1], TwoHash::Hash(T[id][j] - 'a' + 1, j) );
int maxR = 0, pos = 0;
for(int i = 1; i <= n; i++) {
Exist[id][ Hash(id, i, i) ] = 1;
if(i < maxR) {
g[i] = min(g[pos * 2 - i], maxR - i);
Exist[id][ Hash(id, i - g[i], i + g[i]) ] = 1;
}
while(i - g[i] - 1 > 0 && i + g[i] + 1 <= n && tmp[i + g[i] + 1] == tmp[i - g[i] - 1]) {
Exist[id][ Hash(id, i - g[i] - 1, i + g[i] + 1) ] = 1;
g[i]++;
}
if(i + g[i] > maxR) maxR = i + g[i], pos = i;
}
for(int i = 0; i <= n; i++) f[id][i] = g[i], g[i] = 0, tmp[i] = '\0';
}
int Solve(pii hash) {
int flag = 1;
for(int j = 1; j <= K; j++) if( !Exist[j][hash] ) flag = 0;
return flag;
}
signed main()
{
TwoHash::Init();
scanf("%lld", &K);
for(int i = 1; i <= K; i++) {
scanf("%s", T[i] + 1); len[i] = strlen(T[i] + 1);
Manacher(i, len[i]);
}
int ans = 0;
for(auto& i : Exist[1]) ans += Solve(i.first);
if(ans % 2 == 0) assert(-1);
cout << (ans >> 1) << "";
return 0;
}
I. The Great Wall II
设 \(f[k][i]\) 表示将 \([1,i]\) 分为 \(k\) 堆的和的最小花费,有
直接转移复杂度为 \(O(n^3)\),考虑优化这个式子。
用单调栈维护区间最大值,单调栈中会有一堆区间 \([r_0+1,r_1],[r_1+1,r_2]...[r_{t-1}+1,r_t]\),这些区间的最大值都是区间右端点的值。
对于一个区间 \([r_{p-1}+1,r_p]\),有
再对区间维护一个 \(minf_p\),表示区间内 \(f[k-1][j]\) 的最小值,有 \(f[k][i]= min_{p \in [1,t]} \{ minf_p + a[ r_p ] \}\)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 8000 + 10;
const int INF = 0x3f3f3f3f;
struct Node {
int id;
long long minf = INF, premin = INF;
}S[MAXN];
int n, a[MAXN], top;
long long f[MAXN][MAXN];
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for(int i = 1, premax = 0; i <= n; i++) {
premax = max(premax, a[i]);
f[1][i] = premax;
}
for(int k = 2; k <= n; k++) {
top = 0;
S[top].id = 0;
S[top].minf = S[top].premin = f[k][0];
for(int i = 1; i <= n; i++) {
long long minf = f[k - 1][i - 1];
while(top && a[ S[top].id ] < a[i]) {
minf = min(minf, S[top].minf);
--top;
}
++top;
S[top].id = i;
S[top].minf = minf;
S[top].premin = min(S[top - 1].premin, S[top].minf + a[i]);
f[k][i] = S[top].premin;
}
}
/*
for(int k = 1; k <= n; k++) {
for(int i = 1; i <= n; i++) cout << f[k][i] << " "; cout << "f\n";
}
*/
for(int k = 1; k <= n; k++) cout << f[k][n] << "\n";
return 0;
}