[atAGC062D]Walk Around Neighborhood
记\(D=\max_{1\le i\le n}d_{i}\),则无解当且仅当\(2D>\sum_{i=1}^{n}d_{i}\)
结论:\(\forall (x,y),\exists (X,Y),\begin{cases}|X|+|Y|=R\\|x-X|+|y-Y|=d\end{cases}\) 当且仅当\(|r-R|\le d\le r+R\)(其中\(r=|x|+|y|\))
必要性:根据\(|a|-|b|\le |a-b|\le |a|+|b|\),显然成立
充分性:根据对称性,不妨假设\(x,y\ge 0\)
- 若\(d=r+R\),则取\((X,Y)=(-R,0)\)即可
- 若\(d=|r-R|\),则取\((X,Y)=\begin{cases}(x+(R-r),y)&r\le R\\(x-(r-R),y)&r>R\and r-R\le x\\(0,y-((r-R)-x))&r>R\and r-R>x\end{cases}\) 即可
由于\(\{(X,Y)\mid |X|+|Y|=R\}\)构成正方形,进而\(|x-X|+|y-Y|\)的取值构成区间,即得证
根据结论,问题即转化为以下形式:
选择排列\(d_{i}\)和\(r_{i}\),要求\(\begin{cases}r_{0}=r_{n}=0\\\forall i\in [1,n],|r_{i-1}-r_{i}|\le d_{i}\le r_{i-1}+r_{i}\end{cases}\),并最小化\(\max_{1\le i\le n}r_{i}\)
二分枚举答案\(s\),显然有解的必要条件为\(s\ge \frac{D}{2}\)
当确定\(d_{i}\)后,每个\(r_{i}\)的取值构成区间,转移为\([x,y]\rightarrow [\min_{x\le i\le y}|i-d|,\min(y+d,s)]\)
- 对于\(d\le s\),必然可以转移,且\(y=s\)时转移后为\([\max(x-s,0),y]\)
- 对于\(d>s\),转移时需有\(y\ge d-s\),且转移后为\([d-y,s]\)
取\(a,b\)分别为最小/最大的\(i\)满足\(d_{i}>s\),则应有\(\begin{cases}\sum_{i=1}^{a-1}d_{i}\ge d_{a}-s\\\sum_{i=b+1}^{n}d_{i}\ge d_{b}-s\end{cases}\),且其余\(d_{i}\)均存在合适的位置
可以贪心取\(d_{a},d_{b}\)为\(d_{i}>s\)的最、次小值(若唯一则均为\(D\)),即转化为对\(d_{i}\le s\)的背包
具体的,记\(S=\sum_{d_{i}\le s}d_{i}\),即判断是否存在子集和\(\in [d_{a}-s,S-(d_{b}-s)]\)
当区间长度\(\ge s\)时必然有解,进而\(S-(d_{b}-s)-(d_{a}-s)+1<s\),放缩得\(S\le 2D\)
根据经典结论,不同的\(d_{i}\)个数为\(O(\sqrt{D})\),并可以利用加入过程将二分改为枚举,时间复杂度为\(O(D\sqrt{D})\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200005;
int n,s,ans,a[N],cnt[N],g[N<<1],f[N<<1];
ll sum;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
cnt[a[i]]++;
}
sort(a+1,a+n+1);
for(int i=1;i<n;i++)sum+=a[i];
if (sum<a[n]){
puts("-1");
return 0;
}
s=sum=0,ans=a[n],f[0]=1;
for(int i=1;i<a[n];i++){
if (cnt[i]){
s+=cnt[i];
if (sum<=(a[n]<<1)){
for(int j=sum;j;j--)f[j]-=f[j-1];
}
sum+=(ll)i*cnt[i];
if (sum<=(a[n]<<1)){
int s=i*(cnt[i]+1);
for(int j=0;j<=sum;j++)g[j]=f[j]+(j<i ? 0 : g[j-i]);
for(int j=0;j<=sum;j++)f[j]=(g[j]>(j<s ? 0 : g[j-s]));
for(int j=1;j<=sum;j++)f[j]+=f[j-1];
}
}
if (i>=(a[n]>>1)){
if (sum>(a[n]<<1)){ans=i;break;}
int x=a[s+1]-i,y=sum-(a[min(s+2,n)]-i);
if ((x<=y)&&(f[y]>f[x-1])){ans=i;break;}
}
}
printf("%d\n",ans);
return 0;
}