"蔚来杯"2022牛客暑期多校训练营3

A.Ancestor

给定两棵有\(n\)个节点的树\(A、B\),树上节点均有一个权值,给出\(k\)个关键点的编号\(x_1,x_2,...,x_k\),问有多少种方案,使得恰好去掉一个关键点后,剩余关键点在\(A\)中LCA的权值大于\(B\)中LCA的权值?


题解做法是预处理,预处理两个关键点序列在\(A,B\)两棵树上的前后缀LCA,然后枚举去掉的关键点,用前缀后缀LCA计算出剩余节点的LCA比较权值即可

但考场上我比较头铁,写了个虚树的做法(捂脸

\(k\)个关键点求出虚树,当我们要去掉一个节点的时候,对该节点的情况进行分类讨论,并求出新的LCA即可

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const int N = 1e5;
int Point[N + 10], n, K;
struct VirtualTree {
	int pre[(N << 1) + 10], now[N + 10], child[(N << 1) + 10], tot;
	int Fa[N + 10], Num[N + 10], Root;
	void link(int x, int y) { pre[++tot] = now[x], now[x] = tot, child[tot] = y; }
	void connect(int x, int y) { link(x, y), link(y, x); }
	void Dfs(int x, int fa) {
		Fa[x] = fa;
		for (int p = now[x]; p; p = pre[p]) {
			int son = child[p];
			if (son == fa)	continue;
			Dfs(son, x), Num[x]++;
		}
	}
	int solve(int x) {
		if (x == Root) {
			if (Num[x] > 1)	return Root;
			return child[now[Root]];
		}
		if (Fa[x] != Root || Num[x] || Num[Root] > 2)	return Root;
		for (int p = now[Root]; p; p = pre[p]) {
			int son = child[p];
			if (son != x)	return son;
		}
		return 0;
	}
}VTA, VTB;
struct Tree {
	int pre[(N << 1) + 10], now[N + 10], child[(N << 1) + 10], tot, Time;
	int W[N + 10], Deep[N + 10], Top[N + 10], Heavy[N + 10], Sz[N + 10], Fa[N + 10], Dfn[N + 10];
	void link(int x, int y) { pre[++tot] = now[x], now[x] = tot, child[tot] = y; }
	void connect(int x, int y) { link(x, y), link(y, x); }
	void Dfs1(int x, int fa) {
		Deep[x] = Deep[Fa[x] = fa] + (Sz[x] = 1);
		for (int p = now[x]; p; p = pre[p]) {
			int son = child[p];
			if (son == fa)	continue;
			Dfs1(son, x), Sz[x] += Sz[son];
			if (!Heavy[x] || Sz[son] > Sz[Heavy[x]])
				Heavy[x] = son;
		}
	}
	void Dfs2(int x, int fa) {
		if (!x)	return;
		Dfn[x] = ++Time;
		Top[x] = Heavy[fa] == x ? Top[fa] : x;
		Dfs2(Heavy[x], x);
		for (int p = now[x]; p; p = pre[p]) {
			int son = child[p];
			if (son == fa || son == Heavy[x])	continue;
			Dfs2(son, x);
		}
	}
	int LCA(int x, int y) {
		while (Top[x] != Top[y]) {
			if (Deep[Top[x]] < Deep[Top[y]])	swap(x, y);
			x = Fa[Top[x]];
		}
		return Deep[x] < Deep[y] ? x : y;
	}
	void rebuild(VirtualTree& VT) {
		sort(Point + 1, Point + 1 + K, [&](int x, int y) {return Dfn[x] < Dfn[y]; });
		VT.Root = Point[1];
		for (int i = 2; i <= K; i++)	VT.Root = LCA(VT.Root, Point[i]);
		vector<int>stack;
		stack.push_back(VT.Root);
		for (int i = 1; i <= K; i++) {
			int x = Point[i], lca = LCA(x, stack.back());
			if (x == VT.Root)	continue;
			if (lca == stack.back()) {
				stack.push_back(x);
				continue;
			}
			while (stack.size() > 1) {
				int y = *prev(prev(stack.end()));
				if (Dfn[y] >= Dfn[lca])	VT.connect(y, stack.back()), stack.pop_back();
				else {
					if (lca != stack.back()) {
						VT.connect(lca, stack.back());
						stack.pop_back(), stack.push_back(lca);
					}
					break;
				}
			}
			stack.push_back(x);
		}
		while (stack.size() > 1) {
			VT.connect(stack.back(), *prev(prev(stack.end())));
			stack.pop_back();
		}
	}
	void Read(VirtualTree& VT) {
		for (int i = 1; i <= n; i++)	W[i] = read(0);
		for (int i = 2; i <= n; i++)	connect(read(0), i);
		Dfs1(1, 0), Dfs2(1, 0), rebuild(VT), VT.Dfs(VT.Root, 0);
	}
}A, B;
void solve() {
	int Cnt = 0;
	for (int i = 1; i <= K; i++) {
		int _A = VTA.solve(Point[i]);
		int _B = VTB.solve(Point[i]);
		Cnt += A.W[_A] > B.W[_B];
	}
	printf("%d\n", Cnt);
}
int main() {
	//	freopen(".in","r",stdin);
	//	freopen(".out","w",stdout);
	n = read(0), K = read(0);
	for (int i = 1; i <= K; i++)	Point[i] = read(0);
	A.Read(VTA), B.Read(VTB);
	solve();
	return 0;
}

B.Boss

\(n\)个人派遣到\(K\)个城市,每个城市需要\(e_i\)个人,将第\(i\)个人派遣至第\(j\)个城市的代价为\(c_{i,j}\),问最小代价?\((1\leqslant n\leqslant 10^5,1\leqslant K\leqslant 10)\)


一个很显然的做法是费用流,但是点数边数太多,费用流没法跑

考虑费用流的过程,每次都是把一部分人从城市\(a\)挪到城市\(b\)。如果第\(i\)个人从城市\(a\)挪到了城市\(b\),那么其代价为\(-c_{a,i}+c_{b,i}\)。考虑到增广路只会经过每个点一次,所以如果有多个人可以从城市\(a\)到城市\(b\),必然会优先考虑代价小的人

故我们考虑建\(K\)个点代表城市,\(a\)\(b\)的边就是所有在城市\(a\)中的人到城市\(b\)的代价。显然,这样的边数依然是\(O(nK)\)级别,但两点之间会有大量的重边,而上面提到,对于重边我们仅需要考虑其最短边即可。

故我们可以开\(K^2\)个堆去维护点与点之间的重边,这样就可以跑SPFA了。
初始将所有人放在城市1,并且每跑一次就要更新一次边集

SPFA总共跑\(n\)次,每次至多移动\(K\)个人所在的城市,故时间复杂为\(O(nk^3+nk\log n)\)

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const int N = 1e5, K = 10;
struct node {
	int x, v;
	node(int _v = 0, int _x = 0) { x = _x, v = _v; }
	bool operator<(const node& ots)const { return v < ots.v; }
};
struct S1 {
#define ls (p<<1)
#define rs (p<<1|1)
#define fa (p>>1)
	node Q[N + 10];
	size_t tot;
	S1() { tot = 0; }
	void push(node x) {
		Q[++tot] = x;
		size_t p = tot;
		while (fa && Q[p] < Q[fa])	swap(Q[p], Q[fa]), p = fa;
	}
	node top() { return Q[1]; }
	void pop() {
		Q[1] = Q[tot--];
		size_t p = 1, son;
		while (ls <= tot) {
			if (rs > tot || Q[ls] < Q[rs])	son = ls;
			else	son = rs;
			if (Q[son] < Q[p])	swap(Q[p], Q[son]), p = son;
			else	break;
		}
	}
	bool empty() { return !tot; }
#undef ls
#undef rs
#undef fa
}Edge[K + 10][K + 10];
int n, k;
int C[N + 10][K + 10], limit[K + 10], IN[N + 10];
int Dis[K + 10], Frm[K + 10];
bool Vis[K + 10];
int SPFA(int S) {
	memset(Vis, 0x00, sizeof(Vis));
	memset(Frm, 0x00, sizeof(Frm));
	memset(Dis, 0x3f, sizeof(Dis));
	queue<int>H;
	H.push(S), Dis[S] = 0, Vis[S] = 1;
	while (!H.empty()) {
		int x = H.front();
		H.pop();
		for (int y = 1; y < S; y++) {
			if (Edge[x][y].empty())	continue;
			int V = Edge[x][y].top().v;
			if (Dis[y] > Dis[x] + V) {
				Dis[y] = Dis[x] + V;
				Frm[y] = x;
				if (!Vis[y]) {
					Vis[y] = 1;
					H.push(y);
				}
			}
		}
		Vis[x] = 0;
	}
	vector<int>temp;
	int x = 1;
	while (x != S) {
		temp.push_back(Edge[Frm[x]][x].top().x);
		IN[Edge[Frm[x]][x].top().x] = Frm[x];
		x = Frm[x];
	}
	for (auto p : temp)
		for (int i = 1; i <= S; i++)
			if (i != IN[p])
				Edge[i][IN[p]].push(node(C[p][i] - C[p][IN[p]], p));
	return Dis[1];
}
int main() {
	//	freopen(".in","r",stdin);
	//	freopen(".out","w",stdout);
	n = read(0), k = read(0);
	for (int i = 1; i <= k; i++)	limit[i] = read(0);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= k; j++)
			C[i][j] = read(0);
	ll Total = 0;
	for (int i = 1; i <= n; i++)
		Total += C[i][IN[i] = 1];
	for (int j = 2; j <= k; j++) {
		for (int i = 1; i <= n; i++)
			Edge[j][IN[i]].push(node(C[i][j] - C[i][IN[i]], i));
		int temp = limit[j];
		while (temp--) {
			for (int x = 1; x <= j; x++)
				for (int y = 1; y <= j; y++)
					while (!Edge[x][y].empty() && IN[Edge[x][y].top().x] != y)
						Edge[x][y].pop();
			Total += SPFA(j);
		}
	}
	printf("%lld\n", Total);
	return 0;
}

