"蔚来杯"2022牛客暑期多校训练营1 题解

A. Villages: Landlines

用坐标和半径转化为区间,问题转化为:

\(n\)个区间的每一个区间至少与另外一个区间有交集,不相交则延长区间直到相交,问最小延长长度。

按右端点降序排序,每次遍历存储最小的左端点,发现当前区间右端点小于现在的最小左端点时更新答案。

#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;

const int MAXN = 2e5 + 5;

int n;
pii a[MAXN];

bool cmp(pii a, pii b) {
	return a.fi + a.se > b.fi + b.se;
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i].fi >> a[i].se;
	sort(a + 1, a + 1 + n, cmp);
	int ans = 0, minl = a[1].fi - a[1].se, lastl = minl, lastr = a[1].fi + a[1].se;
	for(int i = 2; i <= n; i++) {
		int cl = a[i].fi - a[i].se, cr = a[i].fi + a[i].se;
		if(minl - cr > 0) ans += minl - cr; 
		minl = min(minl, cl);
		lastl = cl, lastr = cr;
	}
	cout << ans << "\n";
	return 0;
} 

B. Spirit Circle Observation

符合要求的两个子串格式为\(Sp9999...999\)\(S(p+1)0000...000\),其中\(p \in [0, 8]\)

对原串构造SAM,枚举节点 \(i\) 找相同前缀,此时 \((st[i].len - st[ st[i].link ].len)\)\(S\) 的不同长度,

接着枚举 \(p\),分别跳到 \(u=nxt[i][p]\)\(v=nxt[i][p+1]\),此时 \(st[u].size\) 代表 \(u\)\(endpos\) 集合,即 \(Sp\) 在原串中的不同位置,\(st[v].size\) 代表 \(v\)\(endpos\) 集合,即 \(S(p+1)\) 在原串中的不同位置,

最后一直沿着9和0的出边走到头,用map存储此过程中经过的节点,直接计算贡献即可。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pir make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 4e5 + 5;
const int MOD = 1e9 + 7;

string S;
int n;

struct SAM {
	int sz = 1, last = 1;
	vector<int> G[MAXN << 1];
	map<pii, int> Map;
	
	struct state {
		int len, link, size;
		int nxt[10];
	}st[MAXN << 1];
	
	void Extend(int c) {
		int cur = ++sz, p = last;
		st[cur].size = 1;
		st[cur].len = st[last].len + 1;
		
		while(p && !st[p].nxt[c]) {
			st[p].nxt[c] = cur;
			p = st[p].link;
		}
		if(!p) st[cur].link = 1;
		else {
			int q = st[p].nxt[c];
			if(st[p].len + 1 == st[q].len) st[cur].link = q;
			else {
				int clone = ++sz;
				st[clone].len = st[p].len + 1;
				for(int i = 0; i <= 9; i++) st[clone].nxt[i] = st[q].nxt[i];
				st[clone].link = st[q].link;
				while(p != -1 && st[p].nxt[c] == q) {
					st[p].nxt[c] = clone;
					p = st[p].link;
				}
				st[q].link = st[cur].link = clone;
			}
		}
		last = cur;
	}
	
	void DFS(int u) {
		for(auto v : G[u]) DFS(v), st[u].size += st[v].size;
	}
	
	void MakeTree() {
		for(int i = 0; i <= sz; i++) G[i].clear();
		for(int i = 2; i <= sz; i++) G[ st[i].link ].push_back(i);
		DFS(1);
	}
	
	int Calc(int u, int v) {
		if(!u || !v) return 0;
		if(Map[ {u, v} ]) return Map[ {u, v} ];
		else return Map[ {u, v} ] = Calc(st[u].nxt[9], st[v].nxt[0]) + st[u].size * st[v].size;
	}
	
