“蔚来杯”2022牛客暑假多校训练营部分题解 2

6A. Array

题目大意

  • 给定长度为 n n n 的数组 a a a,要求构造一个长度为 m m m 的环,满足环上填有数字 1 ∼ n 1 \sim n 1n,且相邻的 i i i 之间的距离小于等于 a i a_i ai(若只有一个 i i i 距离视作 m m m)。
  • 1 ≤ n ≤ 1 0 5 , 2 ≤ a i ≤ 2 × 1 0 5 , ∑ i = 1 n 1 a i ≤ 1 2 1 \le n \le 10^5, 2\le a_i\le 2\times 10^5, \sum \limits_{i = 1}^{n}\frac{1}{a_i}\le\frac{1}{2} 1n105,2ai2×105,i=1nai121 m m m 可任取但需满足 1 ≤ m ≤ 1 0 6 1 \le m \le 10^6 1m106

题解

  • 实际上 ∑ i = 1 n 1 a i ≤ 1 2 \sum \limits_{i = 1}^{n}\frac{1}{a_i}\le \frac{1}{2} i=1nai121 这个怪异的条件才是问题的突破口
    ∑ i = 1 n 1 a i ≤ 1 2 ⇔ ∑ i = 1 n m a i 2 ≤ m \sum \limits_{i = 1}^{n}\frac{1}{a_i}\le \frac{1}{2} \Leftrightarrow \sum \limits_{i = 1}^{n}\frac{m}{\frac{a_i}{2}}\le m i=1nai121i=1n2aimm
  • d i d_i di 为小于等于 a i a_i ai 的最大的 2 的幂次,则
    ∑ i = 1 n m d i ≤ ∑ i = 1 n m a i 2 ≤ m \sum \limits_{i = 1}^{n}\frac{m}{d_i}\le\sum\limits_{i =1}^{n}\frac{m}{\frac{a_i}{2}}\le m i=1ndimi=1n2aimm
  • 考虑收紧问题的限制,要求相邻的 i i i 之间的距离小于 d i d_i di,同时取 m m m d i d_i di 中的最大值。
  • d i d_i di 从小到大排序依次填入,记此次填入的数字为 i i i,每次找到第一个未填入数字的位置 p o s i pos_i posi,将 p o s i + t d i ( t ∈ N + ) pos_i + td_i(t \in \mathbb N_{+}) posi+tdi(tN+) 全部填成 i i i
  • 由不等式 ∑ i = 1 n m d i ≤ m \sum \limits_{i = 1}^{n}\frac{m}{d_i}\le m i=1ndimm 可知填数字的位置是足够的,我们只需要证明填数字时不会产生冲突的情况。
  • 假设存在冲突的情况,有 d i = t 3 d j ( t 3 ∈ N + ) , p o s j < p o s i d_i=t_3d_j(t_3\in \mathbb N_{+}),pos_j < pos_i di=t3dj(t3N+),posj<posi,则存在下式
    p o s j + t 1 d j = p o s i + t 2 d i ⇔ p o s j + ( t 1 − t 2 t 3 ) d j = p o s i ( t 1 , t 2 ∈ N + ) pos_j + t_1 d_j = pos_i+t_2d_i \Leftrightarrow pos_j + (t_1 - t_2t_3)d_j = pos_i(t_1,t_2\in\mathbb N_{+}) posj+t1dj=posi+t2diposj+(t1t2t3)dj=posi(t1,t2N+)
  • p o s i pos_i posi 一定会被先填为 j j j,与假设矛盾,正确性得证。
#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

template <class T>
inline void _put(T x)
{
	if (x < 0)
		x = -x, putchar('-');
	put(x);
}

template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {x < y ? x = y : 0;}
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}
template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}
template <class T>
inline T Sqr(T x) {return x * x;}

using std::map;
using std::set;
using std::pair;
using std::bitset;
using std::string;
using std::vector;
using std::multiset;
using std::priority_queue;

typedef long long ll;
typedef long double ld;
const ld pi = acos(-1.0);
const ld eps = 1e-8;
const int N = 1e5 + 5;
const int M = 1e6 + 5;
const int Maxn = 1e9;
const int Minn = -1e9;
const int mod = 998244353;
int T_data, n, m, x;
int ans[M];
pair<int, int> d[N];

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline void dec(int &x, int y)
{
	x -= y;
	x < 0 ? x += mod : 0;
}

