一二三系列之CodeChef分块——Chef and Churu,Chef and Problems,Children Trips

Chef and Churu

source

solution

对于单独的 i i i,查询可以用线段树/树状数组 O ( n log ⁡ n ) O(n\log n) O(nlogn),这暗示可以平衡查询修改次数

分块

预处理 c n t i , j : cnt_{i,j}: cnti,j: A j A_j Aj对第 i i i块包含的函数贡献次数

每次修改的时候,相当于 + v a l − A p o s +val-A_{pos} +valApos

枚举块,直接整体修改 c n t × ( v a l − A p o s ) cnt\times(val-A_{pos}) cnt×(valApos)

  • 如果是 O ( log ⁡ n ) O(\log n) O(logn)修改值
  • 查询整块直接调用 n \sqrt{n} n
  • 散块区间查询 n log ⁡ n \sqrt{n}\log n n logn
  • 最后时间复杂度是 O ( Q 1 ( n + l o g n ) + Q 2 ( n + n log ⁡ n ) ) O(Q_1(\sqrt{n}+logn)+Q_2(\sqrt n+\sqrt n\log n)) O(Q1(n +logn)+Q2(n +n logn))
  • 卡在查询的 n log ⁡ n \sqrt{n}\log n n logn,非常难受

再分块, O ( n ) O(\sqrt{n}) O(n ) 修改, O ( 1 ) O(1) O(1)查询

tag记录整块的整体加标记,w记录散块暴力加

查询的时候,找到 i i i​的w值再加上所在块的整体加tag

时间复杂度 O ( Q 1 ( n + n ) + Q 2 ( n + n ) ) = O ( Q n ) O(Q_1(\sqrt{n}+\sqrt{n})+Q_2(\sqrt n+\sqrt n))=O(Q\sqrt n) O(Q1(n +n )+Q2(n +n ))=O(Qn )

code

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
#define int unsigned long long
#define maxn 100005
#define maxB 320
int n, Q, B;
int A[maxn], L[maxn], R[maxn], block[maxn], sum[maxB], w[maxn], tag[maxB];
int cnt[maxB][maxn];

void modify( int pos, int val ) {
	for( int i = 1;i <= block[n];i ++ )
		sum[i] += cnt[i][pos] * ( val - A[pos] );
	for( int i = block[pos] + 1;i <= block[n];i ++ )
		tag[i] += val - A[pos];
	for( int i = pos;i <= min( n, B * block[pos] );i ++ )
		w[i] += val - A[pos];
	A[pos] = val;
}

int query( int i ) { return w[i] + tag[block[i]]; }

int query( int l, int r ) {
	int ans = 0;
	if( block[l] == block[r] )
		for( int i = l;i <= r;i ++ )
			ans += query( R[i] ) - query( L[i] - 1 );
	else {
		for( int i = l;i <= B * block[l];i ++ )
			ans += query( R[i] ) - query( L[i] - 1 );
		for( int i = B * ( block[r] - 1 ) + 1;i <= r;i ++ )
			ans += query( R[i] ) - query( L[i] - 1 );
		for( int i = block[l] + 1;i < block[r];i ++ )
			ans += sum[i];
	}
	return ans;
}

signed main() {
	scanf( "%llu", &n );
	B = sqrt( n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%llu", &A[i] ), w[i] = A[i];
		block[i] = ( i - 1 ) / B + 1;
	}
	for( int i = 1;i <= n;i ++ )
		scanf( "%llu %llu", &L[i], &R[i] );
	for( int i = 1;i <= n;i ++ )
		w[i] += w[i - 1];
	for( int i = 1;i <= block[n];i ++ ) {
		for( int j = ( i - 1 ) * B + 1;j <= min( n, i * B );j ++ )
			cnt[i][L[j]] ++, cnt[i][R[j] + 1] --;
		for( int j = 1;j <= n;j ++ )
			cnt[i][j] += cnt[i][j - 1];
		for( int j = 1;j <= n;j ++ )
			sum[i] += cnt[i][j] * A[j];
	}
	scanf( "%llu", &Q );
	int opt, x, y;
	while( Q -- ) {
		scanf( "%llu %llu %llu", &opt, &x, &y );
		if( opt & 1 ) modify( x, y );
		else printf( "%llu\n", query( x, y ) );
	}
	return 0;
}

Chef and Problems

source

solution

分块

预处理出整块 i , j i,j i,j之间的答案

具体而言,用 l a s t i last_i lasti记录年龄为 i i i的第一个人的出现位置,显然越往后的人与第一个年龄相同的人距离越大

对于查询,包含的整块直接调用预处理的数组

散块部分,就在区间中lower_bound找最远的,把人按年龄分道不同容器vector找坐标

code

#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 100005
#define maxB 1005
vector < int > pos[maxn];
int n, m, Q, B;
int A[maxn], block[maxn], last[maxn];
int len[maxB][maxB];

int main() {
	scanf( "%d %d %d", &n, &m, &Q );
	B = 100;
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &A[i] );
		block[i] = ( i - 1 ) / B + 1;
	}
	for( int i = 1;i <= block[n];i ++ ) {
		for( int j = 1;j <= m;j ++ ) last[j] = 0;
		int l = ( i - 1 ) * B + 1, ans = 0;
		for( int j = l;j <= n;j ++ ) {
			if( ! last[A[j]] ) last[A[j]] = j;
			else ans = max( ans, j - last[A[j]] );
			if( j % B == 0 ) len[i][block[j]] = ans; 
		}
	}
	for( int i = 1;i <= n;i ++ ) pos[A[i]].push_back( i );
	while( Q -- ) {
		int l, r;
		scanf( "%d %d", &l, &r );
		int ans = len[block[l] + 1][block[r] - 1];
		for( int i = l;i <= min( r, block[l] * B );i ++ ) {
			int p = lower_bound( pos[A[i]].begin(), pos[A[i]].end(), r ) - pos[A[i]].begin() - 1;
			if( ! ~ p ) continue;
			else ans = max( ans, pos[A[i]][p] - i );
		}
		for( int i = max( l, ( block[r] - 1 ) * B + 1 );i <= r;i ++ ) {
			int p = lower_bound( pos[A[i]].begin(), pos[A[i]].end(), l ) - pos[A[i]].begin();
			if( p == pos[A[i]].size() ) continue;
			else ans = max( ans, i - pos[A[i]][p] );
		}
		printf( "%d\n", ans );
	}
	return 0;
}