C.Concatenation

给定\(n\)个字符串,求将它们拼接起来的字典序最小


直接排序,注意cmp判断函数里面带引用,以及先判断首字母再进行STL自带比较

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const int N = 2e6;
string s[N + 10];
bool cmp(const string& a,const string& b) { return a[0] != b[0] ? a[0] < b[0] : a + b < b + a; }
int main() {
	//	freopen(".in","r",stdin);
	//	freopen(".out","w",stdout);
	int n = read(0);
	for (int i = 1; i <= n; i++)	cin >> s[i];
	sort(s + 1, s + 1 + n, cmp);
	for (int i = 1; i <= n; i++)
		printf("%s", s[i].c_str());
	return 0;
}

D.Directed

给定一棵\(n\)个节点的树和一个起点\(s\),1号节点为终点,随机选择\(k\)条边变成单向边,问从起点开始,在树上随机游走到达终点的期望步数?

首先不考虑反向边,我们设\(F_u\)表示节点\(u\)随机走到其父亲的期望步数,可以得到式子:\(F_u=\frac{1}{d_u}+\sum\limits_{u\rightarrow v}\frac{1+F_v+F_u}{d_u}\),其中\(d_u\)表示点\(u\)的度数

移项可得\(F_u=d_u+\sum\limits_{u\rightarrow v}F_v\),考虑递归套入这个式子,可以发现除了其到父亲的边之外,每条边都被计算了两次,不难得出\(F_u=2\times sz_u-1\)

