2024.09.14模拟赛总结

$ T1 $

似乎是签到题,但是没开 $ unsigned $ $ long $ $ long $ 挂成 $ 88 $ 分了。
直接模拟即可,从后往前考虑,将每个数放到离其最近的位置,不过不会证...

#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long LL;

const int N = 1000010;

struct wasd
{
	int ct, v;
} unq[N];
int n, a[N], b[N];
LL res;
map<int, bool> m;
map<int, int> ctn;
vector<LL> ans;

bool cmp(LL a, LL b)
{
	return a > b;
}

int main()
{
	freopen("openhook.in", "r", stdin);
	freopen("openhook.out", "w", stdout);
	
//	cout << mod << '\n';
	scanf("%lld", &n);
	for (int i = 1; i <= n; i ++ ) scanf("%d", a + i);
	for (int i = 1; i <= n; i ++ ) scanf("%d", b + i);
	
	sort(b + 1, b + n + 1);
	sort(a + 1, a + 1 + n);
	int cnt = 0;
	for (int i = 1; i <= n; i ++ )
	{
		if (m.count(a[i])) 
		{
			unq[cnt].ct ++ ;
			continue;
		}
		unq[ ++ cnt].v = a[i];
		unq[cnt].ct = 1;
		m[a[i]] = 1;
	}
	
	int lst_pos = unq[cnt].v + 1;
	for (int j = 1; j < unq[cnt].ct; j ++ )
		ans.push_back(lst_pos - unq[cnt].v), lst_pos ++ ;
	for (int i = cnt; i > 1; i -- )
	{
		ctn[unq[i].v] = lst_pos;
//		cout << unq[i].v << ' ' << lst_pos << "wasn\n";
		lst_pos = unq[i - 1].v + 1;
		while (ctn.count(lst_pos)) lst_pos = ctn[lst_pos];
		for (int j = 1; j < unq[i - 1].ct; j ++ )
		{
//			cout << unq[i - 1].v << ' ' << lst_pos << '\n';
			ans.push_back(lst_pos - unq[i - 1].v);
			lst_pos ++ ;
			while (ctn.count(lst_pos)) lst_pos = ctn[lst_pos];
		}
	}
	int ct = 0;
	sort(ans.begin(), ans.end(), cmp);
	for (auto x : ans) res += x * (LL)b[ ++ ct];
	
	cout << res << '\n';
	
	return 0;
} 
/*
10
1 1 1 1 3 5 5 6 7 7 
1919810 1919810 1919810 1919810 1919810 1919810 1919810 1919810 1919810 1919810 

1 4
3 1
5 2
6 1
7 2

1 + 3 + 1 + 3 + 9
*/

$ T2 $

赛时基本上没有想,毕竟有关 $ mex $ 的题做得还是太少了。
只想到了一个 $ O(n ^ 3) $ 的做法:设 $ f[i][mex] $ 表示前 $ i $ 个数划分时,每段 $ mex $ 为 $ mex $ 的方案数。
大概是因为没有发现性质。

注意到一个性质:
整个数组的 $ mex $ 应当是要等于每个划分段的 $ mex $ 的。
设划分段 $ mex $ 都为 $ X $,整个数组的 $ mex $ 为 $ Y $。
那么 $ 0 \sim Y - 1 $ 应该都在数组中出现过了。
所以可以得出 $ X \ge Y $,再考虑 $ X > Y $ 的情况。
因为整个数组的 $ mex $ 为 $ Y $,那么 $ Y $ 一定没有出现过,所以 $ X $ 只能是 $ Y $。
得到了这个性质,不难想到 DP。
设 $ f_i $ 表示将数组前 $ i $ 位划分的方案数。
状态转移方程即为

$ f_i = \sum_{j = 1}^{i - 1} f_{j - 1} (mex[j, i] = Y)$

然后发现在这个过程中,$ j $ 只会向右移,于是可以用双指针 $ + $ 前缀和优化,是时间复杂度降到 $ O(n) $。

由于是前缀和,所以答案为 $ f_n - f_{n - 1} $。

#include <bits/stdc++.h>
#define lbw 37000000
#define thr 300010

using namespace std;

const int N = 37000010, mod = 1e9 + 7;

int n, a[N], cnt[N];
int f[N], mex;

signed main()
{
	freopen("clods.in", "r", stdin);
	freopen("clods.out", "w", stdout);
	
	int T;
	cin >> T;
	while (T -- )
	{
		cin >> n;
		if (n != lbw)
			for (int i = 1; i <= n; i ++ ) cin >> a[i];
		else 
		{
			int x, y;
			cin >> x >> y;
			a[1] = 0;
			for (int i = 2; i <= n; i ++ )
				a[i] = (a[i - 1] * x + y + i) & 262143;
		}
		
		f[0] = 1;
		for (int i = 0; i <= min(thr, n); i ++ ) cnt[i] = 0;
		for (int i = 1; i <= n; i ++ )
			cnt[a[i]] ++ ;
		mex = 0;
		while (cnt[mex] > 0) mex ++ ;
		for (int i = 0; i <= min(thr, n); i ++ ) cnt[i] = 0;
		int nw = 0, pre = -1;
		for (int i = 1, j = 0; i <= n; i ++ )
		{
			pre = -1;
			cnt[a[i]] ++ ;
			while (cnt[nw] > 0) nw ++ ;
			while (nw >= mex && j <= i)
			{
				j = max(j, 1);
				cnt[a[j]] -- ;
				if (!cnt[a[j]] && a[j] < nw) pre = nw, nw = a[j];
				j ++ ;
			}
			if (~pre)
			{
				j -- ;
				cnt[a[j]] ++ ;
				nw = pre;
			}
			f[i] = f[i - 1];
			if (j > 0) (f[i] += f[j - 1]) %= mod;
		}
		
		cout << (f[n] - f[n - 1] + mod) % mod << '\n';
	}
	
	return 0;
}