int main()
{
	read(n);
	for (int i = 1, x; i <= n; ++i)
	{
		read(x);
		d[i].first = 1;
		while ((d[i].first << 1) <= x)
			d[i].first <<= 1;
		d[i].second = i;	
	}
	std::sort(d + 1, d + n + 1);
	m = d[n].first;
	int r = 1;
	for (int i = 1; i <= n; ++i)
	{
		while (ans[r])
			++r;
		for (int j = r; j <= m; j += d[i].first)
			ans[j] = d[i].second;
	}
	for (int i = 1; i <= m; ++i)
		if (!ans[i])	
			ans[i] = 1;
	put(m), putchar('\n');
	for (int i = 1; i <= m; ++i)
		put(ans[i]), putchar(' ');
	puts("");
	return 0;
}

6C. Forest

题目大意

  • 给定一张 n n n 个点 m m m 条边的无向图,求图中所有边的子集的最小生成森林的边权和。
  • n ≤ 16 , m ≤ 100 n \le 16, m \le 100 n16,m100,边权小于等于 1 0 9 10^9 109,答案对 998244353 \text{998244353} 998244353 取模。

题解

  • 一般的状压 DP \text{DP} DP 并不能解决生成树计数类的问题。
  • 考虑计算每条边被包含在某个边集的最小生成森林内的方案数。
  • 先将边按照权值排序,相同权值按照编号排序,使任意两条边可以严格区分大小,一张图的最小生成树方案唯一。
  • 根据 Kruskal \text{Kruskal} Kruskal 算法, 一条边在某个边集的最小生成森林内,当且仅当该边集内小于它的边不能使两端点连通。
  • 考虑用总的方案数减去能使两端点连通的方案,设小于它的边形成了一个包含两端点的连通块点集 S S S
  • U U U 为点的全集, c n t S cnt_S cntS 表示点集 S S S 内部小于当前边的边数, f S f_S fS 表示点集 S S S 内部形成连通块的方案数,则上述情况对方案数的贡献为 2 c n t U − S f S 2^{cnt_{U-S}}f_S 2cntUSfS
  • 考虑 f S f_S fS 的计算,同样用总方案数减去不能形成连通块的方案数,有转移
    f S = 2 c n t S − ∑ T ⊊ S 且 T 包含 S 中编号最小的点 2 c n t S − T f T f_S = 2^{cnt_S} - \sum \limits_{T\subsetneq S 且 T 包含 S 中编号最小的点} 2^{cnt_{S-T}}f_{T} fS=2cntSTST包含S中编号最小的点2cntSTfT
  • 时间复杂度 O ( m 3 n ) \mathcal O(m3^{n}) O(m3n),具体实现时每加入一条边很多 c n t S cnt_S cntS 并没有发生改变,对应的 f S f_S fS 也无需重新计算,可直接沿用之前的结果,大约能减少一半的常数。
#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

template <class T>
inline void _put(T x)
{
	if (x < 0)
		x = -x, putchar('-');
	put(x);
}

template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {x < y ? x = y : 0;}
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}
template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}
template <class T>
inline T Sqr(T x) {return x * x;}

using std::map;
using std::set;
using std::pair;
using std::bitset;
using std::string;
using std::vector;
using std::multiset;
using std::priority_queue;

typedef long long ll;
typedef long double ld;
const ld pi = acos(-1.0);
const ld eps = 1e-8;
const int S = 1 << 16;
const int M = 105;
const int Maxn = 1e9;
const int Minn = -1e9;
const int mod = 998244353;
int pow2[M], a[20][20];
int m, T_data, n, ans;
int f[S], cnt[S]; bool vis[S];

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline void dec(int &x, int y)
{
	x -= y;
	x < 0 ? x += mod : 0;
}

inline void mul2(int &x) 
{
	x += x; 
	x >= mod ? x -= mod : 0;
}

struct edge
{
	int x, y, z;
	
	edge() {}
	edge(int X, int Y, int Z):
		x(X), y(Y), z(Z) {} 

	inline bool operator < (const edge &a) const 
	{
		if (z == a.z)
		{
			if (x == a.x)
				return y < a.y;
			else
				return x < a.x;
		}
		else
			return z < a.z;
	}
}p[M];
 
