【莫队/树上莫队/回滚莫队】原理详解及例题:小B的询问(普通莫队),Count on a tree II(树上莫队),kangaroos(回滚莫队)

文章目录

  • 问题引入
  • 介绍莫队算法及其实现过程
  • 时间复杂度
  • 莫队算法适用范围
  • 莫队奇偶优化
  • 普通莫队:小B的询问
  • 树上莫队:SP10707 COT2 - Count on a tree II
  • 回滚莫队:[PA2011]Kangaroos

upd:2021-08-11:重新对博客进行了外观美化修正,以及新增树上莫队
upd:2021-08-19:新增回滚莫队

问题引入

给定一个大小为NNN的数组,数组中所有元素的大小≤N\le NN。你需要回答MMM个查询。
每个查询的形式是L,RL,RL,R。你需要回答在范围[L,R][L,R][L,R]中至少重复222次的数字的个数


如果按照以往的想法,就会是O(n2)O(n^2)O(n2)的暴力枚举

for ( int i = 1;i <= Q;i ++ ) {scanf ( "%d %d", &l, &r );for ( int j = l;j <= r;j ++ ) {count[a[j]] ++;if ( count[a[j]] == 3 )result ++;}}

就算加一些优化,用l,rl,rl,r采取指针转移,但总归上还是在[1,n][1,n][1,n]区间内进行移动

最坏多半也是逼近于O(n2)O(n^2)O(n2)

void add ( int x ) {count[a[x]] ++;if ( count[a[x]] == 3 )result ++;
}
void removed ( int x ) {count[a[x]] --;if ( count[a[x]] == 2 )result --;
}
for ( int i = 1;i <= m;i ++ ) {scanf ( "%d %d", &l, &r );while ( curl < l )removed ( curl ++ );while ( curl > l )add ( -- curl );while ( curr > r )removed ( curr -- );while ( curr < r )add ( ++ curr );printf ( "%d\n", result );
}

add ​添加该位置的元素到当前集合内,并且更新答案

remove 从当前集合内删除该位置的元素,并更新答案


那么这个时候莫队算法就重磅登场了

在这里插入图片描述

为什么叫做莫队算法呢?

据说算法是由之前的国家队队长莫涛发明的,他的队友平日里称他为莫队,所以称之为莫队算法


介绍莫队算法及其实现过程

莫队算法就是一个离线算法,仅仅调整了处理查询的顺序

