[WerKeyTom_FTD的模拟赛]刻画在历史舞台上的群星
2016-11-03 21:17
141 查看
题目背景
受愤怒权能情感伤害链接的影响,昴死亡轮回了三次。在第四次的轮回中,与贝蒂一起使用魔法杀马克屏蔽了愤怒权能,但艾米莉亚不敌愤怒火焰权能,激战中又被强欲司教劫走,于是留下歌姬和普莉希拉单挑愤怒,昴与莱因哈鲁特前去营救艾米。同时,色欲司教的袭击广播,奥托遇到到处吞噬的暴食司教,复活的尸体特雷西亚的出现,水门都市各个战场战斗激化。王选阵营VS大罪司教,他们,是刻画在历史舞台上的群星。
题目描述
广场站着一排人,在观看着挟持小男孩的愤怒司教的演说,一共有n个,编号为1~n,第i个人的编号为i。第i个人,有一个恐惧程度值为ai。如果[l,r]的人被愤怒司教使用权能链接,那么对于l<=i<=j<=r,便会产生(a[i]+a[i+1]+……a[j-1]+a[j]) mod p那么多的恐惧总值,其中p是给定的一个常数。
为了更好的应战愤怒司教,现在有q个询问,每个询问给定区间[l,r],请你找出对于任意l<=i<=j<=r,[i,j]产生恐惧总值的最小值。
输入格式
第一行两个正整数表示n,p。接下来一行,n个整数,表示序列a。
接下来一行一个正整数表示询问个数q。
接下来q行,每行两个正整数l,r表示一次询问。
输出格式
q行每行一个样例输入
3 31 1 2
3
1 2
2 3
3 3
样例输出
10
2
数据范围及约定
40%,2<=p<=n<=50,q<=5060%,2<=p<=n<=100,q<=100
70%,2<=p<=n<=1000,q<=2000
80%,2<=p<=n<=200000,q<=2000
前80%数据共8个测试点,每个测试点10分,时限均为1s。后20%数据共4个测试点,每个测试点5分。
第九个测试点:2<=p<=n<=2000000,q<=2000,时限3s
第十个测试点:2<=p<=n<=6000000,q<=2000,时限3.5s
第十一个测试点:2<=p<=n<=6000000,q<=2000,时限2s
第十二个测试点:2<=p<=n<=6000000,q<=2000,时限1.5s
其中,本题的数据保证随机生成,生成方法如下:
每个测试点有一个对应的常数len,前70%数据的len约等于n的3/4,后30%的len值如下。
第八个测试点:len为2650
第九个测试点:len为8500
后三个测试点:len为14500
对于a序列,每个ai均在[0,p-1]中等概率随机生成。
对于每组询问,其有3/4的概率满足条件一,1/4的概率满足条件2
条件1:被询问区间的区间大小<=len
条件2:被询问区间的区间大小>len
被询问区间的区间大小x在满足条件情况下等概率随机生成,然后等概率随机生成符合条件的右端点,相减得到左端点。
请注意对读入进行优化。
请注意常数带来的影响。
请注意利用题目的条件优化程序。
题目来源
改编自《小奇的数列》题目背景相关
题目名选自《Re:从零开始的异世界生活》第五章标题“刻画在历史舞台上的群星”,本章讲述了解决圣域问题后的一年后,五大王选阵营因为各种契机集合在水门都市商议,接着水门都市遭到愤怒、强欲、暴食、色欲的大罪司教的进攻,众人一同对敌的故事。40%算法
暴力枚举统计答案。复杂度为O(n^3q)。
60%算法
记录前缀和,计算区间和使用前缀和快速得出,其余暴力。复杂度为O(n^2q)。
70%算法
对于每个询问,我们要找l<=i<=j<=r中,(sum[j]-sum[i-1]) mod p的最小值。顺序扫描,对于枚举的一个j,如何找到这个i?假如sum[j]是新的最小值,那么这个i便是前面的最大值。否则,我们要找一个不比sum[j]大的最小数。
要支持插入和查询,可以使用线段树维护。
复杂度o(nq log p)
80%算法
优化一
相信这个优化大家一定都会。在扫描过程中,如果答案已经为0,那么就退出。
我们如何合理分析这个算法的复杂度呢?
首先根据a的随机方法,第i个位置是j的概率P(a[i]为j)=1/p
我们考虑sum。
先给出结论:P(sum[i]为j)=1/p
当i=1时,显然是正确的,显然sum[1]=a[1]嘛。
如果i=n时结论正确,那么i=n+1时结论也正确。
P(sum[i+1]为j)=sigma{P(sum[i]为k)*P(a[i+1]为(j-k)mod p)}
后面部分为1/(p^2),一共p项加起来为1/p。
因此归纳完毕。
然后呢,每次我们期望会在多少步后就结束算法?
假设i步就结算,那么花费了i步,概率是i-1步内未结束,在第i步时结束了。P(i-1步内未结束)=P(前i-1个互不相同)=(p/p)[(p-1)/p][(p-2)/p]……[(p-i)/p]=p!/[(p-i-1)!*p^(i-1)]
P(在第i步恰好结束)=P(i-1步内未结束)P(第i个与前i-1个中某一个相同)=p!/[(p-i-1)!*p^(i-1)](i-1)/p
对期望的贡献为P(在第i步恰好结束)*i
我们计算一下2*10^5时的期望,大约为560。
2*10^6时的期望,大约为1773。
6*10^6时的期望,大约为3070。
记这个期望步数为a。
那么复杂度为aq log p。
优化二
我们都知道抽屉原理,对于本题,如果询问的区间长度比p还大,那么一定会出现两个相同的,因此答案为0。然而这并没有什么用,因为p和n是同阶的。
我们思考抽屉原理的本质,那就是P(长度为p+1的区间互不相同)=0
假如我们定义一个常数b,那么P(长度为b的区间互不相同)是多少?
可以发现,在80%数据下,b随便一取概率已经是小的不行了。
这个其实叫生日悖论。
因此我们可以考虑一个优化,设置一个常数b,每次询问的区间长度大于b时,就认为答案为0,直接退出。只要我们合理设置b,可以保证程序错误率很小。
怎么计算错误率呢?用优化一的方法可以得到单词错误率,那么程序的正确率为(1-单次错误率)^q,再用1减得到程序的错误率。
我们发现题目条件中有个神秘常数len,事实上,代入题目中给定的len可以发现程序的错误率极小。你设置的b可以直接用len,也可以再小一些。
题目的每组询问有1/4的概率生成长度大于len的区间,因此期望情况下,我们会去执行的区间只有0.75q个。
加了优化后,只有<=b的区间会被执行,那么单次询问最坏复杂度是b log p,总复杂度为0.75bq log p。
将优化一与优化二一起使用,单次询问变成期望复杂度a log p。
总复杂度为0.75aq log p。
85%算法
我们应该如何继续优化每次询问的复杂度呢。注意到每次做的区间长度都很小,但是因为值域是p很大,线段树又是静态的,那么我们会很慢。
因此考虑到使用复杂度动态的平衡树,使用任意一种应该都能过,直接调用STL中的set也行,set求前驱的方法是lower_bound,如果不想改变set的大小比较,可以考虑把数变成相反数放进去,取出来再取相反数即可。
总复杂度0.75aq log b
90%算法
到这里我们考虑优化常数。STL的set是很大常数的,写splay无疑也是很慢的,因为了解splay势能分析的都知道splay带了一个3的常数。可以选择跑的很快的treap。
treap的原理大家应该都知道了。每次新插入的新点如果为t,旋转到了k,那么t的fix在k子树内一定最小,概率为1/size[k],旋转一次t的深度减一,旋转的复杂度是O(1),那么这样复杂度就是d[t]-d[k]+1,对期望贡献为(d[t]-d[k]+1)*1/size[k],我们又知道size[k]>=(d[t]-d[k]+1),所以这个东西<=1。t一共有log n个父亲,所以每次的期望复杂度<=log n。
95%算法
计算过程其实并不会很慢,那么慢的地方到底在哪里呢?n=6000000,读入就超时了啊。
所以我们对读入进行优化,用fread即可。
100%算法
将算法各个地方的常数优化得好一点,就可以过。通常情况下IO优化都加了,不要写太丑,应该都能过了。如果过不了,你可以尝试一下下面这个优化。
优化三
我们观察前两个优化,显然都在考虑如何快速判断一个询问答案是否为0。基于这个思想,我们可以预处理出right[i]表示最小的j>i,满足sum[i]=sum[j]。
那么在做每个询问前,可以先扫一遍[l-1,r-1],如果存在一个i满足right[i]<=r,那么这个询问答案一定为0。
我们检验所需的复杂度?首先因为优化2我们只考虑<=b的区间,那么根据随机方法每种长度区间出现概率均为1/b。我们枚举区间长度x,然后枚举在第i步时,那么前i个一定要互不相同,这是不在i步之前停下的概率。后面x-i个全部不和前i个相同,是(p-i)^(x-i),再用1减,这是在i步可以停下的概率,相乘得到恰好在i步停下的概率。
计算出n=6000000时检验的期望复杂度为2655,设这个常数为c。
这个优化关键还在于有多少个答案为0的区间,根据计算<=b的区间有1/5的概率不为0,那么这些区间就被我们略去了,剩余的区间计算复杂度和原来一致。
总复杂度0.75*0.2(c+b log b)q+0.75*0.8cq
你计算一下发现这样子比原来还慢,所以我们考虑不在每次询问前都去检验,而是类似优化一那样,只是退出条件由答案为0改成了找到后面有相同的。
这样子做总复杂度0.75cq log b,比原来快,不过本题q不大,只是常数优化。
注意使用优化三也要配合优化二!
优化三的改进
我们来追求更快的判断一个询问是否答案为0。注意到[l,r]如果答案为0,那么存在一个在[l-1,r-1]范围中的i满足next[i]<=r,由于满足next[i]<=r的i一定<=r-1,因此只需要满足i>=l-1。
题目没有强制我们在线,那么把[l,r]挂在l-1上,接着我们离线的处理所有询问。扫描线倒着扫,维护next的最小值,每次处理掉挂在左端点的所有询问,因为维护了next的最小值能很轻易的判断一个区间的答案是否为0。
那么总复杂度为0.75*0.2bq log b。
标程
#include<cstdio> #include<algorithm> #include<ctime> #define fo(i,a,b) for(i=a;i<=b;i++) #define fd(i,a,b) for(i=a;i>=b;i--) using namespace std; const int maxn=6000000+10,maxq=2000+10,len=15000,R=8000000*10; char buf[R+7],*ptr=buf-1; int sum[maxn],sta[100],next[maxn],last[maxn]; int father[maxn],key[maxn],tree[maxn][2],fix[maxn]; int ask[maxq][2],num[maxq],h[maxn],go[maxq],n2[maxq]; int i,j,k,l,r,t,n,m,p,q,ans,top,tot,root,mx,mi; bool czy; int read(){ int x=0,c=*++ptr; while(c<48)c=*++ptr; while(c>47)x=x*10+c-48,c=*++ptr; return x; } void add(int x,int y){ go[++tot]=y; n2[tot]=h[x]; h[x]=tot; } int pd(int x){ if (x==tree[father[x]][0]) return 0;else return 1; } void rotate(int x){ int y=father[x],z=pd(x); father[x]=father[y]; if (father[y]) tree[father[y]][pd(y)]=x; tree[y][z]=tree[x][1-z]; if (tree[x][1-z]) father[tree[x][1-z]]=y; tree[x][1-z]=y; father[y]=x; if (root==y) root=x; } void insert(int x,int y,int v){ if (!x){ x=++tot; key[x]=v; tree[x][0]=tree[x][1]=0; father[x]=y; if (y){ if (v<=key[y]) tree[y][0]=x;else tree[y][1]=x; } fix[x]=rand(); return; } if (v<=key[x]){ insert(tree[x][0],x,v); if (fix[tree[x][0]]<fix[x]) rotate(tree[x][0]); } else{ insert(tree[x][1],x,v); if (fix[tree[x][1]]<fix[x]) rotate(tree[x][1]); } } void work(int l,int r,int id){ int i,t,k; tot=root=0; insert(root,0,sum[l-1]); root=1; mx=mi=sum[l-1]; ans=p; fo(i,l,r){ if (ans==1) break; /*if (next[i]<=r){ ans=0; break; }*/ if (sum[i]<mi){ if (sum[i]-mx+p<ans) ans=sum[i]-mx+p; } else{ t=root;k=0; while (t){ if (key[t]>sum[i]) t=tree[t][0]; else{ if (key[t]>k) k=key[t]; t=tree[t][1]; } } if (sum[i]-k<ans) ans=sum[i]-k; } if (sum[i]<mi) mi=sum[i]; if (sum[i]>mx) mx=sum[i]; insert(root,0,sum[i]); } num[id]=ans; } void write(int x){ if (!x){ putchar('0'); putchar('\n'); return; } top=0; while (x){ sta[++top]=x%10; x/=10; } while (top){ putchar('0'+sta[top]); top--; } putchar('\n'); } int main(){ freopen("history.in","r",stdin);freopen("history.out","w",stdout); fread(buf,1,R,stdin); srand(time(0)); n=read();p=read(); fo(i,1,n){ t=read(); sum[i]=(sum[i-1]+t)%p; } fo(i,0,p-1) last[i]=n+1; fd(i,n,0){ next[i]=last[sum[i]]; last[sum[i]]=i; } q=read(); tot=0; fo(i,1,q){ ask[i][0]=read();ask[i][1]=read(); add(ask[i][0]-1,i); } r=n+1; fd(i,n-1,0){ if (next[i]<r) r=next[i]; t=h[i]; while (t){ if (r>ask[go[t]][1]) work(ask[go[t]][0],ask[go[t]][1],go[t]); t=n2[t]; } } fo(i,1,q) write(num[i]); }
题目情况
80分:1人70分:3人
60分:5人
0分:1人
平均分:59
想说的话
jason80分好厉害。毕竟两个优化才加我才给80,jason有其他蜜汁方法过了80。Drin_E也厉害,fread+优化二三+线段树+蜜汁优化艹过了(考试后过的别误解)
相关文章推荐
- [WerKeyTom_FTD的模拟赛]Sone0
- [WerKeyTom_FTD的模拟赛]永恒的契约
- [bzoj4899][WerKeyTom_FTD的模拟赛]记忆的轮廓
- 个人电脑将退出历史舞台
- 模拟京东网上商城--商品浏览历史
- Winamp退出历史舞台?
- 朋友网,是时候退出历史的舞台了
- PHP4即将退出历史舞台
- 实锤!再也不用纠结版本的选择了,Python2.7 即将退出历史舞台
- 线程退出历史舞台
- 有线键盘和鼠标确实应该退出历史舞台了
- PHP4即将退出历史舞台
- 腾讯soso退出历史舞台,搜索结果全部由搜狗提供
- C语言将会推出历史的舞台吗?
- 以太网巨人3Com退出历史舞台
- 感谢这个时代让云计算登上了历史舞台,开创了云的新时代。
- PHP4即将退出历史舞台
- PHP4即将退出历史舞台
- 以太网巨人3Com退出历史舞台
- PHP4即将退出历史舞台