int main()
{
	read(n);
	for (int i = 0; i < n; ++i)
		for (int j = 0; j < n; ++j)
			read(a[i][j]);
	for (int i = 0; i < n; ++i)
		for (int j = i + 1; j < n; ++j)
			if (a[i][j])
				p[++m] = edge(i, j, a[i][j]);
	std::sort(p + 1, p + m + 1);
	const int C = (1 << n) - 1;
	pow2[0] = 1;
	for (int i = 1; i <= m; ++i)
		add(pow2[i] = pow2[i - 1], pow2[i - 1]);
	for (int i = 0; i <= C; ++i)
		cnt[i] = 1, vis[i] = true;
	for (int i = 1; i <= m; ++i)
	{
		int x = p[i].x, y = p[i].y,
			tx = 1 << x, ty = 1 << y,
			w = tx | ty;
		for (int s = 1; s <= C; ++s)
		{
			if (!vis[s])
				continue ;
			int &_f = f[s]; 
			_f = cnt[s];
			int low_s = s & -s, t = s ^ low_s;
			if (!t)
				continue ;
			for (int s1 = (t - 1) & t; s1; s1 = (s1 - 1) & t)
			{
				int s2 = s1 ^ low_s;
				if (f[s2])
					dec(_f, 1ll * f[s2] * cnt[s ^ s2] % mod);
			}
			if (f[low_s])
				dec(_f, 1ll * f[low_s] * cnt[t] % mod);
		}
		int num = pow2[i - 1];
		int c = C ^ w;
		for (int s = c; s; s = (s - 1) & c)
		{
			int s1 = s | w;
			if (f[s1])
				dec(num, 1ll * cnt[C ^ s1] * f[s1] % mod);	
		}
		ans = (1ll * num * pow2[m - i] % mod * p[i].z + ans) % mod;
		mul2(cnt[w]);
		vis[w] = true;
		for (int s = c; s; s = (s - 1) & c)
		{ 
			mul2(cnt[s | w]);
			vis[s | w] = true;
			vis[s] = vis[s | tx] = vis[s | ty] = false;
		}
	}
	put(ans), putchar('\n');
	return 0;
}

6I. Line

题目大意

  • 给定 n n n 个向量,构造一个大小为 m m m 的点集,满足在点集中任取一点 ( a i , b i ) (a_i,b_i) (ai,bi),对于任意一个向量 ( x j , y j ) (x_j,y_j) (xj,yj),直线 ( a i + t x j , b i + t y j ) , t ∈ R (a_i+tx_j,b_i+ty_j),t\in\mathbb R (ai+txj,bi+tyj),tR 恰好经过点集中的 d d d 个点。
  • 1 ≤ n , d ≤ 6 , 0 ≤ x j , y j ≤ 6 1 \le n,d\le 6, 0\le x_j,y_j\le 6 1n,d6,0xj,yj6 m m m 可任取但需满足 1 ≤ m ≤ 1 0 5 1 \le m \le 10^5 1m105

