蔚来杯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\) 堆的和的最小花费,有

\[f[k][i]=min_{j<i} \{ f[k-1][j] + max \{a[j+1]...a[i] \} \ \} \]

直接转移复杂度为 \(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]\),有

\[f[k][i] = min_{p \in [1,t]} \{ \ min_{ j \in [r_{p-1}+1,r_p]}\{ f[k-1][j] \} + a[ 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;
}
posted @ 2022-08-19 15:22  Orzjh  阅读(27)  评论(0)    收藏  举报