题目链接 题目大意: 有n个整数的数组a,现在需要给数组每个元素进行染色,注意: 1、每个元素只能有一个颜色; 2、每个元素都要染色; 每个颜色的收益,等于染该色的元素中最大值减去最小值; 问,染色完所有元素后,最大的收益是多少。
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1000) 每个样例2行,第一行整数𝑛 (1≤𝑛≤50) 第二行n个整数,𝑎1,𝑎2,…,𝑎𝑛(1≤𝑎𝑖≤50)
输出: 每个样例一行,输出最大的收益。
Examples input 6 5 1 5 6 3 4 1 5 4 1 6 3 9 6 1 13 9 3 7 2 4 2 2 2 2 5 4 5 2 2 3
output 7 0 11 23 0 5
题目解析: 每个颜色如果只有1个元素,没有收益; 如果有3个元素,生效的只有最小和最大值,所以每个颜色最多染色2个元素; 那么将元素排序,每次选择最小和最大值即可。
#include<cstdio>
#include<cmath>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<deque>
#include<string>
#include<utility>
#include<sstream>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long lld;
class Solution {
static const int N = 201010;
int a[N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
sort(a, a + n);
int sum = 0;
for (int i = 0; i < n / 2; ++i) {
sum += a[n - i - 1] - a[i];
}
cout << sum << endl;
}
}
}
ac;
int main(int argc, const char * argv[]) {
// insert code here...
ac.solve();
return 0;
}
题目链接 题目大意: 有n个整数的数组a,现在可以对数组进行操作: 将数组区间[l, r]内的数值乘以-1; 比如说[1,2,3,4,5],对区间[2, 3]进行操作,则得到[1, -2, -3, 4, 5]; 现在可以进行任意次(包括0次),问最少多少次,才能将数组所有元素的和最大;
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1000) 每个样例2行,第一行整数𝑛 (1≤𝑛≤2e5) 第二行n个整数,𝑎1,𝑎2,…,𝑎𝑛(-1e9≤𝑎𝑖≤1e9)
输出: 每个样例一行,输出2个整数,第一个整数表示最大的元素和,第二个整数表示最小的操作次数;
Examples input 5 6 -1 7 -4 -2 5 -8 8 -1 0 0 -2 1 0 -3 0 5 2 -1 0 -3 -7 5 0 -17 0 1 0 4 -1 0 -2 -1
output 27 3 7 2 13 1 18 1 4 1
题目解析: 要让结果最大,所有数字都应该变成非负数,最大和就是所有数字的绝对值之和; 关键在于如何让次数尽可能少,我们对题目进行简化,在考虑正负数时,数值大小没有意义,可以将所有数字简化成:-1、0、1; 由于操作的时候,可以选择一个区间进行操作,那么[-1、-1]和 [1, 1]这样的区间,可以简化成[-1]和[1];(0也是同样道理) 那么数组就变成了由[-1, 0, 1]组成的; 同样简化思路,由于[-1, 0, -1]也同样看成一个[-1],于是数组就变成了-1和1交错的情况; 假设简化后的数组长度为m,结果就是m/2;(-1都是单独的,直接消除)
#include<cstdio>
#include<cmath>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<deque>
#include<string>
#include<utility>
#include<sstream>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long lld;
class Solution {
static const lld N = 201010;
int a[N];
public:
void solve() {
lld t;
cin >> t;
while (t--) {
lld n, ans = 0;
cin >> n;
for (lld i = 0; i < n; ++i) {
cin >> a[i];
ans += abs(a[i]);
}
lld sum = 0;
lld cur = 0, last = 0;
while (cur < n && a[cur] >= 0) {
++cur;
}
if (cur < n) {
++sum;
last = a[cur];
}
++cur;
while (cur < n) {
if (a[cur] != 0) {
if (a[cur] * last < 0) {
last = a[cur];
++sum;
}
}
++cur;
}
cout << ans << " " << (sum + 1) / 2 << endl;
}
}
}
ac;
int main(int argc, const char * argv[]) {
// insert code here...
ac.solve();
return 0;
}
题目链接 题目大意: 有如图的二叉树; 数字从1到n有且只有一条路径,求这个路径上的整数和。
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1000) 每个样例一行整数𝑛 (1≤𝑛≤1e16)
输出: 每个样例一行,输出路径上的累加和。
Examples input 6 3 10 37 1 10000000000000000 15
output 4 18 71 1 19999999999999980 26
题目解析: 二进制分析思路: 完全二叉树,每次有两个选择,左或者右; 对应到二进制,左边是0,右边是1; 5是101,对应到二进制树就是左+右;
搜索分析思路: 1到12为例,1到12的路径有且仅有1-3-6-12; 将这些数字的二进制写出来: 1 11 110 1101 容易发现二进制的前缀是相同的;
这样不断能去掉n最为后一位二进制数,可得上一位数字,累加即可。
#include<cstdio>
#include<cmath>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<deque>
#include<string>
#include<utility>
#include<sstream>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long lld;
class Solution {
static const lld N = 201010;
int a[N];
public:
void solve() {
lld t;
cin >> t;
while (t--) {
lld n;
cin >> n;
lld ans = 0;
while (n) {
ans += n;
n /= 2;
}
cout << ans << endl;
}
}
}
ac;
int main(int argc, const char * argv[]) {
// insert code here...
ac.solve();
return 0;
}
题目链接 题目大意: 有n个节点的树,由n-1条边组成; 已知根节点1为最上面节点,其他节点向下成长; 现在有两个苹果,生长在节点x和节点y上面; 苹果成熟后会顺着每个节点的边,朝着远离根节点的方向下落; 如果某个节点是叶子节点,则会从树上落下;
现在有q个询问,每次输入苹果生长节点x和节点y,问苹果落下的位置节点组合(posX,poxY))的可能性,posX和posY分别表示节点X和节点Y苹果落下的节点位置;
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤10000) 每个样例,第一行整数𝑛 (1≤𝑛≤2e5) 接下来有n-1行表示相连边,每行两个整数 𝑢𝑖 and 𝑣𝑖 (1≤𝑢𝑖,𝑣𝑖≤𝑛 , 𝑢𝑖≠𝑣𝑖 ) 然后是整数𝑞,表示q个询问 (1≤𝑞≤2⋅1e5) 接下来是q行整数,每行有整数 𝑥𝑖 and 𝑦𝑖 ,表示苹果生长节点 (1≤𝑥𝑖,𝑦𝑖≤𝑛 )
输出: 每个样例q行,输出每次询问的位置组合数;
Examples input 2 5 1 2 3 4 5 3 3 2 4 3 4 5 1 4 4 1 3 3 1 2 1 3 3 1 1 2 3 3 1
output 2 2 1 4 4 1 2
题目解析: 题目看起来复杂,其实就是算每个节点的子树中,有多少个叶子节点,用数组cnt[i]来表示; 由于苹果x和苹果y是相互独立的,最终答案就是cnt[x] ✖️cnt[y];
快速求cnt[i]可以用dfs,对于每个节点,统计其子节点的叶子节点数量,然后累加即可。
#include<cstdio>
#include<cmath>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<deque>
#include<string>
#include<utility>
#include<sstream>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long lld;
class Solution {
static const lld N = 201010;
vector<int> g[N];
lld cnt[N];
lld dfs(int u, int f) {
int sub = 0;
for (int i = 0; i < g[u].size(); ++i) {
int v = g[u][i];
if (v == f) continue;;
sub += dfs(v, u);
}
cnt[u] = sub == 0 ? 1 : sub;
return cnt[u];
}
public:
void solve() {
lld t;
cin >> t;
while (t--) {
lld n;
cin >> n;
for (int i = 1; i <= n; ++i) {
g[i].clear();
}
for (int i = 0; i < n - 1; ++i) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
int q;
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
cout << cnt[x] * cnt[y] << endl;
}
}
}
}
ac;
int main(int argc, const char * argv[]) {
// insert code here...
ac.solve();
return 0;
}
题目链接 题目大意: 有n个整数的数组,初始状态元素都是0; 有m个区间[ 𝑙𝑖,𝑟𝑖],我们定义一个区间是beautiful的,只要这个区间内,数字1的数量严格大于数字0的数量; 有q个操作依次执行,每次操作是将q𝑖位置的元素变为1; 现在想知道,当第几次操作后,m个区间中将出现至少1个beautiful的区间。
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤10000) 每个样例,第一行整数𝑛 and 𝑚 (1≤𝑚≤𝑛≤1e5 ) 接下来有m行,每行两个整数表示区间 𝑙𝑖 and 𝑟𝑖 (1≤𝑙𝑖≤𝑟𝑖≤𝑛 ) 然后是整数𝑞,表示q个操作 (1≤𝑞≤ n) 接下来是q行整数,每行有整数 𝑥 (1≤𝑥≤𝑛)
输出: 每个样例1行,输出该次操作后,m个区间中将出现至少1个beautiful的区间。
Examples input 6 5 5 1 2 4 5 1 5 1 3 2 4 5 5 3 1 2 4 4 2 1 1 4 4 2 2 3 5 2 1 5 1 5 4 2 1 3 4 5 2 1 5 1 3 5 4 1 2 3 5 5 5 1 5 1 5 1 5 1 5 1 4 3 1 4 3 3 2 2 2 1 3 3 2 3 1
output 3 -1 3 3 3 1
题目解析: 假设不考虑复杂度,对于每次操作,遍历每个区间得到区间内整数1的数量,判断是否有解; 总的复杂度是操作次数q ✖️区间数量m ✖️区间长度n,远远超过题目要求。 分析优化空间,我们发现区间[l, r]总是比[l, r+1]更优,那么所有区间可以拆分为,左右区间节点各不相同的区间(只要有一个节点相同,则存在更优解); 操作同样如此,对于某个位置的重复操作是无意义的,操作可以优化为无重复位置的操作; 但是,上述两个操作都属于常数级别优化,对于特定样例优化空间很小。
当我们考虑最坏情况,即结果是否有解的时候,我们可以把所有操作都执行一遍,得到一个0/1数组,此时再去判断是否存在beautiful区间; 方式有很多,这里可以用前n项和,即sum[i]表示位置i前面所有元素的1数量,这样区间[x, y]就可以用sum[y] - sum[x]快速计算得到区间1的数量,从而很快判断区间是否为beautiful; 这样就可以用O(N)的复杂度,快速判断; 接着就可以用二分的方式,来处理1-q个操作的情况。
#include<cstdio>
#include<cmath>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<deque>
#include<string>
#include<utility>
#include<sstream>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long lld;
class Solution {
static const lld N = 102010;
pair<int, int> seg[N];
int a[N];
int b[N];
int sum[N];
bool check(int n, int m, int len) {
for (int i = 0; i <= n; ++i) {
b[i] = sum[i] = 0;
}
for (int i = 0; i < len; ++i) {
b[a[i]] = 1;
}
for (int i = 1; i <= n; ++i) {
sum[i] += sum[i - 1] + b[i];
}
for (int i = 0; i < m; ++i) {
int x = seg[i].first, y = seg[i].second;
if ((sum[y] - sum[x - 1]) * 2 > (y - x + 1)) {
return true;
}
}
return false;
}
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; ++i) cin >> seg[i].first >> seg[i].second;
int q;
cin >> q;
for (int i = 0; i < q; ++i) cin >> a[i];
if (!check(n, m, q)) {
cout << -1 << endl;
}
else {
int left = 1, right = q;
int ans = right;
while (left < right) {
int mid = (left + right) / 2;
if (check(n, m, mid)) {
ans = mid;
right = mid;
}
else {
left = mid + 1;
}
}
cout << ans << endl;
}
}
}
}
ac;
int main(int argc, const char * argv[]) {
// insert code here...
ac.solve();
return 0;
}
题目链接 题目大意: 有棵树初始时只有根节点1,该节点的值为1; 现在可以往树上增加节点,每个节点新增时序号是当前树上最大节点的加1,节点的值为1或者-1; 现在想知道,随着不断增加节点,对于某两个节点u和v之间,是否存在路径,使得经过的节点和为k;(每个节点只能经过一次,集合可以为0)
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤10000) 每个样例,第一行整数𝑛 (1≤𝑛≤2e5 ) 接下来有n行,每行有两种情况: 情况1,由符号'+'和两个整数v x组成,表示增加一个新节点v,值为x (x为-1或者1) 情况2,由符号'?'和三个整数u v k组成,表示询问u到v的路径中,是否存在某一个段路径的值为k;(u=1,v保证是存在节点)
输出: 对于每一个询问,如果存在则输出YES,如果不存在则输出NO;
Examples input
1
8
+ 1 -1
? 1 1 2
? 1 2 1
+ 1 1
? 1 3 -1
? 1 1 1
? 1 3 2
? 1 1 0
output NO YES NO YES YES YES
题目解析: 先简化题目思考,假如不是树,而是线段; 这个题目在考虑是否存在区间和为k时,其实就询问这条线段中,最大和是多少、最小和是多少; 因为最大和就是每个节点的累加值,而节点的值为1和-1,证明能覆盖1、2、3、4、5、、、、MaxSum整个区间;
于是题目简化为,在每新增一个节点时,我们计算以这个节点结尾时,路径的最大和dpMax、最小和dpMin; 同时我们维护一个ansMax和ansMin表示从根节点1到该节点路径上,所有节点的dpMax的最大值、dpMin的最小值; 假设n是新增节点序号,fat是新增节点的父节点序号,那么有状态转移方程: dpMax[n] = max(0, dpMax[fat]) + a[n]; dpMin[n] = min(0, dpMin[fat]) + a[n]; ansMax[n] = max(dpMax[n], ansMax[fat]); ansMin[n] = min(dpMin[n], ansMin[fat]);
扩展思考: hard version是去掉u=1的限制,题目就复杂很多。 通过lca算法找到u和v的共同祖先,在这条路径上,按照上述思路再处理一次,但是这样的复杂度达到了O(N)级别。 没有太好的优化思路了。
#include<cstdio>
#include<cmath>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<deque>
#include<string>
#include<utility>
#include<sstream>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long lld;
class Solution {
static const lld N = 201010;
static const int inf = 1e9;
vector<int> g[N];
int dpMax[N], dpMin[N];
int ansMax[N], ansMin[N];
int a[N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n = 1;
a[1] = dpMax[1] = dpMin[1] = 1;
ansMax[1] = ansMin[1] = 1;
int tmp;
cin >> tmp;
while (tmp--) {
char c[10];
cin >> c;
if (c[0] == '+') {
int fat;
++n;
cin >> fat >> a[n];
dpMax[n] = max(0, dpMax[fat]) + a[n];
dpMin[n] = min(0, dpMin[fat]) + a[n];
ansMax[n] = max(dpMax[n], ansMax[fat]);
ansMin[n] = min(dpMin[n], ansMin[fat]);
}
else {
int u, v, sum;
cin >> u >> v >> sum;
if ((ansMin[v] <= sum && sum <= ansMax[v]) || sum == 0) {
cout << "YES" << endl;
}
else {
cout << "NO" << endl;
}
}
}
}
}
}
ac;
int main(int argc, const char * argv[]) {
// insert code here...
ac.solve();
return 0;
}