	void Calc() {
		int ans = 0; st[0].size = 0; st[0].len = -1;
		for(int i = 1; i <= sz; i++) {
			for(int k = 0; k < 9; k++) {
				int u = st[i].nxt[k], v = st[i].nxt[k + 1];
				int cur = ans;
				ans += (st[i].len - st[ st[i].link ].len) * Calc(u, v);
			}
		}
		cout << ans << "\n";
	}	
}sam;


signed main()
{
	cin >> n >> S;
	for(auto i : S) sam.Extend(i - '0');
	sam.MakeTree();
	sam.Calc();
	return 0;
}

C. Grab the Seat!

一个点能挡住的区域为\((0, 1)\)\((0, m)\)到这个点的两条射线之间的区域。

分别考虑① \((0,1)\)\(k\) 个点的射线的情况,② \((0, m)\)\(k\) 个点的射线的情况。

考虑情况①,我们先对 \(\forall y \in [1,m]\) 找出 \(x\) 值最小的点,按 \(y\) 值从小到大加入射线,

在加入射线的过程中,我们发现斜率大的射线会覆盖斜率小的射线,我们在加入射线的时候维护一下斜率最大值,并以此更新 \(y\) 值固定时的答案上界。

情况②同理,最后直接枚举 \(y\) 值累加即可。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define pir make_pair
#define fi first
#define se second
using namespace std;

const int MAXN = 2e5 + 5;

int n, m, k, q, f[MAXN], ans[MAXN];
pii a[MAXN], maxx, minn;

void Max(int x, int y) {
	int cx = maxx.fi, cy = maxx.se;
	if(y * cx > x * cy) maxx = pir(x, y); 
}

void Min(int x, int y) {
	int cx = minn.fi, cy = minn.se;
	if(y * cx < x * cy) minn = pir(x, y);
}

signed main()
{
	cin >> n >> m >> k >> q;
	for(int i = 1; i <= k; i++) cin >> a[i].fi >> a[i].se;	
	while(q--) {
		int p, cx, cy; cin >> p >> cx >> cy;
		a[p] = pir(cx, cy);
		for(int i = 1; i <= m; i++) f[i] = n + 1;
		for(int i = 1; i <= k; i++) f[ a[i].se ] = min(a[i].fi, f[ a[i].se ]);
		ans[1] = f[1], ans[m] = f[m];
		maxx = pir(f[1], 0); 
		for(int i = 2; i <= m; i++) {
			Max(f[i], i - 1);
			ans[i] = min(f[i], (int)ceil(1.0 * maxx.fi * (i - 1) / maxx.se) );
		}
		minn = pir(f[m], 0);
		for(int i = m - 1; i >= 1; i--) {
			Min(f[i], i - m);
			ans[i] = min(ans[i], (int)ceil(1.0 * minn.fi * (i - m) / minn.se) );
		}
		
		int tot = 0;
		for(int i = 1; i <= m; i++) tot += ans[i] - 1;
		cout << tot << "\n";
	}
	return 0;
} 

F. Cut

只考虑第3个操作,我们可以用线段树维护 当前区间两个端点为奇数还是偶数 和 当前区间能满足题目要求的最长序列。

合并时对于最长序列首先直接加和,然后判断两端是否为01交替,是的话令答案自加1。

其答案最优性可以通过相同的0和1可以合并来考虑,区间的最长序列一定有一个方案会用到端点。

考虑前两个操作,我们将区间分为一个个连续段(排序后连续),并用一个标记来表示当前连续段为升序还是降序,

通过线段树合并和分裂即可维护这些连续段,

对于排序操作,我们进行线段树分裂,将端点部分的连续段分裂出来,再用线段树合并将询问区间内的所有连续段合并,具体看代码。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pir make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 1e5 + 5;
const int MOD = 1e9 + 7;

int n, m, Op[MAXN];
set<int> Set;

struct Node {
	int ltype, rtype, ans, sze;
	friend Node operator + (Node a, Node b) {
		if(!a.sze) return b;
		if(!b.sze) return a;
		Node res;
		res.ans = a.ans + b.ans + (a.rtype ^ b.ltype);
		res.ltype = a.ltype;
		res.rtype = b.rtype;
		res.sze = a.sze + b.sze;
		return res;
	}
}tree[MAXN << 2];