Children Trips

source

solution

dfs确定每个点到根的距离 d i s dis dis以及深度 d e p dep dep

c c c n \sqrt{n} n 的大小关系分块

  • c > n c>\sqrt n c>n

    对于 u , v u,v u,v,找到其 l c a lca lca,暴力一次一次跳,一次最多跳 p p p长度,最多跳 n \sqrt n n

    最后得到跳的次数以及距离 l c a lca lca的距离,两个距离合在一起看是再跳一次还是两次

  • c ≤ n c\le \sqrt n cn

    这个时候就不能暴力跳了,因为可能会达到 n n n级别

    但是 c c c最多只有 n \sqrt n n 个取值

    将排序按 c c c从小到大排序,预处理倍增数组 g i , j : g_{i,j}: gi,j: i i i 2 j 2^j 2j​次所达到的点

按理来说 O ( n n log ⁡ n ) O(n\sqrt n \log n) O(nn logn)有点尴尬,但这道题是八秒,交上去倒是挺快的,╮(╯▽╰)╭

code

#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define Pair pair < int, int >
#define maxn 100005
int n, m, B;
vector < Pair > G[maxn];
int dis[maxn], dep[maxn], ans[maxn];
int f[maxn][20], g[maxn][20];

void dfs( int u, int fa ) {
	f[u][0] = fa, dep[u] = dep[fa] + 1;
	for( int i = 1;i <= 16;i ++ )
		f[u][i] = f[f[u][i - 1]][i - 1];
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i].first;
		int w = G[u][i].second;
		if( v == fa ) continue;
		else dis[v] = dis[u] + w, dfs( v, u );
	}
}

struct node {
	int u, v, p, id;
	node(){}
	node( int U, int V, int P, int ID ) { u = U, v = V, p = P, id = ID; }
}block_little[maxn], block_large[maxn];

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];
}

int jump_once( int u, int p ) {
	int t = u;
	for( int i = 16;~ i;i -- ) {
		if( dis[u] - dis[f[t][i]] > p ) continue;
		else t = f[t][i];
	}
	return t;
}

Pair climb_large( int u, int lca, int p ) {
	int cnt = 0;
	while( u ^ lca ) {
		int t = jump_once( u, p );
		if( dep[t] > dep[lca] ) cnt ++, u = t;
		else break;
	}
	return make_pair( cnt, dis[u] - dis[lca] );
}

void solve_large( int n ) {
	for( int i = 1;i <= n;i ++ ) {
		int u = block_large[i].u;
		int v = block_large[i].v;
		int p = block_large[i].p;
		int id = block_large[i].id;
		int lca = get_lca( u, v );
		Pair l = climb_large( u, lca, p );
		Pair r = climb_large( v, lca, p );
		int len = l.second + r.second;
		ans[id] = l.first + r.first + ( int )ceil( len * 1.0 / p );
	}
}

Pair climb_little( int u, int lca, int p ) {
	int cnt = 0;
	for( int i = 16;~ i;i -- )
		if( dep[g[u][i]] > dep[lca] ) 
			cnt += 1 << i, u = g[u][i];
	return make_pair( cnt, dis[u] - dis[lca] );
}

void build( int p ) {
	for( int i = 1;i <= n;i ++ ) g[i][0] = jump_once( i, p );
	for( int j = 1;j <= 16;j ++ )
		for( int i = 1;i <= n;i ++ )
			g[i][j] = g[g[i][j - 1]][j - 1];
}

void solve_little( int n ) {
	sort( block_little + 1, block_little + n + 1, []( node x, node y ) { return x.p < y.p; } );
	for( int i = 1;i <= n;i ++ ) {
		int u = block_little[i].u;
		int v = block_little[i].v;
		int p = block_little[i].p;
		int id = block_little[i].id;
		int lca = get_lca( u, v );
		if( p != block_little[i - 1].p ) build( p );
		Pair l = climb_little( u, lca, p );
		Pair r = climb_little( v, lca, p );
		int len = l.second + r.second;
		ans[id] = l.first + r.first + ( int )ceil( len * 1.0 / p );
	}
}

int main() {
	scanf( "%d", &n );
	for( int i = 1, u, v, w;i < n;i ++ ) {
		scanf( "%d %d %d", &u, &v, &w );
		G[u].push_back( make_pair( v, w ) );
		G[v].push_back( make_pair( u, w ) ); 
	}
	dfs( 1, 0 );
	B = sqrt( n );
	scanf( "%d", &m );
	int little = 0, large = 0;
	for( int i = 1, u, v, p;i <= m;i ++ ) {
		scanf( "%d %d %d", &u, &v, &p );
		if( p <= B ) block_little[++ little] = node( u, v, p, i );
		else block_large[++ large] = node( u, v, p, i );
	}
	solve_large( large );	
	solve_little( little );
	for( int i = 1;i <= m;i ++ ) printf( "%d\n", ans[i] );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值