题解

  • 先将 n n n 个向量中共线的多余向量删除,记删除完多余向量后的向量个数为 n 0 n_0 n0,分别记为 v 1 → , v 2 → , … , v n 0 → \overrightarrow{v_1},\overrightarrow{v_2},\dots,\overrightarrow{v_{n_0}} v1 ,v2 ,,vn0

  • 可以观察到, n 0 = 1 n_0 = 1 n0=1 时答案组成的图形是一条包含 d d d 个点的线段, n 0 = 2 n_0 = 2 n0=2 时答案组成的图形是一个包含 d 2 d^2 d2 个点的平行四边形。

  • 对于任意的 n 0 n_0 n0,答案组成的图形即为包含 d n 0 d^{n_0} dn0 个点的 n 0 n_0 n0 维立方体在二维平面上的投影。

  • 每次取出一个向量,将当前所有的点沿着该向量的方向平移 d − 1 d - 1 d1 次,将得到的点加入当前点集,即可得到更高一维的立方体在二维平面上的投影。

  • 现在唯一要解决的问题就是要保证每次新产生的点不能在原来的点对形成的直线上。

  • 设这 n 0 n_0 n0 个每次平移的向量分别为 v 1 ′ → , v 2 ′ → , … , v n 0 ′ → \overrightarrow{v_1'},\overrightarrow{v_2'},\dots,\overrightarrow{v_{n_0}'} v1 ,v2 ,,vn0 ,其中 v i ′ → = ( x i ′ , y i ′ ) , x i ′ , y i ′ ∈ Z \overrightarrow{v_i'} = (x_i',y_i'),x_i',y_i'\in\mathbb Z vi =(xi,yi),xi,yiZ,考虑产生错误的情况,即
    ∃ a 1 , a 2 , … , a n 0 , ∑ i = 1 n 0 a i v i ′ → = 0 ⇔ ∑ i = 1 n 0 a i x i ′ = 0 , ∑ i = 1 n 0 a i y i ′ = 0 \exist a_1, a_2, \dots, a_{n_0}, \sum \limits_{i = 1}^{n_0}a_i\overrightarrow{v_i'} = 0 \Leftrightarrow \sum \limits_{i = 1}^{n_0}a_ix_i' =0, \sum \limits_{i = 1}^{n_0}a_iy_i' = 0 a1,a2,,an0,i=1n0aivi =0i=1n0aixi=0,i=1n0aiyi=0

  • ∑ i = 1 n 0 a i x i ′ = 0 \sum \limits_{i = 1}^{n_0}a_ix_i' = 0 i=1n0aixi=0 为例,取定 a n 0 a_{n_0} an0,其余 a i a_i ai 为未知量,该式可视为 n 0 − 1 n_0 - 1 n01 元不定方程,其无解的充分必要条件为 gcd ⁡ ( x 1 ′ , x 2 ′ , … , x n 0 − 1 ′ ) ∤ ( a n 0 x n 0 ′ ) \gcd(x_1',x_2',\dots,x_{n_0 - 1}')\not| (a_{n_0}x_{n_0}') gcd(x1,x2,,xn01)(an0xn0)

  • n 0 n_0 n0 个大于 6 的互不相同的质数 m 1 , m 2 , … , m n 0 m_1, m_2, \dots, m_{n_0} m1,m2,,mn0,令 M = ∏ i = 1 n 0 m i M = \prod\limits_{i = 1}^{n_0}m_i M=i=1n0mi M i = M m i M_i =\frac{M}{m_i} Mi=miM,取 v i ′ → = M i v i → \overrightarrow{v_i'} = M_i \overrightarrow{v_i} vi =Mivi ,容易验证其满足条件。

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

template <class T>
inline void _put(T x)
{
	if (x < 0)
		x = -x, putchar('-');
	put(x);
}

template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {x < y ? x = y : 0;}
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}
template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}
template <class T>
inline T Sqr(T x) {return x * x;}

using std::map;
using std::set;
using std::pair;
using std::bitset;
using std::string;
using std::vector;
using std::multiset;
using std::priority_queue;

typedef long long ll;
typedef long double ld;
const ld pi = acos(-1.0);
const ld eps = 1e-8;
const int N = 1e5 + 5;
const int Maxn = 1e9;
const int Minn = -1e9;
const int mod = 998244353;
int T_data, n, d;

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline void dec(int &x, int y)
{
	x -= y;
	x < 0 ? x += mod : 0;
}

const int prime[] = {7, 11, 13, 19, 23, 29};

struct point
{
	ll x, y;
	
	point() {}
	point(int X, int Y):
		x(X), y(Y) {}
	
	inline void scan()
	{
		read(x);
		read(y);
		ll z = std::__gcd(x, y);
		x /= z;
		y /= z;
	}
	
	inline point operator + (const point &a) const
	{
		return point(x + a.x, y + a.y);		
	}
	
	inline point operator - (const point &a) const
	{
		return point(x - a.x, y - a.y); 
	}
	
	inline point operator * (const ll &k)
	{
		return point(k * x, k * y);
	} 
	
	inline ll operator * (const point &a) const
	{
		return x * a.y - y * a.x;
	}
};

vector<point> a, ans, _ans;

