题目1
题目链接
题目大意:
给出n个整数的数组a和b,现在可以执行任意次下面的操作:
选择索引x,交换数组a和b中的元素a[x]和b[x];
现在想知道|𝑎1−𝑎2|+|𝑎2−𝑎3|+⋯+|𝑎𝑛−1−𝑎𝑛| + |𝑏1−𝑏2|+|𝑏2−𝑏3|+⋯+|𝑏𝑛−1−𝑏𝑛| 的最小值是多少;
输入:
第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤4000)
每个样例3行,第一行整数 𝑛 (2≤𝑛≤25)
第二行n个整数 𝑎1,𝑎2,…,𝑎𝑛 (1≤𝑎𝑖≤1e9)
第三行n个整数 𝑏1,𝑏2,…,𝑏𝑛 (1≤𝑏𝑖≤1e9)
输出:
每个样例一行,输出最小值;
Examples
input
3
4
3 3 10 10
10 10 3 3
5
1 2 3 4 5
6 7 8 9 10
6
72 101 108 108 111 44
10 87 111 114 108 100
output
0
8
218
题目解析:
假设有数字a1和a2,b1和b2;
在一个一维数轴上,a1和b1就是两个点,并且我们可以让a1<b1;
然后 |a1-a2|+|b1-b2| 其实就是在数轴上还有两个点a2和b2,也可以让a2<b2;
题目的要求,就是求(a1到a2距离+b1到b2距离) 和 (a1到b2距离+b1到a2的距离)哪个距离和更小;
可以知道,还是前者的距离更小,因为后者中的a1到b2距离 或者 b1到a2的距离,总会有一个距离很大;
也可以用简单的思维来看:小的和小的比,大的和大的的比。
class Solution {
static const int N = 201010;
int a[N], b[N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
for (int i = 0; i < n; ++i) {
cin >> b[i];
if (a[i] > b[i]) {
swap(a[i], b[i]);
}
}
lld sum = 0;
for (int i = 0; i < n - 1; ++i) {
sum += abs(a[i] - a[i + 1]);
sum += abs(b[i] - b[i + 1]);
}
cout << sum << endl;
}
}
}
ac;
题目2
题目链接
题目大意:
给出一个整数v,现在可以执行任意次操作:
𝑣 = (𝑣+1) mod 32768
或 𝑣 = (2⋅𝑣) mod 32768.
现在想知道,最少执行多少次操作,才能让整数变成0;
输入:
第一行整数𝑛 ,表示n个整数(1≤𝑛≤32768)
第二行n个整数𝑎1,𝑎2,…,𝑎𝑛 (0≤𝑎𝑖<32768)
输出:
输出n个整数,分别表示n个整数的最少操作次数。
Examples
input
4
19 32764 10240 49
output
14 4 4 15
题目解析:
32768是一个比较大的整数,由于操作里面有一个乘以2操作,我们将32768进行除以2操作,发现32768=2^15;
那么如果将整数v当成一个二进制数,就是寻求如何快速将这个二进制数的后面15位变为0;
那么+1就是在整数末尾+1,如果是乘以2就是将整个整数左移;
最极端的情况,将整个整数左移15次,一定会有解;
另外,容易知道如果执行过一次x2操作,就不会再执行+1操作,因为x2操作是末尾补0,但是+1会导致末尾变成1;
那么问题就变成在x2之前,需要执行多少次+1操作;
这里有很多种做法:枚举x2操作的次数,然后计算剩下所有加法次数;
枚举+1的次数,再计算枚举x2的次数;
class Solution {
static const int N = 201010;
int a[N], b[N];
void check(int n) {
char s[20];
int pos = 0;
while (n) {
s[pos++] = '0' + n % 2;
n /= 2;
}
reverse(s, s + pos);
s[pos] = 0;
cout << s << endl;
}
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n == 0) {
cout << 0 << endl;
continue;;
}
int ans = 15; // 1 << 15
// check(n);
for (int i = 0; i < 15; ++i) {
int mod = 1 << (15 - i);
int left = n % mod;
ans = min(ans, i + (mod - left) % mod);
}
cout << ans << endl;
}
}
}
ac;
题目3
题目链接
题目大意:
有n个整数,现在可以执行若干次操作,每次操作可以选择数组中某个元素进行增长,也可以选择跳过该操作;
这样进行第1次操作、第2次操作、第3次操作....
假如第1、2、3次操作中,第k次操作是选择数组元素a[i],则如果k是奇数则使a[i]=a[i]+1;如果k是偶数则使a[i]=a[i]+2;
现在想知道,最好经过多少次操作可以让整个数组的元素相等。
输入:
第一行整数𝑡,表示t个样例 (1≤𝑡≤2⋅1e4)
每个样例2行,第一行是整数 𝑛 (1≤𝑛≤3⋅1e5)
第二行是整数 ℎ1,ℎ2,…,ℎ𝑛 (1≤ℎ𝑖≤1e9)
输出:
每个样例一行,最少的操作数。
Examples
input
3
3
1 2 4
5
4 4 3 5 5
7
2 5 4 8 3 7 4
output
4
3
16
题目解析:
目标是所有整数一样,那么假设maxItem是数组中最大的元素,最终的结果必然是maxItem或者maxItem+1;
证明:可以用反证法,假如最终结果是maxItem+2,那么必然可以将所有整数-2(或者连续-1两次),得到最终元素maxItem。
现在问题就变成,假如n个元素都要变成maxItem,最少需要多少次操作。
根据对于第i个元素a[i],我们得到diff=maxItem-a[i];
如果diff是偶数,那么需要diff/2次+2操作;如果diff是奇数,那么需要diff/2次+2操作和1次+1操作;
我们用odd表示+1操作次数,event表示+2操作次数,根据上面的规则,我们可以算出来总的odd和even。
接下来考虑替换的问题,假如even比odd大,那么存在一种替换关系:
两个+1操作可以替换成1个+2操作;
那么我们用tmp = (even - odd)/3,可以得到tmp次无缝替换;(为什么要/3?以为在把+2替换成+1的时候,+2的数量也会-1)
这样令odd = odd + tmp * 2, even = even - tmp,得到无法替换的odd和even次数;
此时还存在一种特殊情况,就是even - odd = 2的情况:
此时仍然1个+2可以替换成2个+1,整体的代价为1。
比如说样例
3
1 1 3
odd=0,even=2,如果不做任何替换,则ans=even*2=4;
但是可以把1个+2替换成2个+1,此时odd=2, even=1,此时ans = odd * 2 - 1 = 3;
得到最终的odd和even数量后,就可以得到答案:
如果odd>even, 则 ans = odd * 2 - 1;
如果odd<=even,则 ans = even * 2;
注意:题目需要用long long来处理,避免整数溢出;以及整体数据量偏大,注意数组边界,避免越界;
class Solution {
static const int N = 301010;
lld a[N];
lld check(lld n, lld maxItem) {
lld ans = 0;
lld odd = 0, even = 0;
for (int i = 0; i < n; ++i) {
lld diff = maxItem - a[i];
even += diff / 2;
if (diff % 2) {
odd += 1;
}
}
// cout << "max:" << maxItem << " odd: " << odd << " even: " << even << " ans: " << ans << endl;
// 接下来就是将多余的even拆成odd来实现
// (even - odd) / 3 这部分是一定可以拆的
if (even > odd && (even - odd) / 3) {
lld tmp = (even - odd) / 3;
odd += tmp * 2;
even -= tmp;
}
if (even == 0 && odd == 0) {
ans = 0;
}
if (odd > even) {
ans = odd * 2 - 1;
}
else {
if (even - odd == 2) {
ans = even * 2 - 1;
}
else {
ans = even * 2;
}
}
return ans;
}
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
lld maxItem = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
maxItem = max(maxItem, a[i]);
}
cout << min(check(n, maxItem), check(n, maxItem + 1)) << endl;
}
}
}
ac;
题目4
题目链接
题目大意:
有n个整数,现在可以执行若干次操作,每次操作可以选择数组中某个元素进行增长,也可以选择跳过该操作;
这样进行第1次操作、第2次操作、第3次操作....
假如第1、2、3次操作中,第k次操作是选择数组元素a[i],则如果k是奇数则使a[i]=a[i]+1;如果k是偶数则使a[i]=a[i]+2;
现在想知道,最好经过多少次操作可以让整个数组的元素相等。
输入:
第一行整数𝑡,表示t个样例 (1≤𝑡≤2⋅1e4)
每个样例2行,第一行是整数 𝑛 (1≤𝑛≤3⋅1e5)
第二行是整数 ℎ1,ℎ2,…,ℎ𝑛 (1≤ℎ𝑖≤1e9)
输出:
每个样例一行,最少的操作数。
Examples
input
3
3
1 2 4
5
4 4 3 5 5
7
2 5 4 8 3 7 4
output
4
3
16
题目解析:
把每次操作都当成是选择一个线段,那么最终会有m个线段,我们关注每个线段右边终点;
然后从右到左遍历数组元素,对于第i个元素,尽可能使得元素i满足a[i]>=b[i],这样一定会有最优解。
这里的证明比较明显,比如线段[i, j]对于元素j肯定是最优解,[i+1, j+1]会浪费j+1部分,[i-1, j-1]则会无法增加j的大小。
但是按照上的做法,每个元素都要遍历元素i和前面k个元素,总体的复杂度是O(NK),会出现超时的情况。
但是对于已经给定的某个线段数量,我们能在O(N)的时间判断这个是否有解,配合数量的二分,可以在O(NLogM)的复杂度内得到结果,M是最大的结果。
对于mid条线段,我们先假设全部添加在元素i上面,然后每次计算元素i上面有多少条线段可以左移1位,这个数字就是(a[i]-b[i])/k;
然后不断计算这个移动的过程,直到线段无法移动(如果移动之后a[i]会小于b[i],则这个位置无法移动线段);
对于元素i,初始化的时候添加mid条线段的复杂度是O(K),接下来每次移动一条线段都可以用c[i-k-1] -= 1和c[i-1] += 1来标记,后面就需要根据初始化的值以及这个区间内c的大小来得到结果;
思考🤔:
是否有最优解呢?
有的,理论最优解应该是O(N),从最初的贪心做法开始分析,最大的重复计算是每次操作之后,进行区间计算的过程。
这个计算有明显的线性特性,可以进行如下的优化:
对于数组[1, 3, 4, 7]来说,当我们在最后添加3条线段的时候,我们会得到[0, 3, 6, 9],但是需要计算3次,这也是复杂度超标的所在。
接着上面的思路,当我们在第4个元素选择添加3条线段时,我们令a[3]=9,同时记录sum=9,cnt=3,并且用上面的区间标记方式,在c[2]和c[0]的地方标记+3和-3;
这样当我们访问第3个元素时,我们只需要计算sum-cnt,就可以快速得到之前的结果,并且根据c的信息实时更新cnt的大小即可。
这样只需要遍历一次数组,并且每次操作都是O(1),达到了理论最优解O(N);
class Solution {
static const int N = 301010;
lld a[N], b[N], c[N], n;
bool check(lld k, lld mid) {
for (lld i = 0; i <= n; ++i) {
a[i] = c[i] = 0;
}
for (lld i = 0; i < k; ++i) {
a[n - k + i] = mid * (i + 1);
}
lld cur = mid, seg = 0; // 当前有cur条线段以i结束,仅有这部分可以左移
for (lld i = n - 1; i >= 0; --i) {
seg += c[i];
// cout << mid <<" " << i << " " << a[i] + seg << endl;
if (a[i] + seg < b[i]) {
return false;
}
lld move = min(cur, (a[i] + seg - b[i]) / k);
cur = move;
if (i >= k) {
c[i - 1] += move;
}
if (i >= k + 1) {
c[i - k - 1] -= move;
}
}
return true;
}
public:
void solve() {
lld t = 1;
while (t--) {
lld k;
cin >> n >> k;
lld top = 0;
for (lld i = 0; i < n; ++i) {
cin >> b[i];
}
for (lld i = 0; i < n; i+=k) {
lld tmp = 0;
for (lld j = 0; j < k; ++j) {
tmp = max(tmp, b[i + j]);
}
top += tmp;
}
lld left = 1, right = top + 1;
lld ans = right;
while (left <= right) {
lld mid = (left + right) / 2;
if (check(k, mid)) {
ans = mid;
right = mid - 1;
}
else {
left = mid + 1;
}
}
cout << ans << endl;
}
}
}
ac;
网友评论