初识莫队——小Z的袜子
以前一直觉得莫队是多么高大上的一种算法,然而仔细看了下发现其实并不复杂,实质上就是技巧性的暴力美学。
在我看来莫队是一种分块排序后降低复杂度的算法,当答案可以通过左右端点一个一个移动维护出来的时候就可以使用莫队了。
比如给你4个区间
(1, 2)
(99, 100)
(3, 4)
(101, 102)
如果你是傻傻的按照给定的顺序去移动左右端点,那就是先计算出(1,2)区间的值,然后左端点先从1移动到99,右端点从2移动到100,计算完(99,100)区间内的值,又哼哧哼哧的左端点从99移回3,右端点从100移回4,计算完(3,4)后又移动到(101,102),显然这样实在是太蠢了。
正常人都应该想到,那我把计算顺序改成(1,2)(3,4)(99,100)(101,102)不就行了,但是因为区间是二维的,简单的排序似乎是行不通的(通过lhs或者rhs来排序似乎都不太好)
莫队为我们提供了一个排序方法,假设有Q个询问,区间范围是1~N,那么可以对左端点分块,分成根号N块,这样就先把不同块的左端点排好序了,接下来同一块的询问根据右端点排序,这样就形成了一个(比较科学)的序列,可以使左右端点移动的距离变小,变相减小了复杂度。
实际上莫队就是一个科学的二维数组排序方法嘛
然后对于这道题因为是求概率,每次如果都维护一个分数很困难,所以选择每次维护分子和分母,这样这个概率公式就很好求了,分母只与区间长度有关,分子就是(每种袜子数目*(每种袜子数目-1))然后求和,化简后发现只要求(每种袜子数目)的平方的和就可以了,最后求一下gcd把分数表示成最简形式
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <math.h> 7 #include <string> 8 #include <algorithm> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #define lowbit(x) (x&-x) 15 #define goe(i, a, b) for(int i=a; i<=b; i++) 16 #define go(i, a, b) for(int i = a; i < b; i++); 17 #pragma warning ( disable : 4996 ) 18 19 using namespace std; 20 typedef long long LL; 21 inline LL LMax(LL a,LL b) { return a>b?a:b; } 22 inline LL LMin(LL a,LL b) { return a>b?b:a; } 23 inline LL lgcd( LL a, LL b ) { return b==0?a:lgcd(b,a%b); } 24 inline LL llcm( LL a, LL b ) { return a/lgcd(a,b)*b; } //a*b = gcd*lcm 25 inline int Max(int a,int b) { return a>b?a:b; } 26 inline int Min(int a,int b) { return a>b?b:a; } 27 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 28 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 29 const LL INF = 0x3f3f3f3f3f3f3f3f; 30 const LL mod = 1000000007; 31 const double eps = 1e-8; 32 const int inf = 0x3f3f3f3f; 33 const int maxk = 5e4+5; 34 const int maxn = 3e4+5; 35 36 int belong[maxk], wazi[maxk]; 37 int N, M, unit; 38 LL ans, cnt[maxk], fina[maxk], finb[maxk]; 39 struct query { 40 int lhs, rhs, id; 41 LL a, b; //分子和分母 42 }q[maxk]; 43 44 bool cmp(const query& a, const query& b) 45 { 46 if (belong[a.lhs] == belong[b.lhs]) return a.rhs < b.rhs; 47 return a.lhs < b.lhs; 48 } 49 50 void revise( int x, int d ) 51 { 52 //ans先减去以前的贡献再加上现在的贡献 53 ans -= cnt[wazi[x]]*cnt[wazi[x]]; 54 cnt[wazi[x]] += d; 55 ans += cnt[wazi[x]]*cnt[wazi[x]]; 56 } 57 58 void read() 59 { 60 scanf("%d %d", &N, &M); unit = sqrt(N); 61 62 63 goe(i, 1, N) { scanf("%d", &wazi[i]); belong[i] = i/unit+1; } 64 goe(i, 1, M) { scanf("%d %d", &q[i].lhs, &q[i].rhs); q[i].id = i; } 65 sort(q+1, q+1+M, cmp); 66 67 } 68 69 70 int main() 71 { 72 read(); 73 74 int l = 1, r = 0; 75 ans = 0; 76 77 goe(i, 1, M) 78 { 79 //如果是减操作,则先要执行revise, 80 //注意顺序 81 while(l < q[i].lhs) { revise(l, -1); l++; } 82 while(l > q[i].lhs) { l--; revise(l, 1); } 83 while(r < q[i].rhs) { r++; revise(r, 1); } 84 while(r > q[i].rhs) { revise(r, -1); r--; } 85 86 if ( q[i].lhs == q[i].rhs ) { q[i].a = 0; q[i].b = 1; continue; } 87 q[i].a = ans - (q[i].rhs - q[i].lhs + 1); 88 q[i].b = (LL)1*(q[i].rhs - q[i].lhs)*(q[i].rhs - q[i].lhs + 1); 89 90 LL Gcd = lgcd(q[i].a, q[i].b); 91 q[i].a /= Gcd; 92 q[i].b /= Gcd; 93 } 94 95 goe(i, 1, M) 96 { 97 fina[q[i].id] = q[i].a; 98 finb[q[i].id] = q[i].b; 99 } 100 101 goe(i, 1, M) 102 printf("%lld/%lld\n", fina[i], finb[i]); 103 104 return 0; 105 }
待修改的莫队,这就有点难受了,原先的二维区间变成了三维,加了一个时间维度(怎么感觉怪怪的),大意就是原先每个结构体多存储一个了时间变量,修改操作也通过时间变量储存,并且通过change这一数据结构将一系列修改的数据存储下来(即能通过时间确定修改的值,就像能操纵时间,随时可以通过时间还原原来的序列或者变化成修改后的序列),然后就是在原始莫队的基础上加上一个时间维度的移动,不得不说这个算法真是朴素而美妙...
HYSBZ 2120 数颜色---模板题
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <math.h> 7 #include <string> 8 #include <algorithm> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #define lowbit(x) (x&-x) 15 #define foe(i, a, b) for(int i=a; i<=b; i++) 16 #define fo(i, a, b) for(int i = a; i < b; i++); 17 #pragma warning ( disable : 4996 ) 18 19 using namespace std; 20 typedef long long LL; 21 inline LL LMax(LL a,LL b) { return a>b?a:b; } 22 inline LL LMin(LL a,LL b) { return a>b?b:a; } 23 inline LL lgcd( LL a, LL b ) { return b==0?a:lgcd(b,a%b); } 24 inline LL llcm( LL a, LL b ) { return a/lgcd(a,b)*b; } //a*b = gcd*lcm 25 inline int Max(int a,int b) { return a>b?a:b; } 26 inline int Min(int a,int b) { return a>b?b:a; } 27 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 28 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 29 const LL INF = 0x3f3f3f3f3f3f3f3f; 30 const LL mod = 1000000007; 31 const double eps = 1e-8; 32 const int inf = 0x3f3f3f3f; 33 const int maxk = 1e6+5; 34 const int maxn = 1e4+5; 35 36 37 int belong[maxn], num[maxn], now[maxn], fin[maxn]; 38 int N, Q, unit, l, r; 39 int t, T, Time, ans; 40 int cnt[maxk]; 41 42 struct query { 43 int lhs, rhs, tim, id; 44 query() {} 45 query(int l, int r, int t, int i) 46 : lhs(l), rhs(r), tim(t), id(i) {} 47 48 }q[maxn]; 49 50 // pos表示修改的位置,new表示修改后的值,Old表示修改前的值 51 struct change { 52 int pos, New, Old; 53 change() {} 54 change(int p, int n, int o) 55 : pos(p), New(n), Old(o) {} 56 }c[maxn]; 57 58 bool cmp(const query& a, const query& b) 59 { 60 if (belong[a.lhs != b.lhs]) return a.lhs < b.lhs; 61 if (belong[a.rhs != b.rhs]) return a.rhs < b.rhs; 62 return a.tim < b.tim; 63 } 64 65 void read() 66 { 67 //最优分块策略不再是根号 68 scanf("%d %d", &N, &Q); unit = pow((double)N, (double)0.666666); 69 foe(i, 1, N) { scanf("%d", &num[i]); now[i] = num[i]; belong[i] = i/unit+1; } 70 71 char str[2]; 72 int x, y; t = T = 0; 73 foe(i, 1, Q) 74 { 75 scanf("%s %d %d", str, &x, &y); 76 if (str[0] == 'Q') q[++t] = query(x, y, T, t); 77 if (str[0] == 'R') { c[++T] = change(x, y, now[x]); now[x] = y; } 78 } 79 sort(q+1, q+t+1, cmp); 80 } 81 82 void revise(int x, int d) 83 { 84 cnt[x] += d; 85 if (d > 0) 86 ans += (cnt[x] == 1); //只有从0加到1才会增加一种颜色数量 87 if (d < 0) 88 ans -= (cnt[x] == 0); 89 } 90 91 void reviseT(int x, int d) 92 { 93 if ( x >= l && x <= r ) 94 { 95 revise(d, 1); 96 revise(num[x], -1); 97 } 98 num[x] = d; 99 } 100 101 int main() 102 { 103 read(); 104 105 l = 1; r = 0; Time = 0; 106 foe(i, 1, t) 107 { 108 //若修改时间小于tim,则要添加新的修改 109 while(Time < q[i].tim) { Time++; reviseT(c[Time].pos, c[Time].New); } 110 //若time大于tim,则还原老修改 111 while(Time > q[i].tim) { reviseT(c[Time].pos, c[Time].Old); Time--; } 112 113 while(l < q[i].lhs) { revise(num[l], -1); l++; } 114 while(l > q[i].lhs) { l--; revise(num[l], 1); } 115 while(r < q[i].rhs) { r++; revise(num[r], 1); } 116 while(r > q[i].rhs) { revise(num[r], -1); r--; } 117 118 fin[q[i].id] = ans; 119 120 } 121 122 foe(i, 1, t) 123 printf("%d\n", fin[i]); 124 125 return 0; 126 }