[题目大意]
给出一个长度为N的序列A,其中A[i]=i。然后对它依次进行M次操作:每一次操作用一个整数q[i]描述,表示构造一个无穷长的序列B=AAAAAA...,然后令A=B[1..q[i]]。M次操作全部结束后,对每一个i∈[1,N]询问:A中有多少项等于i。
[思路]
这题有毒!!(话说AGC系列哪个没毒??)
首先画几个序列观察一下吧:
n=3,m=2,q={8,21}
初始:A0=(1,2,3)
第一次操作后:A1=[(1,2,3),(1,2,3),1,2]
第二次操作后:A2={ [(1,2,3),(1,2,3),1,2] , [(1,2,3),(1,2,3),1,2] , (1,2,3),1,2}
我们发现,最终的序列Am满足一种"可拆"的优美性质:Am其实可以由[之前的Ai]+[长度不足N的A0的前缀]这两类序列拼接而成,比如上面A2=A0+A0+(1,2)+A0+A0+(1,2)+A0+(1,2),也可以这样写:A2=A1+A1+A0+(1,2)。
这启发我们倒过来,把Am拆分成更小的Ai,分治地解决问题。(先用单调栈把所有q预处理一下,去掉那些没有后面长的q(显然不影响答案),这样q就单调递增了,不会出现细节问题)
我们起初拥有一个序列Am,考虑令i从大到小,每一次将现在拥有的num[j]个Ai全部拆分掉:
二分找到最长的Aj,使得|Aj|<|Ai|,然后将Ai拆成Aj+Aj+...+Aj+B。
前面的一堆Aj都可以先不管,等到拆Aj的时候再处理。
可以发现这个B仍然满足“可拆”的优美性质,我们继续用上面那种方法去拆它,直到拆完,或者无法再拆得更小(|B|<=N)。这个时候,剩下的B就是A0的前缀,我们现在就把它的贡献处理掉:直接将计数器cnt[1..|B|]全部加num[i]即可。
这样从大到小,每一次将Ai拆成更小的A的组合,全部搞好后,原来的Am被拆成了若干A0以及A0的前缀,这些无法拆分的"零头"都已经在拆的过程中计入了答案。
最后直接输出计数器cnt[1..N]即为答案。
[复杂度]
整个算法过程由m次拆分组成,每一次拆分显然不会超过log(q[i])次,每一次用二分来计算。所以总复杂度O(mlogQlogm),其中Q是max{q[i]}。可以通过10^5的数据。
[总结]
做这类题目,用不到多少高端的算法,但要很快想到正解真的好困难。。只有仔细观察,永不言弃,多角度灵活尝试,才可能搞出正解!
Code:
#include <cstdio>
#define ll long long
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=1e5+5;
ll n,m,top,i,div,rest,q[N],num[N],s[N],stk[N];
ll erfen(ll x)
{
ll l=0,r=m,mid;
while (l<r)
{
if (r-l>1) mid=(l+r)>>1;
else mid=r;
if (q[mid]<x) l=mid;
else r=mid-1;
}
return l;
}
int main()
{
scanf("%lld%lld",&n,&m); q[1]=n;
rep(i,2,m+1) scanf("%lld",&q[i]);
rep(i,1,m+1)
{
while (q[i]<=stk[top]) top--;
stk[++top]=q[i];
}
m=top;
rep(i,1,m) q[i]=stk[i];
num[m]=1;
down(i,m,1)
{
if (!num[i]) continue;
rest=q[i];
while (rest>0)
{
div=erfen(rest);
if (!div) break;
num[div]+=rest/q[div]*num[i];
rest%=q[div];
}
s[1]+=num[i]; s[rest+1]-=num[i];
}
rep(i,1,n) { s[i]+=s[i-1]; printf("%lld\n",s[i]); }
return 0;
}