考虑单向边的影响,可以发现一条单向边\(<u,Fa_u>\)是不会对其子树造成任何影响的,但是它会使其所有祖先的\(F'\)减少\(2\times sz_u\)。换而言之,一个点\(y\)对祖先\(x\)有贡献当且仅当它们之间不存在单向边,如果\(x,y\)之间的距离为\(l\),则\(y\)\(F_x\)的贡献为\(\binom{n-i-1}{k}/\binom{n-1}{k}\)

此外,注意到我们要统计的是\(s\)到1路径上所有点的\(F\)值之和,可以发现对于树上任意一个点而言,其对祖先的贡献在答案所求的路径上均为连续的一整段,故我们可以对这一整段连续的贡献进行与预处理,然后区间求和即可。

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const int N = 1e6, P = 998244353;
int pre[(N << 1) + 10], now[N + 10], child[(N << 1) + 10], tot;
int Deep[N + 10], F[N + 10];
void link(int x, int y) { pre[++tot] = now[x], now[x] = tot, child[tot] = y; }
void connect(int x, int y) { link(x, y), link(y, x); }
void Dfs(int x, int fa) {
	Deep[x] = Deep[F[x] = fa] + 1;
	for (int p = now[x]; p; p = pre[p]) {
		int son = child[p];
		if (son == fa)	continue;
		Dfs(son, x);
	}
}
bool Vis[N + 10];
int Inv[N + 10], S[N + 10], Ans, L, R;
int solve(int x, int y) { return y ? (S[x + y - 1] - S[y - 1] + P) % P : S[x + y - 1]; }
void Dfs(int x) {
	if (Deep[x])
		Ans = (Ans + solve(L, R)) % P;
	for (int p = now[x]; p; p = pre[p]) {
		int son = child[p];
		if (son == F[x])	continue;
		L += Vis[son], R += Vis[son] ^ 1;
		Dfs(son);
		L -= Vis[son], R -= Vis[son] ^ 1;
	}
}
int main() {
	//	freopen(".in","r",stdin);
	//	freopen(".out","w",stdout);
	int n = read(0), k = read(0), s = read(0);
	for (int i = 1; i < n; i++) {
		int x = read(0), y = read(0);
		connect(x, y);
	}
	Deep[0] = -1;
	Dfs(1, 0);
	for (int x = s; x; x = F[x])	Vis[x] = 1;
	Inv[0] = Inv[1] = S[0] = 1;
	for (int i = 2; i <= n; i++)	Inv[i] = 1ll * (P - P / i) * Inv[P % i] % P;
	for (int i = 1; i <= n; i++)	S[i] = 1ll * S[i - 1] * (n - i - k) % P * Inv[n - i] % P;
	for (int i = 1; i <= n; i++)	S[i] = (S[i] + S[i - 1]) % P;
	Dfs(1);
	Ans = (2ll * Ans - Deep[s] + P) % P;
	printf("%d\n", Ans);
	return 0;
}

E.Electrician

给定一个圆,圆上有\(n\)个等距点,和\(m\)条等长的弦。现在要在圆上铺设两条电线,一条从1到\(n\),另一条从2到\(n-1\),电线可以沿着圆环铺设,也可以沿着弦铺设,在弦与弦相交的地方也可以转到另一根弦上铺设。问有多少种方案使得电线铺设不相交?


容斥+dp,咕咕咕

F.Fief

给定一个无向图,每次询问两点\(x,y\),问是否存在一个\(n\)的排列,使得第一个元素为\(x\),最后一个元素是\(y\),且排列的任意一个前缀、后缀都在图中连通


考虑化简问题,对于树上问题而言,有解当且仅当图为一条链,且询问两点为链的端点

对于一般图而言,有解当且仅当建出圆方树后方点构成一条链,询问两点位于两个方点端点,且两个点不能是割点

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const int N = 1e5, M = 2e5;
int pre[(M << 1) + 10], now[N + 10], child[(M << 1) + 10], tot;
int Dfn[N + 10], Low[N + 10], stack[N + 10], Cnt[N + 10], Time, top, Number, Root;
void link(int x, int y) { pre[++tot] = now[x], now[x] = tot, child[tot] = y; }
void connect(int x, int y) { link(x, y), link(y, x); }
vector<int>bcc[N + 10];
bool Vis[N + 10];
void Tarjan(int x, int fa) {
	Dfn[x] = Low[x] = ++Time;
	stack[++top] = x;
	if (x == Root && !now[x]) {
		bcc[++Number].push_back(x);
		return;
	}
	int cnt = 0;
	for (int p = now[x]; p; p = pre[p]) {
		int son = child[p];
		if (!Dfn[son]) {
			Tarjan(son, x);
			Low[x] = min(Low[x], Low[son]);
			if (Dfn[x] <= Low[son]) {
				Number++, cnt++;
				if (x != Root || cnt > 1)	Vis[x] = 1;
				bcc[Number].push_back(x);
				while (stack[top] != son)
					bcc[Number].push_back(stack[top--]);
				bcc[Number].push_back(stack[top--]);
			}
		}
		else Low[x] = min(Low[x], Dfn[son]);
	}
}
struct Edge {
	int x, y;
	Edge(int _x = 0, int _y = 0) :x(_x), y(_y) {}
};
int Deg[M + 10];
int main() {
	//	freopen(".in","r",stdin);
	//	freopen(".out","w",stdout);
	int n = read(0), m = read(0);
	vector<Edge>edges;
	for (int i = 1; i <= m; i++) {
		int x = read(0), y = read(0);
		connect(x, y);
	}
	int res = 0;
	for (int i = 1; i <= n; i++) {
		if (!Dfn[i]) {
			Root = i;
			Tarjan(i, 0), res++;
		}
	}
	int _N = Number;
	for (int i = 1; i <= n; i++)
		if (Vis[i]) 
			Cnt[i] = ++_N;
	for (int i = 1; i <= Number; i++) {
		for (auto p : bcc[i]) {
			if (Vis[p])
				Deg[i]++, Deg[Cnt[p]]++;
			else
				Cnt[p] = i;
		}
	}
	bool Flag = (res == 1);
	for (int i = 1; i <= _N; i++)
		Flag &= Deg[i] <= 2;
	int q = read(0);
	for (int i = 1; i <= q; i++) {
		int x = Cnt[read(0)], y = Cnt[read(0)];
		if (!Flag) {
			printf("NO\n");
			continue;
		}
		if (!Deg[x] || !Deg[y]) {
			printf("YES\n");
			continue;
		}
		if (Deg[x] == 1 && Deg[y] == 1 && x != y) {
			printf("YES\n");
			continue;
		}
		printf("NO\n");
	}
	return 0;
}

G.Geometry

给定二维平面中两个凸包,问他们相撞的时刻


计算几何难难,咕咕咕

H.Hacker

给定一个长为\(n\)的主串\(s\),再给定\(m\)个数\(V_i\),然后给定\(k\)个长为\(m\)的匹配串\(t_i\),对于每个\(t_i\),若其子串\(t_i[l...r]\)\(s\)的子串,则记\(C_{l,r}=\sum\limits_{j=l}^rV_j\),求每个串\(t_i\)\(C_{l,r}\)的最大值


考虑对主串建SAM,对每个模式串在SAM上进行匹配,对于\(t_i\)的位置\(j\)而言,记其匹配节点在SAM上为\(p\),则其所能匹配的最大长度为\(F_p\rightarrow len\)\(F_p\)为其在SAM上的父节点

求出最长匹配长度后,再用线段树求一次区间最大连续子段和即可

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_set>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const int N = 1e5;
int V[N + 10];
struct ST {
#define ls (p<<1)
#define rs (p<<1|1)
	struct node {
		ll l, r, now, sum;
		node(ll _l = 0, ll _r = 0, ll _n = 0, ll _s = 0) { l = _l, r = _r, now = _n, sum = _s; }
		void init(ll v) { l = r = now = max(0ll, sum = v); }
		friend node operator+(const node& A, const node& B);
	};
	friend node operator +(const node& A, const node& B) {
		node C;
		C.l = max(A.l, A.sum + B.l);
		C.r = max(A.r + B.sum, B.r);
		C.now = max(max(A.now, B.now), A.r + B.l);
		C.sum = A.sum + B.sum;
		return C;
	}
	node Tree[(N << 2) + 10];
	void update(int p) { Tree[p] = Tree[ls] + Tree[rs]; }
	void build(int p, int l, int r) {
		if (l == r) {
			Tree[p].init(V[l]);
			return;
		}
		int mid = (l + r) >> 1;
		build(ls, l, mid);
		build(rs, mid + 1, r);
		update(p);
	}
	node Query(int p, int l, int r, int L, int R) {
		if (L <= l && r <= R)	return Tree[p];
		int mid = (l + r) >> 1;
		node res = 0;
		if (L <= mid)	res = res + Query(ls, l, mid, L, R);
		if (R > mid)	res = res + Query(rs, mid + 1, r, L, R);
		return res;
	}
#undef ls
#undef rs
}st;//segment tree
struct SAM {
	struct node {
		int maxl, fa, son[26];
		node() {
			maxl = fa = 0;
			memset(son, 0, sizeof(son));
		}
		node operator&=(const node& ots) {
			maxl = ots.maxl, fa = ots.fa;
			memcpy(son, ots.son, sizeof(ots.son));
			return *this;
		}
	}step[(N << 1) + 10];
	int root, last, tot;
	SAM() { root = last = tot = 1; }
	void insert(int ch) {
		int p = last, np = ++tot;
		step[np].maxl = step[p].maxl + 1;
		while (p && !step[p].son[ch]) {
			step[p].son[ch] = np;
			p = step[p].fa;
		}
		if (!p)	step[np].fa = root;
		else {
			int q = step[p].son[ch];
			if (step[q].maxl != step[p].maxl + 1) {
				int nq = ++tot;
				step[nq] = step[q];
				step[nq].maxl = step[p].maxl + 1;
				step[np].fa = step[q].fa = nq;
				while (p && step[p].son[ch] == q) {
					step[p].son[ch] = nq;
					p = step[p].fa;
				}
			}
			else	step[np].fa = q;
		}
		last = np;
	}
	ll solve(char* s, int m) {
		int len = strlen(s + 1), p = root, L = 0;
		ll Ans = 0;
		for (int i = 1; i <= len; i++) {
			while (!step[p].son[s[i] - 'a'] && p != root)
				p = step[p].fa, L = step[p].maxl;
			if (step[p].son[s[i] - 'a'])
				p = step[p].son[s[i] - 'a'], L++;
			Ans = max(Ans, st.Query(1, 1, m, i - L + 1, i).now);
		}
		return Ans;
	}
}sam;
char s[N + 10];
int main() {
	int n = read(0), m = read(0), k = read(0);
	UNUSED(k);
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++)	sam.insert(s[i] - 'a');
	for (int i = 1; i <= m; i++)
		V[i] = read(0);
	st.build(1, 1, m);
	for (int i = 1; i <= k; i++) {
		scanf("%s", s + 1);
		printf("%lld\n", sam.solve(s, m));
	}
	return 0;
}

