AtCoder Regular Contest 123

D.Inc,Dec-Decomposition

题目描述

点此看题

给一个长度为 \(n\)\(A\) 序列,试构造单调不降的 \(B\) 和单调不增的 \(C\),满足 \(a_i=b_i+c_i\) 并且最小化代价:

\[\sum_{i=1}^n|b_i|+|c_i| \]

\(n\leq 2\cdot 10^5,-10^8\leq a_i\leq 10^8\)

解法

这题 \(\tt slope\ trick\) 做不带脑子,还是先把前置知识看一下吧。

首先写出暴力 \(dp\),管它多大的数直接塞状态里,设 \(dp[i][j]\) 表示考虑到 \(i\),并且 \(b_i=j\) 的最小代价,转移:

\[dp[i][j]=dp[i-1][k]+|j|+|a_i-j| \ \ \ k\leq j,a_{i-1}-k\geq a_i-j \]

发现新增的代价是绝对值的形式,可以看成折线函数直接合并上去,然后考虑限制 \(k\leq j\) 是一个取前缀最小值的操作,但是 \(k-(a_{i-1}-a_i)\leq j\) 就不是单纯的前缀最小值了,但是可以把 \(k\) 先往右平移 \(a_i-a_{i-1}\),然后再取前缀最小值,直接打整体标记就行了。

时间复杂度 \(O(n\log n)\),折线算法总是超级超级短\(\sim\)

总结

\(\tt slope\ trick\) 可以优化 \(dp\),直接写暴力方程式然后观察代价与限制即可,打整体标记是常用方法。

真正在做题的时候先考虑操作的顺序,比如这题必须先插入两条绝对值折线之后再取最小值。再讨论插入点和 \(0\) 斜率线的关系,取的最优决策点应该既在原来的 \(0\) 斜率线上又在当前的 \(0\) 斜率线上,本题可以通过讨论证明一定存在这样的点。

\(2021/10/8\) 补充:注意每个位置实际上会有两个重合的转移点。

#include <cstdio>
#include <queue>
using namespace std;
const int M = 200005;
#define int long long
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,sh,ans,a[M];priority_queue<int> q;
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();int x=a[i],y=0;
		if(i>1) sh+=max(a[i]-a[i-1],0ll);//tag
		q.push(-sh);q.push(-sh);//insert the change point
		q.push(a[i]-sh);q.push(a[i]-sh);
		q.pop();y=q.top()+sh;q.pop();//pre min
		ans+=2*y-x;//update the 0-slope-line
	}
	printf("%lld\n",ans);
}

F.Insert Addition

题目描述

点此看题

一个序列 \(p\),定义生成序列 \(f(p)\) 为把 \(p_i+p_{i+1}\) 插入到 \(i,i+1\) 中间生成的序列。

初始时 \(A=(a,b)\),我们把 \(A\) 替换成 \(f(A)\) 进行 \(n\) 次,然后得到删除所有 \(>n\) 的数,得到序列 \(B\),求 \(B_L...B_R\) 具体的值。

\(a,b\leq n\leq 3\cdot 10^5,L\leq R\leq 10^{18},R-L<3\cdot 10^5\)

前言

首先我想用贡献法直接求出 \(n\) 次后 \(B\) 某个位置的值,发现根本就不会,因为这题是拿来给你找性质的。

你最好先看一下 Stern-Brocot树,本题的很多性质都可以通过类比它获得(所以说学算法要看证明)。

解法

首先声明 \(a,b\) 不是很重要,因为我们可以把原数写成系数对的形式,比如这样:

\[\begin{matrix} (1,0)&(0,1)\\ (1,0)&(1,1)&(0,1)\\ (1,0)&(2,1)&(1,1)&(1,2)&(0,1)\\ (1,0)&(3,1)&(2,1)&(3,2)&(1,1)&(2,3)&(1,2)&(1,3)&(0,1) \end{matrix} \]

我继续声明 \(n\) 不是很重要,因为生成新数的大小永远大于当前迭代次数,所以 \(n\) 的增大只是再 \(B\) 序列中激活了更多旧数,那么我们可以考虑充分多次迭代后保留 \(\leq n\) 的数,这和原问题是等价的。

结合你对系数对的观察和类比 \(\tt Stern-Brocot\) 树,我们开始找性质。

性质一

如果 \((x_1,y_1),(x_2,y_2)\) 在系数数列中相邻,那么 \(x_1y_2-x_2y_1=1\)

证明考虑归纳法,首先第一行是成立的,我们考虑 \(x_3=x_1+x_2,y_3=y_1+y_2\) 时,\((x_1,y_1),(x_3,y_3)\) 有没有此性质:

