「ZJU Summer Training 2020

补题地址:https://zjusummer.contest.codeforces.com/

Content

  • ZJU-ICPC Summer 2020 Contest 1 by Group A
    • Problem A. MUG
    • Problem B. Count Angles
    • Problem F. Balloons Tower Defence
  • ZJU-ICPC Summer 2020 Contest 2 by Group B
    • Problem A. The Number of Good Intervals
    • Problem D. Pick Branches
  • ZJU-ICPC Summer 2020 Contest 3 by Group C
    • Problem A. FibTree
    • Problem C. More and more
    • Problem E. Election

ZJU-ICPC Summer 2020 Contest 1 by Group A

Date:2020/7/13

Problem A. MUG

哇这个题面真的长不翻了QAQ

U0LBo8.png

U0LrFS.png

动态规划。设 (f(i, 0/1/2)) 表示按到第 (i) 个键时,当前键不按,左手按或右手按,产出的最大分数为多少。

由题意得状态转移方程:

  • (f(i, 0) = max{ f(i - 1, 0), f(i - 1, 1), f(i - 1, 2) })
  • (t_i > t_{i - 1} ightarrow f(i, 1) = max{ f(i - 1, 0) + s_i, f(i - 1, 2) +s_i imes dfrac{ p_1}{100} })
  • (t_i le t_{i - 1} ightarrow f(i, 1) = max{ f(i - 1, 0) + s_i, f(i - 1, 2) +s_i })
  • (t_i < t_{i - 1} ightarrow f(i, 2) = max{ f(i - 1, 0) + s_i, f(i -1, 1)+s_i imesdfrac{p_2}{100}, f(i - 1, 2)+s_1 imesdfrac{p_3}{100}})
  • (t_i ge t_{i - 1} ightarrow f(i, 2) = max{ f(i - 1, 0) + s_i, f(i -1, 1)+s_i, f(i - 1, 2)+s_1 imesdfrac{p_3}{100}})

复杂度为线性。

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <algorithm>
#include <cstdio>

namespace fastIO_int {
	int get_int()
	{
		int x=0,f=1;char c=getchar();
		while(c<'0'||c>'9')
		{
			if(c=='-')f=-1;
			c=getchar();
		}
		while(c>='0'&&c<='9')
			x=x*10+c-'0',c=getchar();
		return f*x;
	}
	
	void read(){}
	template<class T1,class ...T2>
	void read(T1 &i,T2&... rest)
	{
		i=get_int();
		read(rest...);
	}
};
using fastIO_int::read;

using namespace std;
const int N = 1e6 + 5;

int p1, p2, p3;
int n;
int s[N], t[N];
long long f[N][3];