struct Segment_Tree {
	int root[MAXN * 40], son[MAXN * 40][3];
	Node sum[MAXN * 40];
	int pool[MAXN * 40], delcnt = 0, cnt = 0;
	
	void PushUp(int p) { sum[p] = sum[ son[p][0] ] + sum[ son[p][1] ]; }
	
	int NewNode() { return delcnt ? pool[delcnt--] : ++cnt; }
	
	void DelNode(int p) {
		pool[++delcnt] = p;
		sum[p] = {0, 0, 0, 0};
		son[p][0] = son[p][1] = 0;
	}
	
	void Insert(int& p, int l, int r, int loc) {
		if(!p) p = NewNode();
		if(l == r) {
			sum[p].ltype = sum[p].rtype = (loc & 1);
			sum[p].ans = 0, sum[p].sze = 1;
			return ;
		}
		int mid = l + r >> 1;
		if(loc <= mid) Insert(son[p][0], l, mid, loc);
		else Insert(son[p][1], mid + 1, r, loc);
		PushUp(p);
	}
	
	int Merge(int u, int v, int l = 1, int r = n) {
		if(!u || !v) return u + v;
		if(l == r) {
			sum[u] = sum[u] + sum[v];
			DelNode(v); 
			return u;
		}
		int mid = l + r >> 1;
		son[u][0] = Merge(son[u][0], son[v][0], l, mid);
		son[u][1] = Merge(son[u][1], son[v][1], mid + 1, r);
		DelNode(v);
		PushUp(u);
		return u;
	}
	
	void Split(int u, int& v, int k, int flag) {	//把u节点分裂,得到新的放到v里面,分裂前k个数的节点
		if(!k) return ;
		v = NewNode();
		if(k >= sum[ son[u][flag] ].sze) {
			Split(son[u][flag ^ 1], son[v][flag ^ 1], k - sum[ son[u][flag] ].sze, flag);
			swap(son[u][flag], son[v][flag]);
		} else Split(son[u][flag], son[v][flag], k, flag);
		PushUp(u), PushUp(v);
	}
}S;

void Modify(int loc, int l, int r, int p) {
	if(l == r) {
		tree[p] = S.sum[ S.root[loc] ];
		if(Op[loc]) swap(tree[p].ltype, tree[p].rtype);
		return ;
	}
	int mid = l + r >> 1;
	if(loc <= mid) Modify(loc, l, mid, p << 1);
	else Modify(loc, mid + 1, r, p << 1 | 1);
	tree[p] = tree[p << 1] + tree[p << 1 | 1];
}

Node Query(int nl, int nr, int l, int r, int p) {
	if(nl <= l && nr >= r) return tree[p];
	int mid = l + r >> 1;
	if(nl > mid) return Query(nl, nr, mid + 1, r, p << 1 | 1);
	else if(nr <= mid) return Query(nl, nr, l, mid, p << 1);
	else return Query(nl, nr, l, mid, p << 1) + Query(nl, nr, mid + 1, r, p << 1 | 1);
}

set<int>::iterator Split(int x) {
	auto pos = Set.lower_bound(x), tmp = pos;
	if(*pos == x) return pos;
	pos--;
	S.Split(S.root[*pos], S.root[x], *tmp - x, Op[*pos] ^ 1);
	Modify(*pos, 1, n, 1);
	Op[x] = Op[*pos];
	Modify(x, 1, n, 1);
	Set.insert(x);
	return Set.lower_bound(x);
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> n >> m; Set.insert(n + 1);
	for(int i = 1; i <= n; i++) {
		int x; cin >> x;
		Set.insert(i);
		S.Insert(S.root[i], 1, n, x);
		Modify(i, 1, n, 1);
	}
	while(m--) {
		int op, l, r; cin >> op >> l >> r;
		auto pl = Split(l), pr = Split(r + 1);
		if(op == 3) cout << Query(l, r, 1, n, 1).ans + 1 << "\n";
		else {
			for(auto i = ++pl; i != pr; i++) {
				S.Merge(S.root[l], S.root[*i]);
				S.root[*i] = 0;
				Modify(*i, 1, n, 1);
			}
			Set.erase(pl, pr);
			Op[l] = op - 1;
			Modify(l, 1, n, 1);
		}
	}
	return 0;
}

