一、题目
二、解法
真是第一次见,这是一道 (dp) 维护直线函数的题。
设 (x_i=X \% a_1 \% a_2 ...\% a_i),如果 (x_i>0),那么 (X) 减 (1) 时 (x_1,x_2...x_i) 必定减 (1),考虑到 (i) 的答案是 (sum_{j=1}^i x_j),那么此种情况下答案就会减少 (i),这样我们找到了答案和 (X) 的关系:答案是 (icdot X+b) 的形式。
那么考虑 (dp) 来维护函数,设 (dp[i][j]) 考虑到 (i),函数 (x_ileq j) 的最大 (b),转移考虑添加模数 (a_{i+1})
-
如果 (j<a_{i+1}),那么这个模数对函数没有任何影响:(dp[i+1][j]leftarrow dp[i][j])
-
否则 (jgeq a_{i+1}),考虑函数的上界会被模成 (a_{i+1}-1),那么以前的函数只有两段最优,我们只需要考虑它们的转移。
设 (y=a_{i+1}-1+a_{i+1}lfloorfrac{j-a_{i+1}+1}{a_{i+1}} floor),也就是模 (a_{i+1}) 之后最大的数,这一段在原函数中是 ((y-a_{i+1},y]),取模之后相当于 (kin [0,a_{i+1})),那么取模之后函数的方程式:((i+1)cdot k+ia_{i+1}lfloorfrac{j-a_{i+1}+1}{a_{i+1}} floor+dp[i][j]),有转移:
还有一段是 ((y,j]),取模之后相当于 (kin[0,j\%a_{i+1}]),考虑 (j) 处原来的点值是 (ij+dp[i][j]),新的点值是 (ij+j\% a_{i+1}+dp[i][j]),函数斜率是 (i+1),写出方程式:((i+1)cdot k+i(j-j\% a_{i+1})+dp[i][j]),有转移:
现在来分析一波时间复杂度,有一个 (a_{i+1}-1) 位置的插入,每次转移都会使之减少 (frac{1}{2}),所以每个初始的位点会转移 (O(log a)) 次,用 ( t map) 维护 (dp) 数组,那么时间复杂度 (O(nlog nlog a))
三、总结
总结一下 (dp) 维护直线函数的注意事项,毕竟这是一个船新题型。
首先观察题目给的权值,如果是相加之类的一次操作但值域很大可以往这个方面考虑。
然后就是找函数关系式,看权值是否和某个量存在直线关系。
操作函数时(转移)考虑维护最大的 (b),那么取最优段转移即可,排除一定不优的转移。
#include <cstdio>
#include <map>
using namespace std;
#define int long long
#define mit map<int,int>::iterator
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,ans,a[M];map<int,int> mp;
signed main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();
mp[a[1]-1]=0;
for(int i=2;i<=n;i++)
for(mit it=mp.lower_bound(a[i]);it!=mp.end();mp.erase(it++))
{
int x=it->first,y=it->second;
mp[x%a[i]]=max(mp[x%a[i]],y+(i-1)*(x-x%a[i]));
mp[a[i]-1]=max(mp[a[i]-1],y+(i-1)*((x+1)/a[i]*a[i]-a[i]));
}
for(mit it=mp.begin();it!=mp.end();it++)
ans=max(ans,it->second+n*it->first);
printf("%lld
",ans);
}