I.Ice Drinking

\(x\)为一个随机\(n\)阶排列中\(a_i=i\)的个数,求\(x^k\)的期望值


数论难难,咕咕咕

J.Journey

一个城市中有若干个十字路口,右转不需要等红灯,直行左转掉头都需要等红灯,求起点到终点最少需要等多少次红灯?


其实是个裸的最短路,但本题建图的方式稍有区别

本题为路口编号,道路则由\(<a,b>\)的形式给出。故我们将道路看做点,点与点之间的权值为是否需要等红灯,只要将图建好即可跑最短路了

/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<unordered_set>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
	int f = 1; char ch = getchar();
	for (; ch < '0' || ch>'9'; ch = getchar())	if (ch == '-')	f = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())	x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}
inline void print(int x) {
	if (x < 0)	putchar('-'), x = -x;
	if (x > 9)	print(x / 10);
	putchar(x % 10 + '0');
}
const ll N = 5e9;
template<typename T>
class MyArray {
private:
	T* S;
	int Msize;
	void expandSize();
public:
	MyArray(int size = 1) { S = new T[Msize = size]; }
	~MyArray() { delete[] S; }
	MyArray(const MyArray& tis) = delete;
	MyArray& operator=(const MyArray& tis);
	bool operator !()const { return !Msize; }
	T& operator [](int index);
};
template<typename T>
void MyArray<T>::expandSize() {
	T* temp = S;
	S = new T[Msize << 1];
	for (int i = 0; i < Msize; i++)
		S[i] = temp[i];
	Msize <<= 1;
	delete[] temp;
}
template<typename T>
MyArray<T>& MyArray<T>::operator=(const MyArray& tis) {
	S = new T[Msize = tis.Msize];
	for (int i = 0; i < tis.Msize; i++)
		S[i] = tis.S[i];
	return *this;
}
template<typename T>
T& MyArray<T>::operator [](int index) {
	while (index >= Msize)
		expandSize();
	return S[index];
}

