Codeforces Round #703 (Div. 2) 题解


#703 (Div. 2)

A. Shifting Stacks

从左往右构造递增0,1,2...,如果这样都不能递增就肯定无解

雷区:不能用等差数列算个数,因为这个移动必须是左到右的不能逆,hack: 0 0 3 AC:NO WA:YES

B. Eastern Exhibition

以前考试遇到过的相似度百分之九十五,横纵坐标拆开分别取中位数即可;偶数个点中位数是一段区间

#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 1005
int T, n;
int x[maxn], y[maxn];

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%lld %lld", &x[i], &y[i] );
		sort( x + 1, x + n + 1 );
		sort( y + 1, y + n + 1 );
		if( n & 1 ) printf( "1\n" );
		else {
			int l = n >> 1, r = l + 1;
			printf( "%lld\n", ( x[r] - x[l] + 1 ) * ( y[r] - y[l] + 1 ) );
		}
	}
	return 0;
}

C. Guessing the Greatest

第一次询问整个序列确定次大值,然后再一次询问判断最大值是在左边还是右边

之后就是二分位置与次大值进行询问,找到最大值

煞笔二分再次死亡

#include <cstdio>
int n;

int print( int l, int r ) {
	if( l >= r ) return -1;
	printf( "? %d %d\n", l, r );
	fflush( stdout );
	int pos;
	scanf( "%d", &pos );
	return pos;
}

int main() {
	scanf( "%d", &n );
	int pos = print( 1, n );
	if( pos > 1 && print( 1, pos ) == pos ) {
		int l = 1, r = pos;
		while( l < r ) {
			int mid = ( l + r + 1 ) >> 1;
			if( print( mid, pos ) == pos ) l = mid;
			else r = mid - 1;
		}
		printf( "! %d\n", l );
		fflush( stdout );
	}
	else {
		int l = pos, r = n;
		while( l < r ) {
			int mid = ( l + r ) >> 1;
			if( print( pos, mid ) == pos ) r = mid;
			else l = mid + 1;
		}
		printf( "! %d\n", l );
		fflush( stdout );
	}
	return 0;
}

D. Max Median

二分最后的答案,将原数组转换为大于等于 1 1 1小于 − 1 -1 1,看有无一段长度大于等于 k k k的区间和为

对于第 i i i个位置,有 0 , 1 , 2 , . . . , i − k 0,1,2,...,i-k 0,1,2,...,ik种选择,用前缀和最小值优化即可做到 O ( n l o g n ) O(nlogn) O(nlogn)

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int n, k, ans;
int a[maxn], b[maxn], minn[maxn];

bool check( int x ) {
	for( int i = 1;i <= n;i ++ )
		if( a[i] >= x ) b[i] = 1;
		else b[i] = -1;
	for( int i = 1;i <= n;i ++ )
		b[i] += b[i - 1];
	for( int i = 1;i <= n;i ++ )
		minn[i] = min( minn[i - 1], b[i] );
	for( int i = k;i <= n;i ++ )
		if( b[i] > minn[i - k] ) return 1;
	return 0;
}

int main() {
	scanf( "%d %d", &n, &k );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &a[i] );
	int l = 1, r = n;
	while( l <= r ) {
		int mid = ( l + r ) >> 1;
		if( check( mid ) ) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	printf( "%d\n", ans );
	return 0;
}

E. Paired Payment

如果可以一条一条边地走,就是一个dijkstra板子

但必须两条两条走,也就是说这一次走的边产生的贡献与上一条边边权挂钩

再观察边权范围,只有 50 50 50,小得离谱;很有可能就是从这里入手

不妨把上一条边权是多少带着dijkstra

d i s i , j dis_{i,j} disi,j表示走到 i i i点上一条的边权为 j j j的最短路

最后为什么是 d i s i , 0 dis_{i,0} disi,0,这是为了dijkstra的正确性, d i s i , 0 dis_{i,0} disi,0含义比较特殊,表示 i i i为结束点(第二条边的入点)

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define int long long
#define maxn 200005
struct node {
	int u, lst, d;
	node(){}
	node( int U, int Lst, int D ) {
		u = U, lst = Lst, d = D;
	}
	bool operator < ( node t ) const {
		return d > t.d;
	}
};
int n, m;
priority_queue < node > q;
vector < pair < int, int > > G[maxn];
int dis[maxn][55];

signed main() {
	scanf( "%lld %lld", &n, &m );
	for( int i = 1, u, v, w;i <= m;i ++ ) {
		scanf( "%lld %lld %lld", &u, &v, &w );
		G[u].push_back( make_pair( v, w ) );
		G[v].push_back( make_pair( u, w ) );
	}
	memset( dis, 0x3f, sizeof( dis ) );
	dis[1][0] = 0;
	q.push( node( 1, 0, 0 ) );
	while( ! q.empty() ) {
		node t = q.top(); q.pop();
		int u = t.u, lst = t.lst;
		if( dis[u][lst] < t.d ) continue;
		for( int i = 0;i < G[u].size();i ++ ) {
			int v = G[u][i].first, w = G[u][i].second;
			int nxt = lst ? 0 : w;
			if( dis[v][nxt] > dis[u][lst] + 2 * lst * w + w * w )
				q.push( node( v, nxt, dis[v][nxt] = dis[u][lst] + 2 * lst * w + w * w ) );
		}
	}
	for( int i = 1;i <= n;i ++ )
		if( dis[i][0] >= 0x3f3f3f3f ) printf( "-1 " );
		else printf( "%lld ", dis[i][0] );
	return 0;
}