G. Lexicographical Maximum

①若前\(n-1\)位都为9则直接输出字符串

②若前\(n-1\)位有一位不为9则输出长度为\(n-1\),只包含\('9'\)的字符串

#include<bits/stdc++.h>
using namespace std;

string S;
int n;

signed main()
{
	cin >> S;
	n = S.length();
	bool flag = 1;
	for(int i = 0; i < n - 1; i++) flag &= (S[i] == '9');
	if(flag) cout << S;
	else { for(int i = 0; i < n - 1; i++) cout << '9'; } 
	return 0;
} 

I. Chiitoitsu

发现答案不与麻将的具体花色有关,只与各类麻将的数量有关,且一类麻将最多不超过两个。

我们可以通过计算两个同类的麻将(对子)数量和只有一个的麻将(单牌)数量来得出答案。

又因为每次抽到麻将之后不能放回去,所以当前能抽到的麻将的数量也需要被考虑。

\(f[i][j]\)为单牌数量为\(i\)个,当前能抽到的麻将的数量为\(j\)个。

根据贪心策略,我们需要尽量将单牌凑双。所以抽到麻将后我们有两种情况:

①抽到合适的麻将可以凑双,我们结束游戏或者扔掉一张单牌,符合条件的麻将共有\(3i\)个,状态转移到\(f[i-2][j-1]\)

②抽到不合适的麻将直接扔掉,此时状态为\(f[i][j-1]\)

此时有

\[f[i][j] = 1 + \frac{j-3i}{j}f[i][j - 1] + (\frac{3i}{j}f[i - 2][j - 1]) \cdot (i > 1) \]

边界条件有

\[f[i][3i]=f[i-2][3i-1]+1 \]

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define pir make_pair
#define fi first
#define se second
using namespace std;

const int MAXN = 2e5 + 5;
const int MOD = (int)1e9 + 7;

vector<int> vec;
vector< vector<int> > st;
int cnt, f[99][9999];
map<int, int> cur;
string S;

int qpow(int a, int p) {
	int res = 1;
	while(p) {
		if(p & 1) res = res * a % MOD;
		a = a * a % MOD;
		p >>= 1;
	}
	return res;
}

void Init() {
	for(int i = 1; i <= 13; i += 2) {
		if(i == 1) f[1][3] = 1;
		else f[i][3 * i] = f[i - 2][3 * i - 1] + 1;
		for(int j = 3 * i + 1; j <= 34 * 4; j++) {
			f[i][j] = j + (j - 3 * i) * f[i][j - 1] % MOD;
			if(i > 1) f[i][j] += 3 * i * f[i - 2][j - 1] % MOD;
			f[i][j] = f[i][j] % MOD * qpow(j, MOD - 2) % MOD;
		}
	}
}

int Hash(char x, char y) { return x * 3570 + (y + 128); }

signed main()
{
	Init();
	int TT, cnt = 0; cin >> TT;
	while(TT--) {
		cout << "Case #" << ++cnt << ": "; 
		cin >> S; 
		cur.clear();
		for(int i = 0; i < 26; i += 2) cur[ Hash(S[i], S[i + 1]) ] += 1;
		int cnt1 = 0;
		for(auto i : cur) cnt1 += (i.se == 1);
		cout << f[cnt1][34 * 4 - 13] << "\n";
	}
	return 0;
} 
posted @ 2022-07-26 09:42  Orzjh  阅读(40)  评论(0)    收藏  举报