2022-04-22每日刷题打卡
代码源——每日一题
古老的星球上有这样一群人,他们每年都会参加盛大的周年庆。在进入场地之前所有人在入口排成两队,每队人数都是 n 人,第一队第 i 人身高为 ai,第二队第 i 人身高为 bi。
人们在排队时,喜欢跟另一队相同位置的伙伴亲切地交谈。但是如果这两人身高相差太多,他们会感到有些尴尬,两人的尴尬指数 gi 与身高差呈这样一种关系:gi=(ai−bi)2。
为了尽可能减轻尴尬,每队的人都可以与前后相邻者交换位置,次数不限,但只能在同队范围内交换。问最少需要交换多少次位置,可以使得总体尴尬度 ∑(ai−bi)2 最小。如果答案太大,请输出这个最少交换次数对 108−7 取模的结果。
保证每队人的身高两两不同。
输入格式
输入共三行。第一行是一个正整数 n,表示每队的人数。 第二行有 n 个整数,每两个整数用一个空格隔开,表示第一队人们的身高。 第三行有 n 个整数,每两个整数用一个空格隔开,表示第二队人们的身高。
输出格式
一个整数,表示最少的交换次数。
数据范围
1≤n≤105,0≤ai,bi<231,保证每队人们的身高各不相同
输入样例
4
2 3 1 4
3 2 1 4
输出样例
1
样例解释
只需交换第一队前两人,或者交换第二队前两人。这样人们都跟与自己身高相同的伙伴交谈,总体尴尬度为0,即最小。
首先,我们要(a-b)2最小,我们把式子展开得到(a2-2*a *b-b2),然后我们应该清楚,不管a和b位置如何,最后的结果a2和b2这两种情况是改不了的,所以我们只能从2ab上下手,然后通过这个式子我们也能看出,要想(a-b)2最小,那么2ab应该要最大。
此时问题初步变成了,如何排序能让2ab最大。这里涉及到一个知识(我觉着挺神奇),就是说如果两个数组相乘,有序的数组相乘会大于无序的数组相乘。(关于证明感兴趣的可以去上网学习一下),所以我们现在要做的就是把一个数组对于另一个数组相对有序:两边数组第一大的在同一个位置,第三大的在同一个位置,第二大的在同一个位置(不是说数组必须有序,而是相对有序)。
现在题目最终变成了,每次可以交换两个相邻的数,问两个数组变的相对有序,步数最少是几步。这一步就相当于是求逆序对了,我们固定一个数组的顺序,仅改变另一个数组的值。那我们就根据固定的数组为准来离散化另一个数组,然后求出这个数组的逆序对数量,至于求逆序对用线段树或者逆序对都是可以的。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 1e5 + 50, MOD = 1e8 - 7;
ll f[4 * N];
void revise(int k, int l, int r, int x)
{
if (l == r)
{
f[k] += 1;
return;
}
int m = (l + r) / 2;
if (x <= m)revise(k + k, l, m, x);
else revise(k + k + 1, m + 1, r, x);
f[k] = (f[k + k] + f[k + k + 1])%MOD;
}
ll calc(int k, int l, int r, int x, int y)
{
if (l == x && y == r)
{
return f[k];
}
int m = (l +r) / 2;
if (y <= m)return calc(k + k, l, m, x, y);
else
if (x > m)return calc(k + k + 1, m + 1, r, x, y);
else return (calc(k + k, l, m, x, m) + calc(k + k + 1, m + 1, r, m + 1, y))%MOD;
}
int main() {
int n;
cin >> n;
vector<PII>a(n), b(n);
for (int i = 0; i < n; i++)
{
cin >> a[i].first;
a[i].second = i;
}
for (int i = 0; i < n; i++)
{
cin >> b[i].first;
b[i].second = i;
}
sort(a.begin(), a.end());
sort(b.begin(), b.end());
vector<int>v(n);
for (int i = 0; i < n; i++)
{
v[a[i].second] = b[i].second + 1;
}
ll res = 0;
for (int i = n-1; i >= 0; i--)
{
if (v[i] != 1)res = (res + calc(1, 1, n, 1, v[i] - 1))%MOD;
revise(1, 1, n, v[i]);
}
cout << res << endl;
return 0;
}
CodeForces——线段树专题
C - Segment Tree, part 2 - Addition and First element at least X
There is an array of n elements, initially filled with zeros. You need to write a data structure that processes two types of queries:
add v to all elements on the segment from l to r−1,
find the minimum index j such that j≥l and a[j]≥x.
Input
The first line contains two numbers n and m (1≤n,m≤100000), the size of the array and the number of operations. The following lines contain the description of the operations. The description of each operation is as follows:
1 l r v: add v to all elements on the segment from l to r−1 (0≤i<n, 0≤v≤10^4).
2 x l: find the minimum index j such that j≥l and a[j]≥x (0≤x≤10^9, 0≤l<n). If there is no such element, print −1.
Indices start from 0.
Output
For each operation of the second type, print the answer for the query.
Example
input
5 7
1 2 5 3
1 1 3 4
2 3 0
2 5 0
1 4 5 5
2 5 3
2 8 1
output
1
2
4
4
题目是说给你一个初始为0的数组,进行两种操作,一个是把区间l~r的数都加上v,一种是让你找到下标大于等于j,且值大于等于x的第一个节点的位置。
区间加就是很基础的标记下传,这里主要讲第二种操作,线段树节点记录的是区间内的最大值,每次我们判断一下,如果左子节点的值大于x,那我们优先去左边找,如果找到的节点下标小于j,那我们就返回一个-1,然后回到上一层去右边找,也就是说我们这个线段树要有个回溯功能,如果再去右边找,找到的节点是小于x或是下标小于j的,那也返回-1。
但是这种做法,最坏情况下我们一次查询可能要把整个线段树跑一边,相当慢了,所以我们要减枝。当当前区间的最大值是小于x时,我们也不用纠结去左或右了,直接返回-1即可。或者当前区间的下标都是小于j的,我们也可直接返回-1。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 100500;
ll f[4 * N], b[4 * N], lmax[4 * N], rmax[4 * N], date[4 * N];
void push_down(ll k, ll l, ll r)
{
if (b[k] != 0)
{
int m = (l + r) / 2;
b[k + k] += b[k];
b[k + k + 1] += b[k];
f[k + k] += b[k];
f[k + k + 1] += b[k];
b[k] = 0;
}
}
void revise(ll k, ll l, ll r, ll x, ll y, ll v)
{
if (l == x && r == y)
{
b[k] += v;
f[k] += v;
return;
}
push_down(k, l, r);
int m = (l + r) / 2;
if (y <= m)revise(k + k, l, m, x, y, v);
else
if (x > m)revise(k + k + 1, m + 1, r, x, y, v);
else
{
revise(k + k, l, m, x, m, v);
revise(k + k + 1, m + 1, r, m + 1, y, v);
}
f[k] = max(f[k + k], f[k + k + 1]);
}
ll calc(int k, int l, int r, int x, int y)
{
if (l == r)
{
if (l < y || f[k] < x)return -1;
return l;
}
push_down(k, l, r);
int m = (l + r) / 2;
if (f[k] < x || r < y)return -1;
int res = -1;
if (f[k + k] >= x)res = calc(k + k, l, m, x, y);
if (res == -1)res = calc(k + k + 1, m + 1, r, x, y);
f[k] = max(f[k + k], f[k + k + 1]);
return res;
}
int main()
{
int n, m;
cin >> n >> m;
while (m--)
{
ll t, x, y, v;
cin >> t >> x >> y;
if (t == 1)
{
cin >> v;
revise(1, 1, n, x + 1, y, v);
}
else cout << max((ll)-1, calc(1, 1, n, x, y + 1) - 1) << endl;
}
return 0;
}