bzoj2038: [2009国家集训队]小Z的袜子(hose)
2017-01-24 00:12
375 查看
链接
http://www.lydsy.com/JudgeOnline/problem.php?id=2038题解
式子的推导 & 大暴力
把问题抽象一下,原序列有N个数,给出M次询问每次询问一段[L,R],问你在这段区间随便选两个数字,不相同的概率是多少。这是一个古典概型,计算公式ans=符合条件的数对数目总的数对数目,对于一段确定的区间,分母肯定是(R−L+1)(R−L)2,考虑分子,如果有2个1,3个3,那么情况就是从2个1中选出2个1,或者从3个三种选出2个3,即C22+C23,那就是说如果有多种数字出现次数分别为a,b,c,d...,那么这个问题的答案就是ans=C2a+C2b+C2c+...(R−L+1)(R−L)2
化简得
ans=a(a−1)+b(b−1)+c(c−1)+...(R−L+1)(R−L)
观察式子发现,对于一次询问,分母只和L,R有关,这很简洁,可以只考虑分子。一个新加入的元素只会对分子的一项造成影响,形如,a(a−1)变成a(a+1),这两个式子只差了一个2a,所以给代表分子的变量直接加上2a就好了,同时a++以进行下一次计算。考虑一个元素的删除,就是让a(a−1)变成(a−1)(a−2),相当于直接给分子加(−2a+2),同时a−−以进行下一次运算。
我直接做大暴力,对于每个询问直接扫描区间,复杂度O(NM),结果18s过了.
莫队
这个算法很神奇,首先进行分块。以N−−√为每块的大小,把整个序列分成N−−√块(关于为什么是N−−√,你可以设每块的大小为size,然后计算一下整个算法的时间复杂度,结果是N(size+Nsize),再用均值不等式求最值,会发现当size=N−−√时整个算法的时间复杂度取到最小值NN−−√)
将询问进行排序,左端点所在的块的编号是第一关键字,右端点是第二关键字。然后暴力做即可,转移时使用上面推出的O(1)转移。
复杂度的分析:
因为询问已经排好序了,所以所有的询问可以看做大致的N−−√个部分,每一个部分内左端点的距离不大于N−−√。如果把块与块之间的转移单独拿出来(均摊O(N)),那么所有左端点之间转移的复杂度是O(N+MN−−√)。因为所有询问被分为N−−√块,每块内右端点是有序的,所以右端点转移的复杂度为O(NN−−√)。
综上,如果转移在O(T)的复杂度内完成,那么莫队算法的复杂度是O{T[N+(N+M)N−−√]}
对于这道题目T=1,而且M和N是同级别的所以时间复杂度为O(NN−−√)
代码
AC的暴力//暴力 #include <cstdio> #include <algorithm> #include <cmath> #define ll long long #define maxn 51000 using namespace std; ll N, M, col[maxn], cnt[maxn]; struct Quiry{ll l, r, ans1, ans2, id;}quiry[maxn]; bool operator<(Quiry q1, Quiry q2){return q1.l==q2.l?q1.r<q2.r:q1.l<q2.l;} bool cmp(Quiry q1, Quiry q2){return q1.id<q2.id;} void init() { ll i; scanf("%lld%lld",&N,&M); for(i=0;i<N;i++)scanf("%lld",col+i); for(i=1;i<=M;i++) scanf("%lld%lld",&quiry[i].l,&quiry[i].r), quiry[i].l--,quiry[i].r--,quiry[i].id=i; sort(quiry+1,quiry+M+1); quiry[0].l=-1; } int gcd(ll a, ll b){return !b?a:gcd(b,a%b);} void calc(ll a, ll b, ll num) { if(a==0){quiry[num].ans1=0,quiry[num].ans2=1;return;} quiry[num].ans1=a/gcd(a,b), quiry[num].ans2=b/gcd(a,b); } void solve() { ll l=-1, r=-1, i, fz=0; for(i=1;i<=M;i++) { for(;l<quiry[i].l;l++)if(l!=-1)fz+=-2*cnt[col[l]]--+2; for(;r>quiry[i].r;r--)fz+=-2*cnt[col[r]]--+2; for(r++;r<=quiry[i].r;r++)fz+=2*cnt[col[r]]++;r=quiry[i].r; calc(fz,(quiry[i].r-quiry[i].l+1)*(quiry[i].r-quiry[i].l),i); } } int main() { init(); solve(); sort(quiry+1,quiry+M+1,cmp); for(ll i=1;i<=M;i++)printf("%lld/%lld\n",quiry[i].ans1,quiry[i].ans2); return 0; }
莫队
#include <cstdio> #include <algorithm> #include <cmath> #define maxn 50010 #define ll long long using namespace std; ll col[maxn], lp[maxn], cnt[maxn], num[maxn], quiry[maxn][2], N, M, ans[maxn][2], size; bool cmp(ll a, ll b) { ll la=quiry[a][0], lb=quiry[b][0], ra=quiry[a][1], rb=quiry[b][1]; return lp[la]==lp[lb]?ra<rb:lp[la]<lp[lb]; } void input() { ll i, size; scanf("%lld%lld",&N,&M);size=sqrt(N); for(i=1;i<=N;i++)scanf("%lld",col+i),lp[i]=i/size; for(i=1;i<=M;i++)num[i]=i,scanf("%lld%lld",quiry[i],quiry[i]+1); sort(num+1,num+M+1,cmp); } ll gcd(ll a, ll b){return !b?a:gcd(b,a%b);} void solve() { ll i, l=0, r=0, ql, qr, fz=0; for(i=1;i<=M;i++) { ql=quiry[num[i]][0], qr=quiry[num[i]][1]; for(;l<ql;l++)if(l)fz+=-2*cnt[col[l]]--+2; for(l--;l>=ql;l--)if(l)fz+=2*cnt[col[l]]++;l=ql; for(;r>qr;r--)if(r)fz+=-2*cnt[col[r]]--+2; for(r++;r<=qr;r++)if(r)fz+=2*cnt[col[r]]++;r=qr; if(fz==0)ans[num[i]][0]=0,ans[num[i]][1]=1; else { ll L=qr-ql+1, g=gcd(L*(L-1),fz); ans[num[i]][0]=fz/g, ans[num[i]][1]=(L-1)*L/g; } } for(i=1;i<=M;i++)printf("%lld/%lld\n",ans[i][0],ans[i][1]); } int main() { input(); solve(); return 0; }
相关文章推荐
- Hdu1423 Greatest Common Increasing Subsequence
- java栈的实现
- 有关Android的外部拉起
- C++ 线程-类方式
- C++网络编程
- 排序
- 2016书单总结--看透SpringMvc源代码分析与实践-概述
- c++思考题
- Windows DLL开发笔记
- C++ SQL 语句格式化
- 【PAT】1048. Find Coins
- 学习hibernate_02_双向onetoone注解
- 【机器学习】数据预处理
- jsoncpp 使用详解
- convert 3D matrix into diagonal block matrix
- DLL开发的问题
- Java+mysql用户注册登录
- 使用Visual调试库检测内存泄露
- hdu5862 Counting Intersections
- C++连接MySQL