signed main() {
	read(n);
	for (register int i = 1; i <= n; i++)
		read(s[i]);
	for (register int i = 1; i <= n; i++)
		read(t[i]);
	read(p1, p2, p3);
	
	for (register int i = 1; i <= n; i++) {
		f[i][0] = max({f[i - 1][0], f[i - 1][1], f[i - 1][2]});
		
		if (t[i] > t[i - 1])
			f[i][1] = max({f[i - 1][0] + s[i], f[i - 1][2] + s[i] * p1 / 100});
		else
			f[i][1] = max({f[i - 1][0] + s[i], f[i - 1][2] + s[i]});
		
		if (t[i] >= t[i - 1])
			f[i][2] = max({f[i - 1][0] + s[i], f[i - 1][1] + s[i], f[i - 1][2] + s[i] * p3 / 100});
		else
			f[i][2] = max({f[i - 1][0] + s[i], f[i - 1][1] + s[i] * p2 / 100, f[i - 1][2] + s[i] * p3 / 100});
	}
	
	printf("%lld
", *max_element(f[n], f[n] + 3));
	return 0;
}

Problem B. Count Angles

求平面中 (n(le 10^9)) 条直线可以构成同旁内角对数的最大值,对 ((10^9 + 7)) 取模,(T(le 10^5)) 组数据。

显然当 3 条直线构成 3 个不重合的交点时同旁内角数最大为 6。

于是当 (n) 条直线不出现三线共点的情况是答案最大为 (C_n ^3 imes 6 = n(n-1)(n-2))

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <iostream>

using namespace std;
const int mod = 1e9 + 7;

signed main() {
	int T; cin >> T;
	while (T--) {
		int n; cin >> n;
		cout << n * 1ll * (n - 1) % mod * (n - 2) % mod << endl;
	}
}

Problem F. Balloons Tower Defence

给定平面上 (n(le 10^5)) 个点,判断是否存在一条直线,使该直线上有 (n imes p\%) 个点((pin[20, 100]))

考虑随机化算法:每次随机选取两个点,判断其构成的直线上的点的个数是否满足要求。

clock() 函数控制尝试次数,最后实在找不到了就跳出。

不难算出这样的误判率极低,因为 (p) 被保证不太小。

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <cstdlib>
#include <ctime>
#include <iostream>

using namespace std;
const int N = 1e5 + 5;
typedef pair<int, int> Data;

int n, p;
Data point[N];

inline bool judge(Data& x, Data& y, Data& p) {
	return (x.first - y.first) * 1ll * (p.second - x.second)
		== (p.first - x.first) * 1ll * (x.second - y.second);
}
inline int rand_int() {
	return rand() << 15 | rand();
}

signed main() {
	srand(445);
	ios::sync_with_stdio(false);
	
	cin >> n >> p;
	for (register int i = 1; i <= n; i++)
		cin >> point[i].first >> point[i].second;
	while (clock() < 980) {
		int i = rand_int() % n + 1;
		int j = rand_int() % n + 1;
		
		if (i == j) ++j;
		if (j == n + 1) j = 1;
		
		Data& x = point[i];
		Data& y = point[j];
		
		int cnt = 0;
		for (register int k = 1; k <= n; k++)
			cnt += judge(x, y, point[k]);
		
		if (cnt * 100 >= p * n) {
			cout << "possible" << endl;
			return 0;
		}
	}
	
	cout << "impossible" << endl;
	return 0;
}

ZJU-ICPC Summer 2020 Contest 2 by Group B

Date:2020/7/14

Problem A. The Number of Good Intervals

给定一个长度为 (n(le 4 imes 10^6)) 的数列 ({a_1, a_2, cdots, a_n}(a_ile 4 imes 10^4)),再给定 (m(le 2 imes 10^5)) 个询问,每次询问给出一个正整数 (b(le 4 imes 10^4)),求有多少个区间 ([l, r]),满足 (gcdlimits_{lle ile r} {a_i} = b)

当一个正整数 (x) 与另一个数 (y)(gcd) 时,即 (x leftarrow gcd(x, y)),若 (x) 较之前发生了变化,那么一定是减小了至少两倍。那么 (x) 最多改变 (log x) 次,(n) 个就是 (nlog x) 种值。

于是可以用 map 直接做,每做到一个数 (a_i),就将上一轮的 map 中的元素结合 (a_i) 更新至该轮 map 中,然后倒入答案 map。时间复杂度 (O(nlog^2 x + m))(x) 为值域。

遗憾的是 st 表做法因为时空两爆炸被卡了。 /dk

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <algorithm>
#include <iostream>
#include <map>

using namespace std;
const int N = 4e6 + 5;

typedef map<int, long long> recorder;
int a[N], n, q;
recorder ans, pre;

signed main() {
	scanf("%d", &n);
	for (register int i = 1; i <= n; i++)
		scanf("%d", a + i);
	
	for (register int i = 1; i <= n; i++) {
		recorder cur; cur[a[i]]++;
		for (auto it : pre) cur[__gcd(a[i], it.first)] += it.second;
		for (auto it : cur) ans[it.first] += it.second;
		pre.swap(cur);
	}
	
	int q; scanf("%d", &q);
	while (q--) {
		int x; scanf("%d", &x);
		printf("%lld
", ans[x]);
	}
	return 0;
}

Problem D. Pick Branches

(n imes n)棋盘上的 ((n + 1)^2) 个点,从 ((0,0)) 出发的直线,恰好通过 (m) 个点的方案数。((1le n, mle 5 imes 10^6)) 定义两个方案是不同的,当且仅当存在一个点属于方案 A 不属于方案 B。(T(le 10^5)) 组数据。

若我们的直线为 (y = dfrac{a}{b} x(a < b,gcd(a, b))),那么上面的点数为 (lfloor{dfrac{n}{a}} floor + 1)

于是答案不难表示为:(sumlimits_{i = 2}^n sumlimits_{j = 1}^{i} [gcd(i, j) = 1][lfloor{dfrac{n}{i}} floor + 1 = m])

把其中的互质换成 (varphi),可得 (sumlimits_{i = 2}^n varphi(i) [lfloor{dfrac{n}{i}} floor + 1 = m])

可以看出后面的条件实质上是连续的一段区间,于是预处理出 (varphi) 及其前缀和即可。使用线性筛可以有 (O(n + T)) 的复杂度。然而这里并没有用

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <cstdio>

using namespace std;
const int N = 5e6 + 5;

int phi[N];
long long sum[N];

inline void initPhi(int n) {
	for (register int i = 1; i <= n; i++)
		phi[i] = i;
	for (register int i = 2; i <= n; i++) {
		if (phi[i] != i) continue;
		for (register int j = i; j <= n; j += i)
			phi[j] = phi[j] / i * (i - 1);
	}
	for (register int i = 1; i <= n; i++)
		sum[i] = sum[i - 1] + phi[i];
}

int T, m, n;

signed main() {
	initPhi(5e6);
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		if (m == ++n) { puts("3"); continue; }
		if (m == 1) { puts("1"); continue; }
		
		int l = (n - 1) / m;
		int r = (n - 1) / (m - 1);
		printf("%lld
", (sum[r] - sum[l]) << 1);
	}
}

ZJU-ICPC Summer 2020 Contest 3 by Group C

Date:2020/7/15

Problem A. FibTree

给定一个 (n(le 10^5)) 个点,(m(le 10^6)) 条边的无向图,边权 (cin {0, 1})。求问是否存在一个生成树,其边权和在斐波那契数列中出现过。

当我们得到一个生成树后,我们可以通过把一条 1 边用 0 边替换,或用 1 边替换 0,这样可以使总边权 +1 / -1。

那么我们只需要用 Kruskal 求出最大、最小生成树,然后判断整个区间中是否存在斐波那契数即可。复杂度 (O(mlog m))

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <algorithm>
#include <cstdio>

using namespace std;
namespace fastIO_int {
	int get_int()
	{
		int x=0,f=1;char c=getchar();
		while(c<'0'||c>'9')
		{
			if(c=='-')f=-1;
			c=getchar();
		}
		while(c>='0'&&c<='9')
			x=x*10+c-'0',c=getchar();
		return f*x;
	}
	
	void read(){}
	template<class T1,class ...T2>
	void read(T1 &i,T2&... rest)
	{
		i=get_int();
		read(rest...);
	}
};
using fastIO_int::read;

const int N = 1e5 + 5;
const int M = 1e6 + 5;

int n, m, maxt, mint;
struct edge { int u, v, w; } e[M];

inline bool cmp0(const edge& x, const edge& y) {
	return x.w < y.w;
}
inline bool cmp1(const edge& x, const edge& y) {
	return x.w > y.w;
}

int uset[N];
int find(int x) {
	return x == uset[x] ? x : uset[x] = find(uset[x]);
}
int f[N];

signed main() {
	read(n, m);
	for (register int i = 1; i <= m; i++)
		read(e[i].u, e[i].v, e[i].w);
	
	for (register int i = 1; i <= n; i++) uset[i] = i;
	sort(e + 1, e + 1 + m, cmp0);
	int left = n - 1;
	for (register int i = 1; left && i <= m; i++) {
		int u = e[i].u, v = e[i].v;
		if (find(u) == find(v)) continue;
		--left, mint += e[i].w;
		uset[find(u)] = find(v);
	}
	if (left) {
		puts("No");
		return 0;
	}
	
	for (register int i = 1; i <= n; i++) uset[i] = i;
	sort(e + 1, e + 1 + m, cmp1);
	left = n - 1;
	for (register int i = 1; left && i <= m; i++) {
		int u = e[i].u, v = e[i].v;
		if (find(u) == find(v)) continue;
		--left, maxt += e[i].w;
		uset[find(u)] = find(v);
	}
	if (left) {
		puts("No");
		return 0;
	}
	
	f[1] = 1, f[2] = 2;
	for (register int i = 3; i <= 35; i++)
		f[i] = f[i - 1] + f[i - 2];
	
	for (register int i = 1; i <= 35; i++)
		if (mint <= f[i] && f[i] <= maxt) {
			puts("Yes");
			return 0;
		}
	puts("No");
	return 0;
}

Problem C. More and more

给定一个长为 (n(le 3 imes 10^5)) 的数列 ({a_1, a_2, cdots, a_n}(a_iin [-10^5, 10^5])),现在可以选取 不超过 (m(le 30))互不相交 的区间,对选中的区间中每个数字 (a_i) 乘上 (x(in [-10^4, 10^4]))。最后对操作后的序列求最大子段和,试问可以得到的最终结果的最大值。

动态规划。设:

  • (f(i, j)) 表示,选到第 (i) 个数时,且 (a_i) 处于被选中区间中,加上当前的区间一共选中了 (j) 个区间的答案。
  • (g(i, j)) 表示,选到第 (i) 个数时,且 (a_i) 不处于被选中区间中,之前一共选中了 (j) 个区间的答案。

那么根据定义,可得状态转移方程:

  • (f(i, j) = max{ f(i - 1, j), g(i - 1, j - 1), 0 } + a_i imes x)
  • (g(i, j) = max{ f(i - 1, j), g(i - 1, j), 0 } + a_i)

最后整个数组的最大值即为答案。时间复杂度 (O(nm))不开 long long 见祖宗

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Article : ZJU Summer Training 2020
*/
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
const int M = 32;

int n, m;
LL x, a[N];
LL f[N][M];
LL g[N][M];

signed main() {
	ios::sync_with_stdio(false);
	cin >> n >> m >> x;
	for (register int i = 1; i <= n; i++)
		cin >> a[i];
	
	memset(f, -0x3f, sizeof f);
	memset(g, -0x3f, sizeof g);
	LL ans = f[0][0];
	
	g[0][0] = 0ll;
	for (register int i = 1; i <= n; i++) {
		for (register int j = 0; j <= m; j++) {
			if (j) f[i][j] = max({0ll, f[i - 1][j], g[i - 1][j - 1]}) + a[i] * x;
			g[i][j] = max({0ll, f[i - 1][j], g[i - 1][j]}) + a[i];
		}
	}
	
	for (register int i = 1; i <= n; i++)
		for (register int j = 0; j <= m; j++)
			ans = max({ans, f[i][j], g[i][j]});
	cout << ans << endl;
	return 0;
}

Problem E. Election

给定一个 (n(le 3 imes 10^5)) 个结点的树,带边权 ((a_i < 2^{31})),若为以结点 (i) 为根,那么一个结点 (j) 产生的价值为 (sumlimits_{k in ext{path}(j ightarrow i)} [a_k < a_j])。记一个结点 (x) 作为根结点时产生所有结点的价值和为 (f(x)),试求 (sumlimits_{xin ext{V}} f(x))。(( ext{V}) 表示点集)

线段树合并。当处理到结点 (x) 时,那么我们计算 (x) 的贡献:

  • 对于 (x) 的某一子结点 (y),对答案产生 不在子树 (y) 中的结点数 ( imes) 子树 (y) 中权值严格小于 (a_x) 的结点数
  • 子树 (x) 中的结点数 ( imes) 不在子树 (x) 中的,权值严格小于 (a_x) 的结点数

维护子树中点权的集合,可以用动态开点权值线段树维护,并在递归中往上合并。

时空复杂度 (O(nlog n))

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : ZJU Summer Training 2020
 */
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;
const int N = 3e5 + 5;
const int S = N << 5;

int lc[S], rc[S], size[S];
int total = 0, root[N];

int val[N], n;
vector<int> G[N];

#define mid ((l + r) >> 1)
void insert(int& x, int p, int v, int l = 1, int r = n) {
	if (!x) x = ++total;
	if (l == r) return size[x] += v, void();
	if (p <= mid) insert(lc[x], p, v, l, mid);
	else insert(rc[x], p, v, mid + 1, r);
	size[x] = size[lc[x]] + size[rc[x]];
}
int merge(int x, int y) {
	if (!x || !y) return x | y;
	size[x] += size[y];
	lc[x] = merge(lc[x], lc[y]);
	rc[x] = merge(rc[x], rc[y]);
	return x;
}
int count(int x, int p, int l = 1, int r = n) {
	if (!x || p < l) return 0;
	if (l == r || p >= r) return size[x];
	if (p <= mid) return count(lc[x], p, l, mid);
	else return count(rc[x], p, mid + 1, r) + size[lc[x]];
}
#undef mid

int trSize[N];
int getSize(int x, int f) {
	trSize[x] = 1;
	for (auto y : G[x]) if (y != f)
		trSize[x] += getSize(y, x);
	return trSize[x];
}
long long ans = 0ll;
void solve(int x, int f) {
	for (auto y : G[x])	if (y != f) {
		solve(y, x);
		ans += (n - trSize[y]) * 1LL * count(root[y], val[x] - 1);
		root[x] = merge(root[x], root[y]);
	}
	insert(root[x], val[x], 1);
	int lessCnt = val[x] - 1 - count(root[x], val[x] - 1);
	ans += lessCnt * 1LL * trSize[x];
}

vector<int> discr;

signed main() {
	ios::sync_with_stdio(false);
	cin >> n;
	for (register int i = 1; i <= n; i++)
		cin >> val[i];
	
	discr = vector<int>(val + 1, val + 1 + n);
	sort(discr.begin(), discr.end());
	for (register int i = 1; i <= n; i++)
		val[i] =  lower_bound(discr.begin(), discr.end(), val[i])
				- discr.begin() + 1;
	
	for (register int i = 1; i < n; i++) {
		int u, v; cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	
	getSize(1, 0);
	solve(1, 0);
	cout << ans << endl;
	return 0;
}
原文地址:https://www.cnblogs.com/-Wallace-/p/13300731.html