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]\),有转移:

\[dp[i+1][a_{i+1}-1]\leftarrow dp[i][j]+ia_{i+1}\lfloor\frac{j-a_{i+1}+1}{a_{i+1}}\rfloor \]

还有一段是 \((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]\),有转移:

\[dp[i+1][j\% a_{i+1}]\leftarrow dp[i][j]+i(j-j\% a_{i+1}) \]

现在来分析一波时间复杂度,有一个 \(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);
}
posted @ 2021-07-30 21:39  C202044zxy  阅读(253)  评论(0编辑  收藏  举报