G
题意
- 给你长度为
N
N
N 的序列
A
、
B
A、B
A、B
你必须操作 K K K 次,每次操作需要选择两个数字 A i , A j A_i,A_j Ai,Aj,然后交换他们
让你最大 ∑ ∣ A i − B i ∣ \sum|A_i-B_i| ∑∣Ai−Bi∣ - 1 ≤ N ≤ 5 e 5 1\le N\le 5e5 1≤N≤5e5
思路
- 首先,
N
>
2
N>2
N>2 时必须操作
K
K
K 次和至多操作
K
K
K 次在答案上是等价的
因为有些交换不会影响答案的点对,交换他们即可 - 交换一次,我们增加了什么?
- 也就是说,我们希望每次最大化
min
(
A
i
,
B
i
)
−
max
(
A
j
,
B
j
)
\min(A_i,B_i)-\max(A_j,B_j)
min(Ai,Bi)−max(Aj,Bj)
我们设 S [ i ] = − max ( A i , B i ) S[i]=-\max(A_i,B_i) S[i]=−max(Ai,Bi), R [ i ] = min ( A i , B i ) R[i]=\min(A_i,B_i) R[i]=min(Ai,Bi)
然后我们 s o r t sort sort 升序他们,每次拿最大的 S [ i ] S[i] S[i] 和 R [ i ] R[i] R[i],然后答案加上 2 ( S [ i ] + R [ i ] ) 2(S[i]+R[i]) 2(S[i]+R[i]),就是目前交换一次的最大收益 - 当然前提是:目前还有交换的次数,以及交换这一次对答案是正贡献的
还有 N = 2 N=2 N=2 的情况,交换操作是唯一的,特判一下即可
代码
- 时间复杂度: O ( N log N ) O(N\log N) O(NlogN)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
const int MAX = 5e5+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;
ll aa[MAX],bb[MAX];
ll AA[MAX],BB[MAX];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i)scanf("%lld",&aa[i]);
for(int i = 1;i <= n;++i)scanf("%lld",&bb[i]);
ll ans = 0;
if(n == 2){
if(k&1)swap(aa[1],aa[2]);
for(int i = 1;i <= n;++i){
ans += abs(aa[i] - bb[i]);
}
}else{
for(int i = 1;i <= n;++i){
ans += abs(aa[i] - bb[i]);
AA[i] = -max(aa[i],bb[i]);
BB[i] = min(aa[i],bb[i]);
}
sort(AA+1,AA+1+n);
sort(BB+1,BB+1+n);
for(int i = n;i > n - k && i >= 1 && AA[i] + BB[i] > 0;--i){
ans += 2*(AA[i] + BB[i]);
}
}
printf("%lld",ans);
return 0;
}
/**
*/
K
题意
- 给你一个长度为
N
N
N 的序列
b
[
N
]
b[N]
b[N],满足
b
[
i
]
∈
[
0
,
n
−
1
]
b[i]\in[0,n-1]
b[i]∈[0,n−1]
让你重排列它,然后最小化这个值:
∑ i = 0 N − 1 i − b i \sum_{i=0}^{N-1}\sqrt{i-b_i} i=0∑N−1i−bi
最后答案必须和标准答案均差 ≤ 4 % \le 4\% ≤4% - 10 ≤ N ≤ 1 0 3 10\le N\le 10^3 10≤N≤103
思路
- 因为和标准答案有较小误差,于是我们想到贪心(???怎么想到的)
我们不能直接单纯 s o r t sort sort ,因为根号函数导数越来越小,不像二次函数导数越来越大
比如 b [ ] = { 1 , 2 , 3 } b[]=\{1,2,3\} b[]={1,2,3},答案为 3 3 3,但是我们让 b [ ] = { 3 , 1 , 2 } b[]=\{3,1,2\} b[]={3,1,2},答案就是 3 \sqrt3 3 更优了 - 我们记录一下每个数字的出现次数,记作
c
n
t
[
i
]
cnt[i]
cnt[i]
我们记录 a n s [ i ] ans[i] ans[i] 表示下标为 i i i 最终放什么 - 我们优先考虑怎么放可以让
i
−
b
i
=
0
i-b_i=0
i−bi=0,然后优先考虑怎么放让
i
−
b
i
=
1
i-b_i=1
i−bi=1,然后诸如此类
也就是我们先枚举差 i = 0 t o n i=0\ to\ n i=0 to n,然后枚举放的位置 j j j
那么,我们只要去找是否存在 c n t [ j − i ] cnt[j-i] cnt[j−i] 与 c n t [ j + i ] cnt[j+i] cnt[j+i],有的话直接拿过来放在这个位置 - 官方:经过试验,这样操作大多数误差是在
2.8
%
2.8\%
2.8% 的
(???)
好吧,以后这种有误差题都会优先去考虑贪心吧 - 对了,如果数据比较小,可以使用 K M KM KM 算法, O ( N 3 ) O(N^3) O(N3) 算出正确的答案,样例数据也都是官方这样跑出来的
代码
- 时间复杂度: O ( N 2 ) O(N^2) O(N2)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
const int MAX = 1e3+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;
int ans[MAX],cnt[MAX];
int main()
{
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
for(int i = 0;i <= n;++i)ans[i] = -1,cnt[i] = 0;
for(int i = 0;i < n;++i){
int t;scanf("%d",&t);
cnt[t]++;
}
for(int i = 0;i <= n;++i)
for(int j = 0;j < n;++j){
if(ans[j] != -1)continue;
int L = j-i,R = j+i;
if(L>=0 && cnt[L])ans[j]=L,cnt[L]--;
else if(R<n && cnt[R])ans[j]=R,cnt[R]--;
}
for(int i = 0;i < n;++i)
printf("%d ",ans[i]);
puts("");
}
return 0;
}
/**
*/