Codeforces Round #754 (Div. 2) D,E 题解

D

这题当时比赛的时候想了一半,另一半看完题解后感觉确实想不出来。

首先观察题目中\(u \bigoplus v \leqslant \min(u,v)\)这个条件,容易发现,当且仅当\(u,v\)在二进制表示下的最高位相同时才成立,不妨写成\(MSB_u = MSB_v\).那么如果点\(u\)不能再走,一定是他周围的所有点\(v_i\),都有\(MSB_u \neq MSB_{v_i}\).

这会让人不禁猜想,是否存在一种方案,使对于任意两个满足\(MSB_u = MSB_v\)的节点,在树上都不相邻呢?答案是肯定的。

首先简单证明一下,为什么这样构造能最大化Eikooc必赢的点的数量。首先这样构造,必赢的点是所有\(n\)个点,而如果存在两个相邻的节点满足\(MSB_u = MSB_v\),那么不妨开始选择了\(u\),Sushi就能走到\(v\),于是Eikooc就输了,这样对于Eikooc来说,必赢的点就不能包含\(u,v\)。因此一这种构造方法一定是最优的。

那么如何构造?到这我就不会了,我当时想的是用树形dp将同一集合的点分开,但是代码难度有点大,没调出来。实际上很简单:首先对树进行黑白染色,满足同一颜色的点不相邻,那么需要满足最高位相同的数必须是同一颜色即可。记白色的点的数量为\(w\),黑色点数量为\(b\)(不妨令\(w < b\)),那么有\(w \leqslant \frac{n}{2}\),因此\(MSB_w < MSB_n\),这个性质在后面的构造中会用到。

接下来是具体的构造方法:如果\(w\)的第\(i\)位为1,那么将所有满足\(MSB_x=i\)的数\(x\)归到白色节点,否则归到黑色节点。这个证明我也想了一会儿:如果不考虑\(n\)的最高位,那么\(MSB=i\)的数就有\(2^i\)个,正好对应二进制表示下第\(i\)位为1,其他位全为0.按照这个构造方法,就刚好把若干个最高位相同的数作为一个整体划分到了白色节点中,而且因为\(MSB_w <MSB_n\),因此对于\(n\)的最高位不会有影响,把剩下的数直接划到黑色节点中即可。

时间复杂度\(O(n)\)。题解的代码写的比较简洁,参考了一下。

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 2e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n, ans[maxn];
struct Edge
{
	int nxt, to;
}e[maxn << 1];
int head[maxn], ecnt = -1;
In void addEdge(int x, int y)
{
	e[++ecnt] = (Edge){head[x], y};
	head[x] = ecnt;
}

vector<int> col[2];
In void dfs(int now, int _f, int c)
{
	col[c].push_back(now);
	forE(i, now, v) if(v ^ _f) dfs(v, now, c ^ 1);
}

int msb[maxn];
In void init()
{
	int bit = 0, k = 2;
	for(int i = 1; i < maxn; ++i)
	{
		if(i == k) bit++, k <<= 1;
		msb[i] = bit;
	}
}

int main()
{
	init();
	int T = read();
	while(T--)
	{
		n = read();
		fill(head, head + n + 1, -1), ecnt = -1;
		for(int i = 1; i < n; ++i)
		{
			int x = read(), y = read();
			addEdge(x, y), addEdge(y, x);
		}
		dfs(1, 0, 0);
		int w = min(col[0].size(), col[1].size()), o = col[0].size() > col[1].size() ? 1 : 0;
		for(int i = 1; i <= n; ++i)
		{
			int x = ((w >> msb[i]) & 1) ? o : (o ^ 1);
			ans[col[x].back()] = i;
			col[x].pop_back();
		}
		for(int i = 1; i <= n; ++i) write(ans[i]), space; enter;
	}
	return 0;
}

E

这题刚开始以为是一个数学题,但实际上几乎不用数学知识,思路也比较简单。

\(f_i\)表示第\(i\)个数需要操作的次数,那么有$$f_i= b_i - a_i - \sum\limits_{j | i} f_j \ \ \ \ (f_1 = b_1 - a_1),$$
于是答案就是\(\sum\limits_{i=1}^n |f_i|\),这个就是单次\(O(n)\)的做法.

现在有\(q\)组询问,每次\(b_1\)都会变,但如果令\(b_1=x\),会发现每一个\(f_i\)都是关于\(x\)的一个一次函数\(f_i=c_ix+d_i\),而这个一次函数也可以在\(O(n\log n)\)内维护出来.

那么现在就变成了给定多个\(x\),每次都要求\(\sum\limits_{i=1}^n |c_ix+d_i|\),把绝对值展开,即\(|f_i|=\left\{\begin{matrix} c_ix+d_i, \ \ x \geqslant -\frac{d}{c}\\ -c_ix-d_i, \ \ x < -\frac{d}{c} \end{matrix}\right.\),那么如果我们把\(f_i\)\(-\frac{d_i}{c_i}\)排序,只要维护前后缀和,每次二分就可以单次\(O(\log n)\)求出答案了。

因此总时间复杂度\(O(n\log n+ q\log n)\).

这样实现有个坑点在于\(c_i=0\)的情况,要单独考虑。题解中给了一个用莫比乌斯函数性质的解法,暂时没有看懂。

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 2e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n, Q, a[maxn], b[maxn];

#define pr pair<ll, ll>
#define mp make_pair
#define F first
#define S second
struct Node
{
	pr f;			//first为ci,second为di 
	In bool operator < (const Node& oth)const
	{
		return 1.0 * f.S / f.F > 1.0 * oth.f.S / oth.f.F;	//移项相乘可能会爆long long. 
	}
}t[maxn], s[maxn];
int cnt = 0;
ll sumc[maxn], sumd[maxn], ans = 0;
In void init_calc()
{
	t[1].f = mp(1, 0);
	for(int i = 1; i <= n; ++i)
	{
		if(i > 1) t[i].f.S += b[i] - a[i];
		for(int j = i + i; j <= n; j += i)
			t[j].f.F -= t[i].f.F, t[j].f.S -= t[i].f.S;
	}
	for(int i = 2; i <= n; ++i)
	{
		if(t[i].f.F == 0) ans += abs(t[i].f.S);		//提前处理c=0的情况 
		else
		{ 
			if(t[i].f.F < 0) t[i].f.F *= -1, t[i].f.S *= -1;
			s[++cnt] = t[i];
		}
	}
	sort(s + 1, s + cnt + 1);
	for(int i = 1; i <= cnt; ++i)
	{
		sumc[i] = sumc[i - 1] + s[i].f.F;
		sumd[i] = sumd[i - 1] + s[i].f.S;	
	} 
}

In ll sum(int L, int R, ll* S)
{
	if(L > R) return 0;
	else return S[R] - S[L - 1]; 
}
In ll solve(ll x)
{
	int L = 1, R = cnt + 1;
	while(L < R)
	{
		int mid = (L + R) >> 1;
		if(x * s[mid].f.F <= -s[mid].f.S) R = mid;
		else L = mid + 1;
	}
	return ans + abs(x) + (sum(1, L - 1, sumc) - sum(L, cnt, sumc)) * x + sum(1, L - 1, sumd) - sum(L, cnt, sumd);
}

int main()
{
	n = read();
	for(int i = 1; i <= n; ++i) a[i] = read();
	for(int i = 1; i <= n; ++i) b[i] = read();
	init_calc();
	Q = read();
	for(int i = 1; i <= Q; ++i) write(solve(read() - a[1])), enter;
	return 0;
}
posted @ 2021-11-16 14:26  mrclr  阅读(91)  评论(0编辑  收藏  举报