0%

[ARC169D] Add to Make a Permutation

一道有趣结论题。

但是感觉官方题解说的真的有点糊。

题目链接

题意

给定一个长度为 的数组 与一个数字 ,你可以进行若干次操作:

选中 个数字,并将他们 ,若有数字到达 ,则将这个数字变为

求将 变为一个 的排列的最小次数,或输出无解。

题解

我们假设 在值为 时不会变成 ,并令最后的结果为 ,那么我们的最终目标是让 互不相等。

,考虑 合法的条件:

考虑在步数最小的情况下,我们求出一个 最小的

可以证明,这个 一定是 的一个排列,可以使用反证法来证明:

设当前 合法,满足步数最小且 最小,且存在

由于 ,所以 会变小。

以前三个条件仍然满足:

  1. ,所以 不变,所以仍然有

  2. 所以仍然有

  3. 所以仍然有

综上所述,步数最小且 最小的 一定为 的排列的形式。

也就是说,我们只需要考虑 的排列的形式。

首先我们将 从小到大排序,不难发现,此时的 递增是一定是最优的。

所以我们只需要找出一个最小的 ,使得 满足上面的条件。

不难发现,条件 给出的是 的下界,设

对于条件 ,有

对于条件 ,有

条件 则给出了关于 的额外限制,设

首先,若 ,则无解;否则,可以暴力枚举,算出 的值(不存在则无解)。

结合三个条件,便可以算出最小的 ,便能算出最小的操作次数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int a[250001];
ll s,ans;
int n,m;
int main()
{
n=read(),m=read(),ans=-inf;
for(int i=1;i<=n;i++) s+=(a[i]=read());
sort(a+1,a+n+1);
for(int i=n;i;i--) ans=max(ans-1,(ll)a[i]);
for(int i=1;i<=n;i++)
{
ll A=(ll)n*(n-1)/2-s,B=(i-1)-a[i];
ans=max(ans,(m*B-A+n-m-1)/(n-m));
}
ll C=(s-(ll)n*(n-1)/2%m+m)%m;int val=-1,g=__gcd(n,m);
for(int i=0;i<m/g;i++) if((ll)n/g*i%(m/g)*g==C){ val=i;break;}
if(val==-1){ printf("-1\n");return 0;}
ans=(ans-val+(m/g)-1)/(m/g)*(m/g)+val;
printf("%lld\n",((ll)n*(n-1)/2+ans*n-s)/m);
return 0;
}

感谢观看!