zoukankan      html  css  js  c++  java
  • 「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;
    }
    
  • 相关阅读:
    UDP——python网络编程笔记
    2018.7计划
    二叉树
    第7.5章:类——Cookbook笔记
    网络编程 Cookbook
    网络编程 csapp
    第5.5章 迭代器与生成器——CookBook笔记
    第3.5章 数据结构与算法——CookBook笔记
    关于排序
    关于搜索
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/13300731.html
Copyright © 2011-2022 走看看