F. Pairs of Paths

事实上,只有两种情况

case 1: 两条路径相交于一条路径的 l c a lca lca

case 2: 两条路径拥有公共的 l c a lca lca

这意味着,符合条件的答案一定是交于某个 l c a lca lca

对于每一条路径都定义一个三元组 ( a , b , l c a ) (a,b,lca) (a,b,lca),其中 a , b a,b a,b分别表示两个端点的树根,如果端点是 l c a lca lca,那么重新给个编号(因为这时候要判定为不同)

case1: l c a lca lca排序, a a a严格递减排序后,用桶存储之前信息,然后每次加 S − c n t b S-cnt_b Scntb,然后再把 c n t a + + , c n t b + + cnt_a++,cnt_b++ cnta++,cntb++

case2: 另一个 l c a lca lca一定是这个交点 l c a lca lca的祖先,用树状数组记录

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 600005
#define int long long
struct node{
	int u, v, tu, tv, lca;
}MS[maxn];
vector < int > G[maxn];
int n, m, T, cnt;
int dep[maxn], l[maxn], r[maxn], tree[maxn], tot[maxn];
int f[maxn][20];

bool cmp( node x, node y ) {
	if( dep[x.lca] == dep[y.lca] )
		if( x.lca == y.lca ) return x.tu > y.tu;
		else return x.lca < y.lca;
	else
		return dep[x.lca] < dep[y.lca];
}

void dfs( int u, int fa ) {
	dep[u] = dep[fa] + 1, f[u][0] = fa;
	for( int i = 1;i < 20;i ++ )
		f[u][i] = f[f[u][i - 1]][i - 1];
	l[u] = ++ cnt;
	for( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		if( v == fa ) continue;
		else dfs( v, u );
	}
	r[u] = cnt;
}

int lca( int u, int v ) {
	if( dep[u] < dep[v] ) swap( u, v );
	for( int i = 19;~ i;i -- )
		if( dep[f[u][i]] >= dep[v] )
			u = f[u][i];
	if( u == v ) return u;
	for( int i = 19;~ i;i -- )
		if( f[u][i] != f[v][i] )
			u = f[u][i], v = f[v][i];
	return f[u][0];
}

int get_top( int u, int lca ) {
	if( u == lca ) return ++ T;
	for( int i = 19;~ i;i -- )
		if( dep[f[u][i]] > dep[lca] )
			u = f[u][i];
	return u;
}

int lowbit( int x ) {
	return x & ( -x );
}

void add( int x ) {
	for( int i = x;i <= n;i += lowbit( i ) )
		tree[i] ++;
}

int ask( int x ) {
	int ans = 0;
	for( int i = x;i;i -= lowbit( i ) )
		ans += tree[i];
	return ans;
}

signed main() {
	scanf( "%lld", &n ); T = n;
	for( int i = 1, u, v;i < n;i ++ ) {
		scanf( "%lld %lld", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );
	}
	dfs( 1, 0 );
	scanf( "%lld", &m );
	for( int i = 1;i <= m;i ++ ) {
		scanf( "%lld %lld", &MS[i].u, &MS[i].v );
		MS[i].lca = lca( MS[i].u, MS[i].v );
		MS[i].tu = get_top( MS[i].u, MS[i].lca );
		MS[i].tv = get_top( MS[i].v, MS[i].lca );
		if( MS[i].tu > MS[i].tv ) {
			swap( MS[i].tu, MS[i].tv );
			swap( MS[i].u, MS[i].v );
		}
	}
	sort( MS + 1, MS + m + 1, cmp );
	int ans = 0;
	for( int i = 1, j;i <= m;i = j + 1 ) { 
		j = i;
		while( j < m && MS[j + 1].lca == MS[i].lca ) j ++;
		cnt = 0;
		for( int s = i, t;s <= j;s = t + 1 ) {
			t = s;
			while( t < j && MS[t + 1].tu == MS[s].tu ) t ++;
			for( int k = s;k <= t;k ++ )
				ans += cnt - tot[MS[k].tv];
			for( int k = s;k <= t;k ++ )
				tot[MS[k].tv] ++, tot[MS[k].tu] ++;
			cnt += ( t - s + 1 );
		}
		for( int k = i;k <= j;k ++ )
			tot[MS[k].tu] = tot[MS[k].tv] = 0;
		for( int k = i;k <= j;k ++ ) {
			ans += ask( r[MS[k].lca] ) - ask( l[MS[k].lca] - 1 );
			if( MS[k].tu <= n ) ans -= ask( r[MS[k].tu] ) - ask( l[MS[k].tu] - 1 );
			if( MS[k].tv <= n )ans -= ask( r[MS[k].tv] ) - ask( l[MS[k].tv] - 1 );
		} 
		for( int k = i;k <= j;k ++ )
			add( l[MS[k].u] ), add( l[MS[k].v] );
	}
	printf( "%lld\n", ans );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值