$ T3 $

赛时本来想写暴力的,但是没写对。

rfy 似乎之前讲过,但是听完忘了,也没做。
做法是 Kruskal 重构树,每条边以相连的点较小的编号为边权,建出 Kruskal 重构树。
然后对于题目中的要求就可以转化为点权为 $ x $ 的点,需是编号为 $ y $ 的点的祖先。
对于第二个条件,将上面建第一棵树的方法反一下即可,也可以转化为点权为 $ y $ 的点,需是编号为 $ x $ 的点的祖先。
然后考虑如何统计满足条件的 $ [x, y] $ 的对数。
一种常用技巧是先求出第二棵树的 $ dfn $ 序,然后在遍历第一棵树到一个非叶子结点时,在树状数组上加一,最后离开时减去即可,统计就是树状数组求和,一个节点的子树范围为 $ dfn_u \sim dfn_u + siz_u - 1 $。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 4000010, M = N;

LL tr[N];
int n;
LL res;
int mp[N];
int h1[N], e1[M], ne1[M], idx1;
int h2[N], e2[M], ne2[M], idx2;
int dfn[N], time_stamp, siz[N];
struct Edge
{
	int a, b, c;
} edge[N], edge1[N];
int p[N], p1[N], w[N], w1[N];

int find(int x)
{
	return p[x] == x ? x : p[x] = find(p[x]);
}

int find1(int x)
{
	return p1[x] == x ? x : p1[x] = find1(p1[x]);
}

int lowbit(int x)
{
	return x & -x;
}

void update(int x, int v)
{
	for (int i = x; i <= 2 * n - 1; i += lowbit(i)) tr[i] += v;
}

LL query(int x)
{
	LL res = 0;
	for (int i = x; i; i -= lowbit(i)) res += tr[i];
	return res;
}

void add1(int a, int b)
{
	e1[idx1] = b, ne1[idx1] = h1[a], h1[a] = idx1 ++ ;
}

void add2(int a, int b)
{
	e2[idx2] = b, ne2[idx2] = h2[a], h2[a] = idx2 ++ ;
}

bool cmp(Edge a, Edge b)
{
	return a.c > b.c;
}

bool cmp1(Edge a, Edge b)
{
	return a.c < b.c;
}

void dfs1(int u, int fa)
{
	dfn[u] = ++ time_stamp;
	siz[u] = 1;
	for (int i = h2[u]; ~i; i = ne2[i])
	{
		int j = e2[i];
		if (j == fa) continue;
		dfs1(j, u);
		siz[u] += siz[j];
	}
}

void dfs2(int u, int fa)
{
	bool fuck = 1;
	for (int i = h1[u]; ~i; i = ne1[i])
	{
		int j = e1[i];
		if (j == fa) continue;
		fuck = 0;
	}
	if (!fuck)
	{
		if (!mp[dfn[w[u]]]) update(dfn[w[u]], 1);
		mp[dfn[w[u]]] ++ ;
	}
	for (int i = h1[u]; ~i; i = ne1[i])
	{
		int j = e1[i];
		if (j == fa) continue;
		dfs2(j, u);
	}
	if (fuck) res += query(dfn[w1[u]] + siz[w1[u]] - 1) - query(dfn[w1[u]] - 1) - query(dfn[u]) + query(dfn[u] - 1);
	if (!fuck)
	{
		if (mp[dfn[w[u]]] == 1) update(dfn[w[u]], -1);
		mp[dfn[w[u]]] -- ;
	}
}

signed main()
{
	freopen("charity.in", "r", stdin);
	freopen("charity.out", "w", stdout);
	
	memset(h1, -1, sizeof h1);
	memset(h2, -1, sizeof h2);
	scanf("%d", &n);
	for (int i = 1; i <= 2 * n - 1; i ++ ) p[i] = p1[i] = i;
	for (int i = 1; i <= n; i ++ )
	{
		int x;
		scanf("%d", &x);
		if (!x) continue;
		edge[i - 1] = {i, x, min(i, x)};
		edge1[i - 1] = {i, x, max(i, x)};
	}
	
	sort(edge + 1, edge + n, cmp);
	sort(edge1 + 1, edge1 + n, cmp1);
	int cnt = n;
	for (int i = 1; i <= n; i ++ ) w[i] = i;
	for (int i = 1; i < n; i ++ )
	{
		int x = edge[i].a, y = edge[i].b;
		x = find(x), y = find(y);
		p[x] = p[y] = ++ cnt;
		w[cnt] = edge[i].c;
		add1(cnt, x), add1(cnt, y);
	}
	cnt = n;
	for (int i = 1; i <= n; i ++ ) w1[i] = i;
	for (int i = 1; i < n; i ++ )
	{
		int x = edge1[i].a, y = edge1[i].b;
		x = find1(x), y = find1(y);
		p1[x] = p1[y] = ++ cnt;
		w1[edge1[i].c] = cnt;
		add2(cnt, x), add2(cnt, y);
	}
	dfs1(2 * n - 1, -1);
	dfs2(2 * n - 1, -1);
	
	cout << res << '\n';
	
	return 0;
}


$ 2024.09.15 $ 初赛模拟

$ 69 $ 分
感觉有点超纲,考到了网络流、虚树什么的。
前面单项选择的话,错的有点多,特别是有关期望、组合计数的题目,基本上是错的。
然后阅读程序,没看得很懂,错的也比较多。
最后是完善程序,感觉难度正常,毕竟给出了解题思路,而且有很多地方都有提示到,只错了一题。

posted @ 2024-09-14 13:25  MafuyuQWQ  阅读(44)  评论(0)    收藏  举报