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
动态规划。设 (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;
}