Codeforces Round #665 (Div. 2)
A Distance and Axis
题意
在X正半轴上有点 A = n A=n A=n,我们想找到一个整数点 B(也在OX轴上),使得 O B OB OB 的距离减 A B AB AB 的距离的绝对值等于 k k k ,如果B找不到,可以将A点每次左移或右移一个单位,知道找到一个B点。求A的最少移动次数。
Solution
如果k大于n,那么B一定在A右边,就变成了
O
B
−
A
B
=
O
A
OB-AB=OA
OB−AB=OA 的距离是k,就把A移动到k位置。
如果k小于等于n,假设B的坐标是m,则
∣
(
n
−
m
)
−
m
∣
=
k
|(n-m)-m|=k
∣(n−m)−m∣=k,整理一下并且把绝对值去掉,就变成了了
2
m
=
n
−
k
2m=n-k
2m=n−k 或者
2
m
=
n
+
k
2m=n+k
2m=n+k,因为m是整数,所以就能得到结论n和k奇偶性相同,答案只能是0或1。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int t;
cin >> t;
while (t--)
{
int n, k;
cin >> n >> k;
if (n >= k)
cout << ((n - k) % 2) << endl;
else
cout << k - n << endl;
}
return 0;
}
B Ternary Sequence
题意
给定两个长度为
n
n
n 的序列
A
,
B
A,B
A,B,他们中分别由
x
i
,
y
i
,
z
i
x_i,y_i,z_i
xi,yi,zi 个
0
,
1
,
2
0,1,2
0,1,2 构成,你可以重新排列A和B中的元素顺序,使得
∑
c
i
\sum c_i
∑ci 最大。
c
i
=
{
a
i
b
i
,
i
f
a
i
>
b
i
0
,
i
f
a
i
=
b
i
−
a
i
b
i
,
i
f
a
i
<
b
i
c_i=\begin{cases} a_{i}b_{i}, & if\ a_i>b_i \\ 0, & if\ a_i=b_i \\ -a_{i}b_{i}, & if\ a_i<b_i \\ \end{cases}
ci=⎩⎪⎨⎪⎧aibi,0,−aibi,if ai>biif ai=biif ai<bi
Solution
贪心。A、B中的0、1、2两两匹配有9种可能,可以得到(1,2)等于-2,(2,1)等于2,其他组合都等于0。
所以要使(2,1)组合尽可能多,使(1,2)组合尽可能少。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int t;
cin >> t;
while (t--)
{
int a[3], b[3];
for (int i = 0; i < 3; i++)
cin >> a[i];
for (int i = 0; i < 3; i++)
cin >> b[i];
int ans = 0;
int temp = min(a[2], b[1]); // (ai=2, bi=1) 尽可能多
ans += temp * 2;
a[2] -= temp;
b[1] -= temp;
if (a[1] > b[0] + b[1]) // 先不匹配 (ai=1, bi=2) 的组合,先尽可能把其他组合匹配完
ans -= (a[1] - b[0] - b[1]) * 2;
cout << ans << endl;
}
return 0;
}
C Mere Array
题意
给你一个长度为 n n n 的数组 a a a , m i n n minn minn 是数组 a a a 的最小值,当 g c d ( a i , a j ) = = m i n n gcd(a_i,a_j)==minn gcd(ai,aj)==minn 时,可以交换 a i , a j a_i,a_j ai,aj 的位置,问能否通过交换使得数组 a a a 变成非递减的。
Solution
思维题。分析一下,因为
g
c
d
(
a
i
,
a
j
)
=
=
m
i
n
n
gcd(a_i,a_j)==minn
gcd(ai,aj)==minn ,所以
g
c
d
(
a
i
,
m
i
n
n
)
=
=
m
i
n
n
gcd(a_i,minn)==minn
gcd(ai,minn)==minn ,
g
c
d
(
a
j
,
m
i
n
n
)
=
=
m
i
n
n
gcd(a_j,minn)==minn
gcd(aj,minn)==minn ,那么要交换
a
i
,
a
j
a_i,a_j
ai,aj 就能通过
m
i
n
n
minn
minn 作为中间值把两个数交换。
那么我们考虑哪些数需要交换位置?假设数组
b
b
b 是排好序的,如果
a
i
≠
b
i
a_i\ne b_i
ai=bi ,那么说明
a
i
a_i
ai 需要交换位置,如果
a
i
a_i
ai 不能整除
m
i
n
n
minn
minn ,那么就说明
a
i
a_i
ai 永远不能换位置,否则
a
i
a_i
ai 就能通过
m
i
n
n
minn
minn 作为中间交换变量到达它应该在的位置,可以考虑最简单的冒泡来证明。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
vector<int> a(n);
int minn = INF;
for (int i = 0; i < n; i++)
{
cin >> a[i];
minn = min(a[i], minn);
}
vector<int> b(a);
sort(b.begin(), b.end());
bool flag = true;
for (int i = 0; i < n; i++)
{
if (a[i] != b[i] && a[i] % minn)
{
flag = false;
break;
}
}
if (flag)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
D Maximum Distributed Tree
题意
给定一棵 n n n 个结点的树,给这棵树的边分配边权,使得所有边权的乘积等于 k k k , k k k 以 m m m 个质因子的形式给出,并且要求 ∑ i = 1 n − 1 ∑ j = i + 1 n f ( i , j ) \sum_{i=1}^{n-1}\sum_{j=i+1}^{n}f(i,j) i=1∑n−1j=i+1∑nf(i,j) 最大,其中 f ( i , j ) f(i,j) f(i,j) 表示 i i i 到 j j j 的简单路径边权之和。
Solution
考虑每条边对答案的贡献。我们想一下,一条边可能出现在哪些简单路径中,答案显然是以这条边分开的上下两子树中任意结点组合的简单路径,我们记数组
s
z
[
i
]
sz[i]
sz[i] 表示
i
i
i 结点的子树大小(包括
i
i
i 结点),那么
i
i
i 到它的父亲这条边对答案的贡献次数就是
s
z
[
i
]
×
(
n
−
s
z
[
i
]
)
sz[i]\times (n-sz[i])
sz[i]×(n−sz[i]) ,统计出每条边的贡献次数
q
q
q ,然后我们贪心地把最大的质因子
p
p
p ,分配给贡献次数最多的边即可。
这里要区分
m
≤
n
−
1
m\le n-1
m≤n−1 和
m
>
n
−
1
m>n-1
m>n−1 这两种情况,第一种是边多质因子不够分的,只能补1,第二种是边少质因子多,我们要把多出来的几个最大的质因子相乘变成一个,这样才使最大化。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
vector<int> e[N];
ll sz[N], p[N], q[N];
void dfs(int u, int fa)
{
sz[u] = 1;
for (int v : e[u])
{
if (v == fa)
continue;
dfs(v, u);
sz[u] += sz[v];
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
for (int i = 0; i <= n; i++)
e[i].clear(), sz[i] = 0, p[i] = 1; // 质因子要初始化为 1
for (int i = 1; i < n; i++)
{
int x, y;
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, 0); // dfs求各子树总节点个数
int m;
cin >> m;
for (int i = 1; i <= m; i++) // 输入 K 的质因子
cin >> p[i];
if (m < n - 1) // 如果质因子个数 m 不够 n-1 条边,补 1
m = n - 1;
sort(p + 1, p + m + 1);
for (int i = n; i <= m; i++) // 如果质因子个数 m 超过了 n-1 则将后面的质因子乘到第 n-1 个质因子上,保证 K 的大小
p[n - 1] = (p[n - 1] * p[i]) % MOD;
for (int i = 1; i < n; i++) // 1号节点是dfs的根节点,无与父亲节点的连边,所以求剩下的 n-1 个点与其父亲节点的连边对答案的贡献次数
q[i] = sz[i + 1] * (n - sz[i + 1]); // 不能取模,下面要对 q[] 排序,而且也没有超longlong
sort(q + 1, q + n);
ll ans = 0;
for (int i = 1; i < n; i++)
ans = (ans + ((p[i] % MOD) * (q[i] % MOD)) % MOD) % MOD;
cout << ans << endl;
}
return 0;
}
E Divide Square
题意
在平面直角坐标系中 ( 0 , 0 ) (0,0) (0,0), ( 0 , 1 6 6 ) (0,16^6) (0,166), ( 1 0 6 , 0 ) (10^6,0) (106,0), ( 1 0 6 , 1 0 6 ) (10^6,10^6) (106,106) 这四个点围成的正方形区域中,有 n n n 条水平线段和 m m m 条竖直线段,并且所有的线段至少与正方形的一侧相交,保证在同一条直线上没有两条线段,问这些线段把这个大正方形分成了多少块。
Solution
线段树 / 树状数组。
在给定的条件下,只有两种情况会导致块数增加。
- 水平线和竖直线相交会增加一块。
- 一条线段的两端都与大正方形相交会增加一块。
加上原来的大正方形是一块。
求线段相交的次数,可以用平行于
y
y
y 轴的直线从左到右扫描。具体方法:直线平行于
y
y
y 轴,所以树状数组是记录
y
y
y 轴上每个点的个数,遇到水平线段的左端点就加一,遇到水平线段的右端点就减一,这样就能知道当竖直直线扫描的这个位置时,有多少条横向线段与之相交。
这里需要注意的是,树状数组计算时是从1开始的,而题目中的坐标是从0到106,所以在计算时要把坐标加一。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll MOD = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int n, m;
ll c[M]; // Y坐标的树状数组,注意坐标范围 1e6
struct Hnode // 横线的左右端点
{
int x, y, d; // d = 1 左端点加一 d = -1 右端点减一
bool operator<(const Hnode &OTHER) const { return x < OTHER.x; }
} a[N << 1];
struct Snode // 竖线
{
int x, ly, ry;
bool operator<(const Snode &OTHER) const { return x < OTHER.x; }
} b[N];
int lowbit(int x) { return x & (-x); }
void add(int x, int d)
{
while (x < M)
{
c[x] += d;
x += lowbit(x);
}
}
ll query(int x)
{
ll res = 0;
while (x)
{
res += c[x];
x -= lowbit(x);
}
return res;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
int cnt = 0;
ll ans = 1; // 开始是一整块
for (int i = 1; i <= n; i++) // 横线
{
int y, lx, rx;
cin >> y >> lx >> rx;
a[++cnt] = {lx, y, 1};
a[++cnt] = {rx + 1, y, -1};
if (lx == 0 && rx == 1000000)
ans++;
}
for (int i = 1; i <= m; i++) // 竖线
{
cin >> b[i].x >> b[i].ly >> b[i].ry;
if (b[i].ly == 0 && b[i].ry == 1000000)
ans++;
}
sort(a + 1, a + cnt + 1);
sort(b + 1, b + m + 1);
for (int i = 1, j = 0; i <= m; i++)
{
while (j < n * 2 && a[j + 1].x <= b[i].x)
{
j++;
add(a[j].y + 1, a[j].d); // 树状数组是从下标 1 开始的,题目中坐标从 0 开始,所以这里要把对应坐标加 1
}
ans += query(b[i].ry + 1) - query(b[i].ly);
}
cout << ans << endl;
return 0;
}