\[x_1y_3-x_3y_1=x_1(y_1+y_2)-(x_1+x_2)y_1=x_1y_2-x_2y_1=1 \]

对于 \((x_3,y_3),(x_1,y_1)\) 也可类似证得,所以可以推至所有情况。

性质二

如果 \(x_1y_2-x_2y_1=1\) 并且 \(0\leq x_1,y_1,x_2,y_2\),那么 \((x_1,y_1)\)\((x_2,y_2)\) 在某时刻的系数序列中会相邻。

\((1,0)\)\((0,1)\) 开始归纳,我们逐步增大这些系数对的值,分为下面两种情况归纳:

  • \(x_1\leq x_2,y_1\leq y_2\)
  • \(x_1\geq x_2,y_1\geq y_2\)

证明第一种情况可以归纳,设 \((x_3,y_3)=(x_2-x_1,y_2-y_1)\),由先前的假设可知 \(x_1y_3-x_3y_1=1\),那么生成的 \((x_2,y_2)\) 就满足 \(x_1y_2-x_2y_1=1\),并且它们在系数数列中相邻,另一种情况类似证明即可。

推论

如果 \(x,y\) 满足 \(\gcd (x,y)=1\),那么 \((x,y)\) 会出现在充分多次迭代后的系数数列,且系数数列只包含这样的 \((x,y)\)

考虑方程 \(xx'-yy'=1\),当且仅当满足 \(\gcd(x,y)=1\) 时有解 \((x',y')\),根据性质二可以知道 \((x,y)\) 存在的充要条件是 \(\gcd(x,y)=1\),并且由归纳法可知 \((x,y)\) 只会出现一次。


查询可以当成在一棵二叉树上跑,根据线段树的复杂度可知跑的次数是 \(O(\log L)\)

那么需要解决的问题是查询某个点子树内大小 \(\leq n\) 数的个数,树上的点可以用生成它的两个端点 \((a,b)\) 表示(注意此时我们用整数而不用系数对了),也就是求满足 \(i\cdot a+j\cdot b\leq n\) 并且 \(\gcd(i,j)=1\) 的点对 \((i,j)\) 的个数。

为了解决 \(\gcd(i,j)=1\),显然可以莫比乌斯反演,设 \(f(n)\) 表示满足上述条件的点对个数,\(F(n)\) 则为不考虑 \(\gcd(i,j)=1\) 的点对个数,那么 \(f(n)=\sum_{i}\mu(i)F(\frac{n}{i})\),可以暴力枚举 \(i\) 然后暴力算 \(F(i)\),时间复杂度 \(O(n\log n)\)

如果遇到一个点的子树都在 \([L,R]\) 的范围中就直接访问这个子树把答案输出,是均摊 \(O(n)\) 的,总时间复杂度 \(O(n\log n\log L)\),官方题解在这一部分的做法根本看不懂,所以不要去管他。

总结

分析性质的重要方法是去除无关量,比如这题把 \(n,a,b\) 全部都不管,问题就在充分迭代背景下发生了。

归纳法需要从初始状态找性质,然后尝试推广到以后的状态,它很适用于充分迭代的问题,类比学过的结构也是找性质的重要方法。

总所周知 \(\tt stern-brocot\) 树可以看成一棵线段树,这题就是很好的例子。

#include <cstdio>
#define int long long
const int M = 300005;
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,a,b,L,R,cnt,p[M],mu[M],vis[M];
void init(int n)
{
	mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) p[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
			mu[i*p[j]]=-mu[i];
		}
	}
}
void print(int x)
{
	if(L>R || x>n) return ;
	if(L==1) printf("%lld ",x),R--;
	else L--,R--;
}
void pall(int x,int y)
{
	if(x+y>n) return ;
	pall(x,x+y);print(x+y);pall(x+y,y);
}
int cal(int p,int q)
{
	int r=0;
	for(int i=1;p+q<=n/i;i++)
	{
		if(!mu[i]) continue;
		for(int j=1;p*j+q<=n/i;j++)
			r+=mu[i]*(n/i-p*j)/q;
	}
	return r;
}
void dfs(int a,int b)
{
	int m=cal(a,b);
	if(L>R || !m) return ;
	if(L>m) {L-=m;R-=m;return ;}
	if(L==1 && m<=R) {pall(a,b);return ;}
	dfs(a,a+b);print(a+b);dfs(a+b,b);
}
signed main()
{
	a=read();b=read();n=read();L=read();R=read();
	init(n);
	print(a);dfs(a,b);print(b);
}
posted @ 2021-07-20 16:41  C202044zxy  阅读(180)  评论(6编辑  收藏  举报