CF889E Mod Mod Mod
一、题目
二、解法
真是第一次见,这是一道 \(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\) 的关系:答案是 \(i\cdot X+b\) 的形式。
那么考虑 \(dp\) 来维护函数,设 \(dp[i][j]\) 考虑到 \(i\),函数 \(x_i\leq j\) 的最大 \(b\),转移考虑添加模数 \(a_{i+1}\)
-
如果 \(j<a_{i+1}\),那么这个模数对函数没有任何影响:\(dp[i+1][j]\leftarrow dp[i][j]\)
-
否则 \(j\geq a_{i+1}\),考虑函数的上界会被模成 \(a_{i+1}-1\),那么以前的函数只有两段最优,我们只需要考虑它们的转移。
设 \(y=a_{i+1}-1+a_{i+1}\lfloor\frac{j-a_{i+1}+1}{a_{i+1}}\rfloor\),也就是模 \(a_{i+1}\) 之后最大的数,这一段在原函数中是 \((y-a_{i+1},y]\),取模之后相当于 \(k\in [0,a_{i+1})\),那么取模之后函数的方程式:\((i+1)\cdot k+ia_{i+1}\lfloor\frac{j-a_{i+1}+1}{a_{i+1}}\rfloor+dp[i][j]\),有转移:
还有一段是 \((y,j]\),取模之后相当于 \(k\in[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)\) 次,用 \(\tt map\) 维护 \(dp\) 数组,那么时间复杂度 \(O(n\log n\log 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\n",ans);
}