int main()
{
	read(n); read(d);
	for (int i = 1; i <= n; ++i)
	{
		point u;
		u.scan();
		bool flag = true; 
		for (point v : a)
			if (u * v == 0)
			{
				flag = false;
				break ;
			}
		if (flag)
			a.push_back(u);
	}
	ll prod = 1;
	for (int i = 0, im = a.size(); i < im; ++i)
		prod *= prime[i];
	for (int i = 0, im = a.size(); i < im; ++i)
		a[i] = a[i] * (prod / prime[i]);
	
	ans.push_back(point(0, 0));
	for (point u : a)
	{
		_ans.clear();
		for (point v : ans)
			for (int j = 1; j < d; ++j)
				_ans.push_back(v + u * j);
		for (point v : _ans)
			ans.push_back(v);
	}
	
	put(ans.size()), putchar('\n');
	for (point u : ans)
	{
		put(u.x), putchar(' ');
		put(u.y), putchar('\n');	
	}
	return 0;
}

7I. Suffix Sort

题目大意

  • 定义一个字符串的最小表示为将从左到右第一种出现的字符全部替换为 a,第二种出现的字符串全部替换为 b,以此类推。要求将一个长度为 n n n 的字符串所有后缀的最小表示排序,按排名输出后缀起始位置。
  • 1 ≤ n ≤ 2 × 1 0 5 1 \le n \le 2\times 10^5 1n2×105

题解

  • 将问题转化为求排序时任意两个后缀的最小表示的最长公共前缀,考虑预处理出每个后缀字符的出现顺序,两个后缀的某一段前缀的最小表示相同就相当于对于任意的 i i i,两个后缀的这一段前缀内第 i i i 种出现的字符的所有出现位置均相同。
  • 为了简化比较,我们可以预处理出整个字符串每种字符出现位置的差分数组,并将其拼接在一起求出其后缀数组,并用 ST \text{ST} ST 表维护 h e i g h t height height 数组,询问时枚举 i i i,后缀内第一个出现位置需要特判,后面的出现位置即可 O ( 1 ) \mathcal O(1) O(1) 查询,最终的最长公共前缀即为所有查询得到的最长公共前缀的最小值。
  • 时间复杂度 O ( 26 n log ⁡ n ) \mathcal O(26n\log n) O(26nlogn),从实际测试情况来看,用 stable_sort 能大幅提高效率。
#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

template <class T>
inline void _put(T x)
{
	if (x < 0)
		x = -x, putchar('-');
	put(x);
}

template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {x < y ? x = y : 0;}
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}
template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}
template <class T>
inline T Sqr(T x) {return x * x;}

using std::map;
using std::set;
using std::pair;
using std::bitset;
using std::string;
using std::vector;
using std::multiset;
using std::priority_queue;

typedef unsigned int uint;
typedef long double ld;
const ld pi = acos(-1.0);
const ld eps = 1e-8;
const int N = 2e5 + 255;
const int Maxn = 1e9;
const int Minn = -1e9;
const int mod = 998244353;
const int X = 131;
int T_data, n, _t, cur1[26], cur2[26];
int ans[N], Pow[N];
int lst[26][N], cnt[26][N];
int _sa[N][26], _rank[N][26];
char s[N];
int pos[26][N], h[26][N];

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline void dec(int &x, int y)
{
	x -= y;
	x < 0 ? x += mod : 0;
}

int rank[N], height[N], sa[N], w[N], r, tn;
int f[22][N], Log[N], t[N];

inline bool Equal(int *x, int a, int b, int k)
{
	if (x[a] != x[b])
		return false;
	else
	{
		int p = a + k > tn ? -1 : x[a + k],
			q = b + k > tn ? -1 : x[b + k];
		return p == q; 
	}
}

