[2015 Regional Online]長春C Aggregated Counting[打表找法则+分块打表]
じòぴé(′▽`)じòぴéじòぴéじòぴéじòぴéじòぴéじòぴéじòぴéじòぴぴéじòぴぴéじòぴぴéじòぴぴぴéじòぴぴé
ゆっくり話しましょう.2つの記号を定義する:d[i]は元のシーケンスのi番目のlast(n)は(タイトルに定義されている)を表し、元のシーケンスの中で最後にnが現れる位置の下付き(明らかにタイトルが要求され、last(last(n))を求める)そして、下書き紙に勝手に書き、明らかにlast(n)=Σi=1 nd[i]
もともと計画していたのは、nが大きいときのlast(n)をすばやく計算できるように書くことで、私は時計を打ってみることにしました.
そして私もその時何を考えていたのか思い出せませんでした(d[i]を2、3、4に区切って保存しているようです.....の値の位置で生み出せる数の最後の位置?どうせ冷静で論理的でない行為ですから…)
次のように書き換えます.
そして表を開けてみると、last(last(n))を求めたようです.トイレに行って落ち着いて、帰ってきて計算して、本当のようです!
そこで私たちは今新しい目標を持っていて、last(last(n))を表を打つ方法を通じて、計算を完成します.まず1つの問題を解決しなければならない:d[i],iの範囲は10億で、このようなint配列はメモリを爆発させて、開けられないでしょう......幸いにも私たちは:last(n)=Σi=1 nd[i]を持っています.そこで,d[i]の接頭辞和を用いて,nごとにd[n]を二分検索で位置決めし,その後上を探しても毎回二分をやり直す必要がなく,直接推定すればよい.そして、d[n]はどのくらい算出しておけばいいのでしょうか.時計を続けてlast(n)≧109のn値を見つけて、喜んで発見して、n=500000の時で十分です.もちろん、n≦109の場合、すべての打表は現実的ではなく、古典的な打表スキーム:ブロック打表--d個ごとに、答えを出力し、その後、所望の複雑度d/2を検索するたびに.喜んで次の表のプログラムを書きました.
注意して、ここは10万項目ごとに1つの値を打つのが多すぎて、100 KBの表は提出できません;20万件ごとに1つの値を打って、表が半分小さくて、順調に提出することができます.そして表を上の打表プログラムに似たコードに貼り付けて、複雑さを期待して運が良かったかも?提出するとTLEが・・・
そしてよく観察すると、実際にはd[i]の接頭辞とsum[i]を十分に利用していないことが分かった.nとそれに対応するd[n]は既知であり、sum[d[n]−1]≦i≦sum[d[n]]を満たすiについては、これらのiが最終結果に乗じたd[i]と同じであることが明らかになった.派手な50 KBのコードで、15 msと4 MBのメモリで解決しました・・・
コードは次のとおりです.
ゆっくり話しましょう.2つの記号を定義する:d[i]は元のシーケンスのi番目のlast(n)は(タイトルに定義されている)を表し、元のシーケンスの中で最後にnが現れる位置の下付き(明らかにタイトルが要求され、last(last(n))を求める)そして、下書き紙に勝手に書き、明らかにlast(n)=Σi=1 nd[i]
もともと計画していたのは、nが大きいときのlast(n)をすばやく計算できるように書くことで、私は時計を打ってみることにしました.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
int d[500005];
int main(){
freopen("03table.txt","w",stdout);
d[1]=1;
d[2]=2;
d[3]=2;
int lastp=3;
for(int i=3;lastp<=500000;i++){
for(int j=0;j<d[i]&&lastp<=500000;j++){
d[++lastp]=i;
}
}
ll sum=0;
for(int i=1;i<=500000;i++){
sum+=d[i];
sum%=1000000007;
printf("%d %d %I64d
",i,d[i],sum);
}
return 0;
}
そして私もその時何を考えていたのか思い出せませんでした(d[i]を2、3、4に区切って保存しているようです.....の値の位置で生み出せる数の最後の位置?どうせ冷静で論理的でない行為ですから…)
sum+=d[i];
次のように書き換えます.
sum+=d[i]*i;
そして表を開けてみると、last(last(n))を求めたようです.トイレに行って落ち着いて、帰ってきて計算して、本当のようです!
そこで私たちは今新しい目標を持っていて、last(last(n))を表を打つ方法を通じて、計算を完成します.まず1つの問題を解決しなければならない:d[i],iの範囲は10億で、このようなint配列はメモリを爆発させて、開けられないでしょう......幸いにも私たちは:last(n)=Σi=1 nd[i]を持っています.そこで,d[i]の接頭辞和を用いて,nごとにd[n]を二分検索で位置決めし,その後上を探しても毎回二分をやり直す必要がなく,直接推定すればよい.そして、d[n]はどのくらい算出しておけばいいのでしょうか.時計を続けてlast(n)≧109のn値を見つけて、喜んで発見して、n=500000の時で十分です.もちろん、n≦109の場合、すべての打表は現実的ではなく、古典的な打表スキーム:ブロック打表--d個ごとに、答えを出力し、その後、所望の複雑度d/2を検索するたびに.喜んで次の表のプログラムを書きました.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN=500000;
int d[MAXN+5];
int sum[MAXN+5];
int main(){
freopen("03tablecode.txt","w",stdout);
printf("0,");
d[1]=1;
d[2]=2;
d[3]=2;
int lastp=3;
for(int i=3;lastp<=MAXN;i++){
for(int j=0;j<d[i]&&lastp<=MAXN;j++){
d[++lastp]=i;
}
}
for(int i=1;i<=MAXN;i++)
sum[i]+=sum[i-1]+d[i];
int p=1;
ll presum=0;
for(int i=1;i<=1000000000;i++){
while(sum[p]<i)p++;
presum+=(ll)p*(ll)i;
presum%=1000000007;
if(i%200000==0)printf("%I64d,",presum);
}
return 0;
}
注意して、ここは10万項目ごとに1つの値を打つのが多すぎて、100 KBの表は提出できません;20万件ごとに1つの値を打って、表が半分小さくて、順調に提出することができます.そして表を上の打表プログラムに似たコードに貼り付けて、複雑さを期待して運が良かったかも?提出するとTLEが・・・
そしてよく観察すると、実際にはd[i]の接頭辞とsum[i]を十分に利用していないことが分かった.nとそれに対応するd[n]は既知であり、sum[d[n]−1]≦i≦sum[d[n]]を満たすiについては、これらのiが最終結果に乗じたd[i]と同じであることが明らかになった.派手な50 KBのコードで、15 msと4 MBのメモリで解決しました・・・
コードは次のとおりです.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXDN=500000;
int d[MAXDN+5];
int table[5005]={};// ,
void init(){
d[1]=1;
d[2]=2;
d[3]=2;
int lastp=3;
for(int i=3;lastp<=MAXDN;i++){
for(int j=0;j<d[i]&&lastp<=MAXDN;j++){
d[++lastp]=i;
}
}
for(int i=2;i<=MAXDN;i++)
d[i]+=d[i-1];
}
int main(){
init();
int T,n;
for(scanf("%d",&T);T--;){
scanf("%d",&n);
int goal=n/200000*200000;
int p=lower_bound(d+1,d+MAXDN+1,goal)-d;
int presum=table[n/200000];
for(int i=goal+1;i<=n;i++){
while(d[p]<i)p++;
//
int obs=(min(d[p],n)-i+1);
int fe=i+min(d[p],n);
ll tmp=(ll)fe*obs/2;
tmp%=1000000007;
// d[i] ,
presum+=(1LL*p*tmp)%1000000007;
if(presum>1000000007)presum-=1000000007;
i=d[p]; p++;
}
printf("%d
",presum);
}
return 0;
}