题意
有n个数a[i],现在有个指针i一直1..n,1..n的循环移动,每次指针移动到i时,a[i]可以在last+1..m中选一个数x,last是上一个被选的数,满足x<=a[i]。并给ans+=x,a[i]-=x.
最后指针必须要指向n,即必须要做完整的循环。
现在给出a[i],求出最大的ans.
30%:M≤20
另有10%:N=2
100%:1≤T≤5 2≤N≤500,000 N≤M≤5,000,000 1≤a[i]≤ (m*(m+1)/2)
分析
先确定循环数(行数),先放最小的一种方案,从1开始顺次取。
想象成一个n*m的表格。
然后考虑给这个方案up。发现问题就变成了怎么才能使得每一个数的+1次数最多。因为移动一次就等于答案+1。
又因为移动收益是相等的,为了使整体移动更多,当然从尾巴开始移动(腾出空位)。
这样就能得到一个暴力算法,枚举行数i,然后每次n*i的暴力移动,直到不能移动为止。
这个复杂度是
O(mn∗n∗m)
也就是
O(m2)
的。 (我觉得这里应该给部分分)
优化
现在来优化这个算法,注意到可以移动的步数最大为m-Max.
设有p行可以被整体最大移动m-Max,设k=n-p+1即第一行可以被最大移动的。
可以证明他不被最大移动,但可以移动的行就是只有接下来两行(即第k-1,k-2行)。
因为在移动第k-1行的时候,第i列可以移动的步数其实就是一个剩余a[i]的后缀min(初始min就是m-Max).因为要后面先动前面才能动。
那么就至少有一个位置的剩余步数会被使用完毕,这样在第k-2行这个位置是无法移动的,上面所有位置都无法移动了。
这样的话计算出能移动m-Max的行数,然后暴力两行即可。
至此得到了一个复杂度为 O(mn∗n)=O(m) 的算法。
Code
#include <cstdio>
#define N 500100
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
ll T;
ll n,m,a[N],ans,mi,b[N];
int main() {
freopen("compete.in","r",stdin);
freopen("compete.out","w",stdout);
for (cin>>T; T; T--) {
cin>>n>>m; ans=0; ll pr=0;
for (ll i=1; i<=n; i++) scanf("%lld",&a[i]);
ll ls=m/n;
for (ll i=1; i<=ls; i++) {
mi=(1ll<<60); ans=(1+i*n)*i*n/2;
for (ll j=1; j<=n; j++) a[j]-=i*n-n+j,mi=min(mi,a[j]);
if (mi<0) break;
ll hang=min(i,mi/(m-i*n));
ans+=n*hang*(m-i*n);
for (ll j=1; j<=n; j++) b[j]=a[j]-hang*(m-i*n);
ll mib=m-i*n;
for (ll z=i-hang; z>=i-hang-1 && z>0; z--) {
for (ll j=n; j; --j) {
mib=min(b[j],mib);
ans+=mib;
b[j]-=mib;
if (mib==0) break;
}
}
pr=max(pr,ans);
}
printf("%lld\n",pr);
}
}