inline void initSA()
{	
	int *x = rank, *y = height;
	r = tn + 200;
	for (int i = 1; i <= tn; ++i)
		++w[t[i]];
	for (int i = 2; i <= tn; ++i)
		w[i] += w[i - 1];
	for (int i = tn; i >= 1; --i)
		sa[w[t[i]]--] = i;
	x[sa[1]] = r = 1;
	for (int i = 2; i <= tn; ++i)
		x[sa[i]] = t[sa[i - 1]] == t[sa[i]] ? r : ++r;
	for (int k = 1; r < tn; k <<= 1)
	{
		int yn = 0;
		for (int i = tn - k + 1; i <= tn; ++i)
			y[++yn] = i;
		for (int i = 1; i <= tn; ++i)
			if (sa[i] > k)
				y[++yn] = sa[i] - k;
		
		for (int i = 1; i <= r; ++i)
			w[i] = 0;
		for (int i = 1; i <= tn; ++i)
			++w[x[y[i]]];
		for (int i = 2; i <= r; ++i)
			w[i] += w[i - 1];
		for (int i = tn; i >= 1; --i)
			sa[w[x[y[i]]]--] = y[i];
		
		std::swap(x, y); 
		x[sa[1]] = r = 1;
		for (int i = 2; i <= tn; ++i)
			x[sa[i]] = Equal(y, sa[i - 1], sa[i], k) ? r : ++r;
	} 
	for (int i = 1; i <= tn; ++i)
		rank[i] = x[i];
		
	for (int i = 1, j, k = 0; i <= tn; ++i)
	{
        if (rank[i] == 1)
            continue ;
		k ? --k : 0;
		j = sa[rank[i] - 1];
		while (i + k <= tn && j + k <= tn && t[i + k] == t[j + k]) ++k;
		height[rank[i]] = k;
	}
    height[1] = 0;

	Log[0] = -1;
	for (int i = 1; i <= tn; ++i)
		Log[i] = Log[i >> 1] + 1, f[0][i] = height[i];
	for (int j = 1, jm = Log[tn]; j <= jm; ++j)
		for (int i = 1; i + (1 << j) - 1 <= tn; ++i)
			f[j][i] = Min(f[j - 1][i], f[j - 1][i + (1 << j - 1)]);
}

inline int queryMin(int l, int r)
{
	if (l > r)
		std::swap(l, r);
	++l;
	int k = Log[r - l + 1];
	return Min(f[k][l], f[k][r - (1 << k) + 1]);
}

inline bool cmp(const int &x, const int &y)
{
	int res = Maxn;
	for (int i = 0; i < 26; ++i)
	{
		int a = _sa[x][i], 
			b = _sa[y][i];
		int px = lst[a][x],
			py = lst[b][y];
		if (px > n || py > n || px - x != py - y)
		{
			CkMin(res, Min(px - x + 1, py - y + 1));
			continue ;
		}
		int lx = cnt[a][px],
			ly = cnt[b][py];
		int tmp = queryMin(rank[h[a][lx + 1]], rank[h[b][ly + 1]]);
		if (pos[a][lx + tmp] <= n && pos[b][ly + tmp] <= n)
				++tmp;
		CkMin(res, Min(pos[a][lx + tmp] - x + 1, 
			           pos[b][ly + tmp] - y + 1));
	}
	if (res > Min(n - x + 1, n - y + 1))
		return x > y;
	else
		return _rank[x][s[x + res - 1]] < _rank[y][s[y + res - 1]];
}

inline bool cmp2(const int &x, const int &y)
{
	return lst[x][_t] < lst[y][_t];
}

int main()
{
	read(n);	
	scanf("%s", s + 1);
	for (int i = 1; i <= n; ++i)
		s[i] -= 'a';
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 0; j < 26; ++j)
			cnt[j][i] = cnt[j][i - 1];
		pos[s[i]][++cnt[s[i]][i]] = i;
	}
	for (int j = 0; j < 26; ++j)
	{	
		lst[j][n + 1] = n + 1;
		cnt[j][n + 1] = cnt[j][n] + 1;
		pos[j][cnt[j][n + 1]] = n + 1;
	}
	for (int i = n; i >= 1; --i)
	{
		for (int j = 0; j < 26; ++j)
			lst[j][i] = lst[j][i + 1];
		lst[s[i]][i] = i;
		for (int j = 0; j < 26; ++j)
			_sa[i][j] = j;
		_t = i;
		std::sort(_sa[i], _sa[i] + 26, cmp2);
		for (int j = 0; j < 26; ++j)
			_rank[i][_sa[i][j]] = j;
	}
	
	for (int i = 0; i < 26; ++i)
	{
		for (int j = 1; j <= cnt[i][n + 1]; ++j)
		{
			t[++tn] = pos[i][j] - pos[i][j - 1];
			h[i][j] = tn;
		}
		if (i < 25)		
			t[++tn] = n + i + 2; 
	}
	initSA();
	for (int i = 1; i <= n; ++i)
		ans[i] = i;
	std::stable_sort(ans + 1, ans + n + 1, cmp);
	for (int i = 1; i <= n; ++i)
		put(ans[i]), putchar(' ');
	return 0;
}

7K. Great Party