MyArray<int>pre, now, child, val;
int tot;
void link(int x, int y, int v) { pre[++tot] = now[x], now[x] = tot, child[tot] = y, val[tot] = v; }
unordered_map<int, int>Dis;
unordered_map<ll, int>Point;
unordered_map<int, bool > Vis;
struct node {
	int x, v;
	node(int _x = 0, int _v = 0) { x = _x, v = _v; }
	bool operator <(const node& ots)const { return v > ots.v; }
};
void Dijkstra(int S, int T) {
	priority_queue<node>Heap;
	Heap.push(node(S, Dis[S] = 0));
	while (!Heap.empty()) {
		node Now = Heap.top(); Heap.pop();
		if (Vis[Now.x])	continue;
		Vis[Now.x] = 1;
		for (int p = now[Now.x]; p; p = pre[p]) {
			int son = child[p];
			if (Dis[son] > Dis[Now.x] + val[p]) {
				Dis[son] = Dis[Now.x] + val[p];
				Heap.push(node(son, Dis[son]));
			}
		}
	}
	printf("%d\n", Dis[T] >= iMax ? -1 : Dis[T]);
}
int main() {
	//	freopen(".in","r",stdin);
	//	freopen(".out","w",stdout);
	int n = read(0), cnt = 0;
	for (int i = 1; i <= n; i++) {
		vector<int>c(4, 0);
		for (int k = 0; k < 4; k++) {
			c[k] = read(0);
			if (c[k]) {
				if (!Point[N * i + c[k]]) {
					Point[N * i + c[k]] = ++cnt;
					now[cnt] = 0;
				}
				if (!Point[N * c[k] + i]) {
					Point[N * c[k] + i] = ++cnt;
					now[cnt] = 0;
				}
			}
		}
		for (int a = 0; a < 4; a++) {
			for (int b = 0; b < 4; b++) {
				if (!c[a] || !c[b])	continue;
				if ((a + 1) % 4 != b)
					link(Point[N * c[a] + i], Point[N * i + c[b]], 1);
				else
					link(Point[N * c[a] + i], Point[N * i + c[b]], 0);
			}
		}
	}
	int s1 = read(0), s2 = read(0), t1 = read(0), t2 = read(0);
	int S = Point[N * s1 + s2], T = Point[N * t1 + t2];
	for (int i = 1; i <= cnt; i++)	Dis[i] = iMax;
	Dijkstra(S, T);
	return 0;
}
posted @ 2022-08-13 01:08  Wolfycz  阅读(40)  评论(0编辑  收藏  举报