实现过程如下:

  • 将给定的输入数组分为n\sqrt{n}n​​​​​块。每一块的大小为 nn\frac{n}{\sqrt{n}}nn

    每个LLL​​​​落入其中的一块,每个RRR也落入其中的一块

    如果某查询的LLL​​​落在第iii​​块中,则该查询属于第iii​块​​

  • 所有的询问首先按照所在块的编号升序排列(所在块的编号是指询问的L属于的块

    如果编号相同,则按R值升序排列

  • 莫队算法将依次处理第111块中的查询,然后处理第222.........直到最后一块

    有很多的查询属于同一块

e.g.

假设我们有333​​个大小为333​的块(0−2,3−5,6−8)(0-2,3-5,6-8)(02,35,68){0,3}{1,7}{2,8}{7,8}{4,8}{4,4}{1,2}\{0,3\} \{1, 7\} \{2, 8\} \{7, 8\} \{4, 8\} \{4, 4\} \{1, 2\}{0,3}{1,7}{2,8}{7,8}{4,8}{4,4}{1,2}

先根据所在块的编号重新排列它们

  • 111块:{0,3}{1,7}{2,8}{1,2}\{0, 3\} \{1, 7\} \{2, 8\} \{1, 2\}{0,3}{1,7}{2,8}{1,2}
  • 222块:{4,8}{4,4}\{4, 8\} \{4, 4\}{4,8}{4,4}
  • 333​块:{7,8}\{7, 8\}{7,8}

接下来按照R的值重新排列

  • 第一块:{1,2}{0,3}{1,7}{2,8}\{1, 2\} \{0, 3\} \{1, 7\} \{2, 8\}{1,2}{0,3}{1,7}{2,8}
  • 第二块:{4,4}{4,8}\{4, 4\} \{4, 8\}{4,4}{4,8}
  • 第三块: {7,8}\{7, 8\}{7,8}

上述过程只是重新排列了查询的顺序
在这里插入图片描述


时间复杂度

我们说了这么多,选用莫队算法无非就是想把时间复杂度给降下来

接下来我们来看看真正莫队的时间复杂度是多少,其实我看了很多博客也是有点懵逼

上面的代码就是起一个铺垫作用,所有查询的复杂性是由444个``while`循环决定的

222​​个while循环可以理解为左指针curl的移动总量

222​​个 while循环可以理解为右指针curr的移动总量

这两者的和将是总复杂性


先算右指针

对于每个块,查询是递增的顺序排序,所以右指针curr按照递增的顺序移动

在下一个块的开始时,指针可能在最右端,将移动到下一个块中的最小的RRR

又可以从本块最左端移动到最右端

这意味着对于一个给定的块,右指针移动的量是O(n)O(n)O(n)(curr可以从111跑到最后的nnn

我们有O(n)O(\sqrt{n})O(n)​块,所以总共是O(nn)O(n\sqrt{n})O(nn)​​


接下来看看左指针怎样移动

对于每个块,所有查询的左指针落在同一个块中,从一个查询移动到下一个查询左指针会移动

但由于前一个LLL​与下一个LLL在同一块中,此移动是O(n)O(\sqrt{n})O(n)​​​(块的大小)

在每一块中左指针的移动总量是O(Qn)O(Q\sqrt{n})O(Qn)​​,(QQQ是落在那个块的查询的数量)

对于所有的块,总的复杂度为O(m∗n)O(m∗\sqrt{n})O(mn)​​


综上,总复杂度为O((n+m)∗n)=O(n∗n)O((n+m)∗\sqrt{n})=O(n∗\sqrt n)O((n+m)n)=O(nn)

实在无法理解就跳过吧(如果有通俗易懂的解释欢迎评论)

莫队算法适用范围

首先莫队算法是一个离线算法,所以如果问题是在线操作带修或者强制特殊的顺序

莫队就失去了它的效应
在这里插入图片描述


其次一个重要的限制性:addremove的操作

当有些题目的addremove耗时很大,O(N)O(\sqrt N)O(N)​​​​时就应该思考能否卡过

因为莫队本身就是一种优美的暴力而已

但是还是有很大一部分区间查询的题可以由莫队进行完成

莫队奇偶优化

sqt = sqrt( n )
bool cmp( node x, node y ) {return ( x.l / sqt == y.l / sqt ) ? ( ( ( x.l / sqt ) & 1 ) ? x.r < y.r : x.r > y.r ) : x.l < y.l;
}

在这里插入图片描述

普通莫队:小B的询问

小B有一个序列,包含N个1~K之间的整数。他一共有M个询问,
每个询问给定一个区间[L…R],求Sigma(c(i)^2)的值,
其中i的值从1到K,其中c(i)表示数字i在[L…R]中的重复次数。
小B请你帮助他回答询问。

输入格式
第一行,三个整数N、M、K。
第二行,N个整数,表示小B的序列。
接下来的M行,每行两个整数L、R。
输出格式
M行,每行一个整数,其中第i行的整数表示第i个询问的答案。

输入输出样例
输入
6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
输出
6
9
5
2
说明/提示
对于全部的数据,1<=N、M、K<=50000

简单题解

说了是算法模板入门题,肯定不会把你拒之门外,还是要让你摸摸门的

这个题就是要简单处理一下∑ci2∑c_i^2ci2​,当ci±1c_i±1ci±1​时,答案会发生怎样的转化?

完全平方公式大家都会吧!!!👇

(c[i]−1)2=c[i]2−2∗c[i]+1(c[i]-1)^2=c[i]^2-2*c[i]+1(c[i]1)2=c[i]22c[i]+1

(c[i]+1)2=c[i]2+2∗c[i]+1(c[i]+1)^2=c[i]^2+2*c[i]+1(c[i]+1)2=c[i]2+2c[i]+1

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
#define LL l;ong long
#define MAXN 50005
struct node {int l, r, num;
}G[MAXN];
int n, m, k, apart, curl = 1, curr; 
int a[MAXN], cnt[MAXN];
LL result;
LL ans[MAXN];bool cmp ( node x, node y ) {return ( x.l / apart == y.l / apart ) ? x.r < y.r : x.l < y.l;
}void add ( int x ) {result += ( cnt[a[x]] << 1 ) + 1;cnt[a[x]] ++;
}
void removed ( int x ) {result -= ( cnt[a[x]] << 1 ) - 1;cnt[a[x]] --;
}int main() {scanf ( "%d %d %d", &n, &m, &k );for ( int i = 1;i <= n;i ++ )scanf ( "%d", &a[i] );apart = sqrt ( n );for ( int i = 1;i <= m;i ++ ) {scanf ( "%d %d", &G[i].l, &G[i].r );G[i].num = i;}sort ( G + 1, G + m + 1, cmp );for ( int i = 1;i <= m;i ++ ) {int l = G[i].l, r = G[i].r;while ( curl < l ) {removed ( curl ++ );}while ( curl > l ) {add ( -- curl );}while ( curr > r ) {removed ( curr -- );}while ( curr < r ) {add ( ++ curr );}ans[G[i].num] = result;}for ( int i = 1;i <= m;i ++ )printf ( "%lld\n", ans[i] );return 0;
}

树上莫队:SP10707 COT2 - Count on a tree II

顾名思义就是把序列莫队搬到树上实现

分块的大小以及移动的操作与序列莫队无差别

唯一的区别就在于询问的l,r

在树上莫队询问的l,rl,rl,r​要用欧拉序进行重新编号

e.g.

fasdfa

原图的欧拉序为1,2,4,6,6,7,7,5,5,4,2,3,3,1,对于点iiili:l_i:li: 第一次访问iiiri:r_i:ri: 最后一次访问iii

树上莫队就是用欧拉序的li,ril_i,r_ili,ri代替访问的iii

对于查询的(u,v)有两种情况

  • 是直系祖先关系(假设uuuvvv的祖先)

    e.g. : u=2,v=7u=2,v=7u=2,v=7​​,拿出[lu,lv][l_u,l_v][lu,lv]​代替u,vu,vu,v

    欧拉序为2,4,6,6,7

  • 不隶属同一棵子树(假设lu<lvl_u<l_vlu<lv

    e.g : u=7,v=3u=7,v=3u=7,v=3​,拿出[ru,lv][r_u,l_v][ru,lv]

    欧拉序为7,5,5,4,2,3

不在路径上的点经过了恰好两次,真正在路径上的点都恰好只出现一次;对于不同子树的两点需要额外加上lca

source

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 40005
#define maxm 100005
vector < int > G[maxn];
int n, Q, B, cnt, ans;
bool vis[maxn];
int c[maxn], MS[maxn], tot[maxn], ret[maxm];
int dep[maxn], l[maxn], r[maxn], id[maxn << 1];
int f[maxn][20];
struct node {int l, r, id, lca;
}q[maxm];void dfs( int u, int fa ) {dep[u] = dep[fa] + 1, l[u] = ++ cnt, id[cnt] = u;for( int i = 1;i <= 16;i ++ )f[u][i] = f[f[u][i - 1]][i - 1];for( auto v : G[u] ) {if( v == fa ) continue;else f[v][0] = u, dfs( v, u );}r[u] = ++ cnt, id[cnt] = u;
}int get_lca( int u, int v ) {if( dep[u] < dep[v] ) swap( u, v );for( int i = 16;~ i;i -- )if( dep[f[u][i]] >= dep[v] ) u = f[u][i];if( u == v ) return u;for( int i = 16;~ i;i -- )if( f[u][i] != f[v][i] ) u = f[u][i], v = f[v][i];return f[u][0];
}void Delete( int x ) { if( -- tot[c[x]] == 0 ) ans --; }void Insert( int x ) { if( ++ tot[c[x]] == 1 ) ans ++; }void modify( int x ) { vis[x] ? Delete( x ) : Insert( x ); vis[x] ^= 1; }int main() {scanf( "%d %d", &n, &Q );for( int i = 1;i <= n;i ++ )scanf( "%d", &c[i] ), MS[i] = c[i];sort( MS + 1, MS + n + 1 );int m = unique( MS + 1, MS + n + 1 ) - MS - 1;for( int i = 1;i <= n;i ++ )c[i] = lower_bound( MS + 1, MS + m + 1, c[i] ) - MS;B = sqrt( n );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}dfs( 1, 0 );for( int i = 1, u, v;i <= Q;i ++ ) {scanf( "%d %d", &u, &v );q[i].id = i;if( l[u] > l[v] ) swap( u, v );int lca = get_lca( u, v );if( u == lca ) q[i].l = l[u], q[i].r = l[v], q[i].lca = 0;else q[i].l = r[u], q[i].r = l[v], q[i].lca = lca;}sort( q + 1, q + Q + 1, []( node x, node y ) { return ( x.l / B == y.l / B ) ? ( x.r < y.r ) : ( x.l < y.l ); } );int curl = 1, curr = 0;for( int i = 1;i <= Q;i ++ ) {while( curl < q[i].l ) modify( id[curl ++] );while( q[i].l < curl ) modify( id[-- curl] );while( curr < q[i].r ) modify( id[++ curr] );while( q[i].r < curr ) modify( id[curr --] );if( q[i].lca ) modify( q[i].lca );ret[q[i].id] = ans;if( q[i].lca ) modify( q[i].lca );}for( int i = 1;i <= Q;i ++ )printf( "%d\n", ret[i] );
}

回滚莫队:[PA2011]Kangaroos

普通莫队是能做到快速增添和删减操作的

但有些题目在区间转移时,可能会出现增加或者删除无法实现的问题

在只有增加不可实现或者只有删除不可实现的时候,就可以使用回滚莫队

同样在O(nn)O(n\sqrt n)O(nn)的时间内解决问题

回滚莫队的核心思想就是既然只能实现一个操作,那么就只使用一个操作,剩下的交给回滚解决

回滚莫队分为只使用增加操作的回滚莫队和只使用删除操作的回滚莫队

以只使用添加操作的回滚莫队为例

  • 首先仍是按照区间左端点分块排序,然后右端点为第二关键字
  • 枚举区间左端点的块
    • 对于询问的左右端点都在同一区间的直接暴力做
    • 将左端点都在该块的按右端点升序排序
    • 每次都是右端点右移,加入答案
    • 左端点每次都回滚到这个块的末尾,然后暴力的往前移到本次询问的左端点处,记录这一路上的答案,但是不像右端点一样是永久的
      再回退到块末尾,这样的时间复杂度,mmm个询问,每次询问左端点最多暴力移整个区间n\sqrt nn,还是根号级别的复杂度
  • 到下一块的时候,l,rl,rl,r一起遍历了整个区间,扔掉所有的标记那些,然后手动重置为初始局面

只支持删除的话,我想应该就是右端点rrr递减,不断前移,然后还是左端点lll在块中反复移动清空

可以看一下这道题→\rightarrow 回滚莫队的例题——历史研究

Kangaroos比较难一点

区间过大,实际上区间相交只关系左右端点的大小关系,所以先离散化区间的端点,最多只有2n2n2n

然后按询问左端点分块,将询问挂到块上

接下来就是回滚莫队问题了

这道题主要是要维护最大连续区间

每次将新增区间的下标扔进去的时候,更新

可以用线段树维护最大连续区间,但是实际上可以用l[],r[]\rm l[],r[]l[],r[]数组来维护左右端点,省去log\rm loglog

这只是简单口胡,看不懂可以看代码,代码里有详细注释

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200005
#define maxB 320
struct node {int l, r, id;node(){}node( int L, int R, int ID ) {l = L, r = R, id = ID;}
}t[maxn];
struct Node {int val, id;Node(){}Node( int Val, int ID ) {val = Val, id = ID;}
}x[maxn];
vector < node > q[maxB];
int n, m, ans, top;
int block[maxn], L[maxB], R[maxB], l[maxn], r[maxn], ret[maxn];bool operator < ( Node s, Node t ) {return s.val == t.val ? s.id < t.id : s.val < t.val;
}struct opt {//记录操作前的相关信息 方便回滚 //flag 0:左 1:右 记录被更改的是哪边 int flag, pos, lst, ans;opt(){}opt( int  Flag, int Pos, int Lst, int Ans ) {flag = Flag, pos = Pos, lst = Lst, ans = Ans;}
}s[maxn << 4];
//l[i]:在当前已加入区间中i区间所能连的最左边区间下标 -> [l(i),i]的所有区间都与当前查询区间有交 
//r[i]:在当前已加入区间中i区间所能连的最右边区间下标 -> [i,r(i)]的所有区间都与当前查询区间有交 
void add( int i ) {if( l[i] || r[i] ) return;//已经算过了i的贡献 if( ! l[i - 1] and ! r[i + 1] ) {//左右的区间都还没有被加进来//只能自己这一个区间 长度为1 s[++ top] = opt( 0, i, l[i], ans );s[++ top] = opt( 1, i, r[i], ans );l[i] = r[i] = i, ans = max( ans, 1 );}else if( ! l[i - 1] ) {//i+1的区间被加入了 可以往左延伸和i接上 s[++ top] = opt( 0, r[i + 1], l[r[i + 1]], ans );s[++ top] = opt( 1, i, r[i], ans );r[i] = r[i + 1], l[r[i + 1]] = i;ans = max( ans, r[i] - i + 1 );}else if( ! r[i + 1] ) {//i-1的区间被加入了 可以往右延伸和i接上 s[++ top] = opt( 1, l[i - 1], r[l[i - 1]], ans );s[++ top] = opt( 0, i, l[i], ans );l[i] = l[i - 1], r[l[i - 1]] = i;ans = max( ans, i - l[i] + 1 );}else {//i-1 i+1都被加入 直接左右连接拼起来 s[++ top] = opt( 0, r[i + 1], l[r[i + 1]], ans );s[++ top] = opt( 1, l[i - 1], r[l[i - 1]], ans );s[++ top] = opt( 0, i, l[i], ans );s[++ top] = opt( 1, i, r[i], ans );//都连起来了 也没有i什么事了 所以随便记录i的一边l/r有值 下次就可以在第一个if语句直接return l[r[i + 1]] = l[i - 1], r[l[i - 1]] = r[i + 1], l[i] = r[i] = i;ans = max( ans, r[i + 1] - l[i - 1] + 1 );		}
}void remove( int lst ) {while( top > lst ) {if( ! s[top].flag ) l[s[top].pos] = s[top].lst;else r[s[top].pos] = s[top].lst;ans = s[top --].ans; }
}void solve( int id ) {remove( 0 );//移动到新块 莫队里的东西清空 for( int i = 1;i <= n;i ++ ) if( t[i].l < L[id] && R[id] < t[i].r )//完全覆盖该块且不在块内的区间一定会对块内所挂询问产生贡献 add( t[i].id );sort( q[id].begin(), q[id].end(), []( node x, node y ) { return x.r < y.r; } );//将块内询问按照右端点排序 只增加的回滚莫队 int lst = top;for( node now : q[id] ) //计算整个区间都在块内的询问的答案if( now.r > R[id] ) continue;else {for( int i = L[id];i <= R[id];i ++ )if( now.l <= t[x[i].id].r && t[x[i].id].l <= now.r )//与查询区间有交 可能会为答案贡献的区间 add( x[i].id );ret[now.id] = ans;remove( lst ); }remove( 0 );int cur = R[id];//处理r在其他块递增的询问 先把指针拨到块的最后for( int i = 1;i <= n;i ++ )if( t[i].l <= R[id] && R[id] <= t[i].r )//一定会与后面的查询有交点 R[id]add( t[i].id );for( node now : q[id] )if( now.r <= R[id] ) continue;else {while( cur < now.r ) add( x[++ cur].id );lst = top;for( int i = R[id];i >= now.l;i -- )//回滚莫队 滚到当前询问的左端 add( x[i].id );ret[now.id] = ans;remove( lst );//返回 回滚 抵消 }
}int main() {scanf( "%d %d", &n, &m );for( int i = 1, l, r;i <= n;i ++ ) {scanf( "%d %d", &l, &r );x[i] = Node( l, i );x[i + n] = Node( r, i );t[i] = node( l, r, i );}//为了块的大小能开出来 1e9首先要离散化 sort( x + 1, x + ( n << 1 ) + 1 );for( int i = 1;i <= n;i ++ ) {t[i].l = lower_bound( x + 1, x + ( n << 1 | 1 ), Node( t[i].l, 0 ) ) - x;t[i].r = upper_bound( x + 1, x + ( n << 1 | 1 ), Node( t[i].r, 1e9 ) ) - x - 1;}int N = n << 1;int B = sqrt( N );for( int i = 1;i <= N;i ++ )block[i] = ( i - 1 ) / B + 1;//L,R 记录块的左右端点 for( int i = N;i;i -- ) L[block[i]] = i;for( int i = 1;i <= N;i ++ ) R[block[i]] = i;for( int i = 1, l, r;i <= m;i ++ ) {scanf( "%d %d", &l, &r );l = lower_bound( x + 1, x + ( n << 1 | 1 ), Node( l, 0 ) ) - x;r = upper_bound( x + 1, x + ( n << 1 | 1 ), Node( r, 1e9 ) ) - x - 1;q[block[l]].push_back( node( l, r, i ) );}for( int i = 1;i <= block[N];i ++ )if( ! q[i].empty() )solve( i );for( int i = 1;i <= m;i ++ )printf( "%d\n", ret[i] );return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318200.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P2146 [NOI2015] 软件包管理器

P2146 [NOI2015] 软件包管理器 题意&#xff1a; 如果软件包 a 依赖软件包 b&#xff0c;那么安装软件包 a 以前&#xff0c;必须先安装软件包 b。同时&#xff0c;如果想要卸载软件包 b&#xff0c;则必须卸载软件包 a。 软件包之间存在依赖关系&#xff0c;除了0号软件包以…

微软 2018 开源大事记

从微软公开宣布 "Microsoft love linux" 那一刻起&#xff0c;过去的几年里&#xff0c;微软积极拥抱开源的举动我们有目共睹&#xff0c;即便有过"Linux is a cancer"这种真香警告的 flag&#xff0c;但不得不承认的是&#xff0c;微软一系列“拥抱开源”…

模板:二叉搜索树平衡树

文章目录前言二叉搜索树代码treap代码splay开点旋转splay插入查找第k大元素查找给定元素的排名前驱&后继删除完整代码练习总结前言 终于开始学这个东西了 看了好几篇博客才找到一篇可读的qwq 我曾经还以为线段树码量大…我真傻&#xff0c;真的 所谓平衡树&#xff0c;就是…

51nod2626-未来常数【树上启发式合并,线段树】

正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId2626 题目大意 给出nnn个点的一棵树&#xff0c;每个区间[l,r][l,r][l,r]的代价是选出这个区间中的一个点xxx使得它走到所有点然后又回到xxx的路程最短长度&#xff0c;求一个随机区间的期望代价。 1≤n…

A*,IDA*—高档次的暴搜

A*通过评价函数来判断当前状态是否可以到达最终状态(即可行性剪枝)&#xff0c;来减少不必要的搜索。 例题——P2324 [SCOI2005]骑士精神 我们通过当前不在指定位置上的棋子个数为评价函数&#xff0c;\(used\) 【评价函数值】超过了预期的值&#xff0c;便不用再线下深入搜索了…

P2486 [SDOI2011]染色

P2486 [SDOI2011]染色 题意&#xff1a; 题解&#xff1a; 与一般的树链剖分相比&#xff0c;不同点在于查询的不是路径上颜色的数量而是颜色段的数量 对于两个颜色段&#xff0c;112和221&#xff0c;两个颜色段数量都是2 如果合在一起颜色段的数量就是3&#xff0c;因为左边…

牛客网CSP-S提高组赛前集训营1题解(仓鼠的石子游戏 [博弈论] + 乃爱与城市的拥挤程度 [树上DP] + 小w的魔术扑克[dfs + 离线])

文章目录T1&#xff1a;仓鼠的石子游戏题目题解代码实现T2&#xff1a;乃爱与城市拥挤程度题目题解代码实现T3&#xff1a;小w的魔术扑克题目题解代码实现T1&#xff1a;仓鼠的石子游戏 题目 仓鼠和兔子被禁止玩电脑&#xff0c;无聊的他们跑到一块空地上&#xff0c;空地上有…

使用PerfView监测.NET程序性能(二):Perfview的使用

在上一篇博客使用PerfView监测.NET程序性能&#xff08;一&#xff09;&#xff1a;Event Trace for Windows 中&#xff0c;我们了解了对Windows及应用程序进行性能分析的基础&#xff1a;Event Trace for Windows (ETW)。现在来看看基于ETW的性能分析工具——Perfview.exePer…

连通性相关

强联通分量 强连通&#xff1a;有向图 \(G\) 强连通表示&#xff0c;\(G\) 中任意两个结点连通。 强连通分量( Strongly Connected Components &#xff0c;简称 \(\operatorname{SCC}\) )&#xff1a;极大的 强连通子图。 Tarjan 维护了以下两个变量&#xff1a; \(dfn\) &…

CF505E-Mr. Kitayuta vs. Bamboos【贪心,二分】

正题 题目链接:https://www.luogu.com.cn/problem/CF505E 题目大意 开始一个长度为nnn的序列hhh&#xff0c;mmm天每天你可以kkk次选择一个hih_ihi​让它等于himax{hi−p,0}h_imax\{h_i-p,0\}hi​max{hi​−p,0}&#xff0c;然后结束时让每个hihiaih_ih_ia_ihi​hi​ai​&…

阶段总结:8.09-8.18 十日模拟

一图流了解一下 文章目录十日谈总结十日谈 再总结一下 8.09 搜索模拟&#xff1a;25分。…毕竟是第一天不太适应吧 &#xff08;拼命找借口&#xff09;。没有看到标题就很淦&#xff0c;就是全写挂了而已&#xff0c;已经无从谈起…hzwer的粉丝那题提醒我们不要被吓人的数据…

学习有向图和无向图的强连通分量(基本概念+割点+点双联通分量+桥+边双连通分量+全套模板【Tarjan】)

最近总是考到Tarjan&#xff0c;让我措手不及基本概念割点以及点双连通分量Tarjan法求割点推导过程代码实现Tarjan法求点双连通分量推导过程代码实现有向图的Tarjan缩点桥与边双连通分量Tarjan法求桥理论推导代码实现Tarjan法求边双连通分量理论推导代码实现前言&#xff1a;有…

.NET Core下的Spring Cloud——前言和概述

前言前几年一直在写类似dubbo&#xff0c;Spring Cloud的微服务框架辗辗转转重复了多次&#xff0c;也重构推翻了很多次&#xff0c;其中诞生了“Rabbit.Rpc”,”Go”,”RabbitCloud”等开源项目。其中不乏他人对这些项目的完善。很高兴自己的开源项目能够给他人提供思路和复用…

期望 概率DP

期望 \(x\) 的期望 \(E(x)\) 表示平均情况下 \(x\) 的值。 令 \(C\) 表示常数&#xff0c; \(X\) 和 \(Y\) 表示两个随机变量。 \(E(C)C\) \(E(C \times X)C \times E(X)\) \(E(XY)E(X)E(Y)\) 期望的线性性 \(E(XY)\) 不一定等于 \(E(X) \times E(Y)\) 期望练习&#xff1a…

CF785E Anton and Permutation

CF785E Anton and Permutation 题意&#xff1a; 对于一个长度为 n 的序列进行 k 次操作&#xff0c;每次操作都是交换序列中的某两个数。对于每一个操作&#xff0c;回答当前序列中有多少个逆序对。 1<n<200000 1<q<50000 题解&#xff1a; 动态逆序对&#x…

P5311-[Ynoi2011]成都七中【点分树,树状数组】

正题 题目链接:https://www.luogu.com.cn/problem/P5311 题目大意 给出nnn个点的一棵树&#xff0c;每个节点有一个颜色&#xff0c;mmm次询问提出区间[l,r][l,r][l,r]的点构成的生成子图中xxx所在连通块的颜色数。 1≤n,m,ai≤1051\leq n,m,a_i\leq 10^51≤n,m,ai​≤105 解…

[ NOIP提高组 2016]愤怒的小鸟(暴搜 + 状压DP)// [SNOI2017]一个简单的询问(莫队)

一次性写两道题T1&#xff1a;一个简单的询问题目题解代码实现T2&#xff1a;愤怒的小鸟题目暴搜题解暴搜代码实现状压DP题解状压DP代码实现T1&#xff1a;一个简单的询问 题目 给你一个长度为 N 的序列 ai ,1≤i≤N&#xff0c;和 q 组询问&#xff0c;每组询问读入 l1,r1,l…

微软发布新的 Azure Pipelines 功能和集成

在最近举行的Connect()大会上&#xff0c;微软发布了几项新功能以及与 Azure Pipelines 的集成&#xff0c;包括 Visual Studio Code 的 Azure Pipelines 扩展、GitHub 版本管理、对 IoT 项目的支持以及 ServiceNow 集成。自从 9 月份推出 Azure Pipelines 以来&#xff0c;这种…

平衡树练习总结

文章目录前言普通平衡树文艺平衡树郁闷的出纳员书架宠物收养场机械排序千山鸟飞绝总结前言 专门刷了一天半的平衡树 &#xff08;附带划水OvO&#xff09; 使用的是蜜汁常数的splay 感觉对平衡树的理解还是有帮助的 一些较为常规的平衡树的题应该是差不多了 更正之前刚学的观点…

二维树状数组

二维树状数组可以实现在平面上的区域加、区域查询等操作。 区域修改 我们在一维时维护树状数组的区间操作时&#xff0c;对其进行了差分。类比一维的思想&#xff0c;我们在二维平面上也对树状数组差分。 我们来看二维的前缀和&#xff1a; \[sum(i,j)sum(i-1,j)sum(i,j-1)-sum…