题目大意

  • n n n 堆石子,第 i i i 堆石子有 a i a_i ai 个。
  • 两个人玩游戏,轮流操作,每次依次进行以下两种操作:
    • 选择一堆石子,拿走任意数量(大于0)的石子。
    • 保持现状或将刚刚拿走石子的那堆石子并入另一堆非空的石子中。
  • 取走最后一个石子者获胜。
  • 给定 q q q 次询问,每次询问 [ l , r ] [l,r] [l,r],求 [ l , r ] [l,r] [l,r] 有多少子区间满足取出该区间内的石子玩游戏先手必胜。
  • n , q ≤ 1 0 5 , a i ≤ 1 0 6 n,q \le 10^5,a_i \le 10^6 n,q105,ai106

题解

  • 结论1 若堆数为偶数,规定谁先取完一堆谁输,则先手必胜的条件为 a i − 1 a_i - 1 ai1 的异或和不为 0,证明类似 Nim Game。
  • 结论2 若堆数为奇数,则先手必胜。
  • 只有一堆时显然成立,堆数大于 1 需证明能通过一次操作使堆数减一且剩下的石子堆 a i − 1 a_i - 1 ai1 的异或和为 0,小范围手玩取石子数最多的那一堆均能找到方案,但目前没有较好的严格证明方法。
  • 套用莫队计算答案即可。
#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

template <class T>
inline void _put(T x)
{
	if (x < 0)
		x = -x, putchar('-');
	put(x);
}

template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {x < y ? x = y : 0;}
template <class T>
inline T Min(T x, T y) {return x < y ? x : y;}
template <class T>
inline T Max(T x, T y) {return x > y ? x : y;}
template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}
template <class T>
inline T Sqr(T x) {return x * x;}

using std::map;
using std::set;
using std::pair;
using std::bitset;
using std::string;
using std::vector;
using std::multiset;
using std::priority_queue;

typedef long long ll;
typedef long double ld;
const ld pi = acos(-1.0);
const ld eps = 1e-8;
const int N = 1e5 + 5;
const int M = 2e6 + 5;
const int Maxn = 1e9;
const int Minn = -1e9;
const int mod = 998244353;
int S, T_data, n, q;
int a[N], sum[N], cnt[2][M]; 
ll fans[N], ans;

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline void dec(int &x, int y)
{
	x -= y;
	x < 0 ? x += mod : 0;
}

struct query
{
	int l, r, bl, id;
	
	inline void scan(int t)
	{
		read(l); read(r);
		++r; id = t;
		bl = (l - 1) / S; 
	}
	
	inline bool operator < (const query &a) const 
	{
		return bl < a.bl || bl == a.bl && r < a.r;
	}
}p[N];

inline void insertCol(int t, int c)
{
	ans -= 1ll * cnt[t][c] * (cnt[t][c] - 1) / 2;
	++cnt[t][c]; 
	ans += 1ll * cnt[t][c] * (cnt[t][c] - 1) / 2;
}

inline void deleteCol(int t, int c)
{
	ans -= 1ll * cnt[t][c] * (cnt[t][c] - 1) / 2;
	--cnt[t][c];
	ans += 1ll * cnt[t][c] * (cnt[t][c] - 1) / 2;
}

int main()
{
	read(n); read(q);
	S = sqrt(n + 1);
	for (int i = 1; i <= n; ++i)
	{
		read(a[i]); --a[i];
		sum[i + 1] = sum[i] ^ a[i]; 
	}
	for (int i = 1; i <= q; ++i)
		p[i].scan(i);
	std::sort(p + 1, p + q + 1);
	int tl = 1, tr = 0;
	for (int i = 1; i <= q; ++i)
	{
		int l = p[i].l, r = p[i].r;
		while (tl < l)
		{
			deleteCol(tl & 1, sum[tl]);
			++tl;
		}
		while (tl > l)
		{
			--tl;
			insertCol(tl & 1, sum[tl]);
		}
		while (tr > r)
		{
			deleteCol(tr & 1, sum[tr]);
			--tr;
		}
		while (tr < r)
		{
			++tr;
			insertCol(tr & 1, sum[tr]);
		}
		fans[p[i].id] = 1ll * (r - l + 1) * (r - l) / 2 - ans;
	}
	for (int i = 1; i <= q; ++i)
		put(fans[i]), putchar('\n');
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值