zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    EMV内核使用中的常见问题
    SM2国密证书合法性验证
    WP8.1中C++的winodws运行时组件位移操作的差异
    [源码]Literacy 快速反射读写对象属性,字段
    Vue 单文件元件 — vTabs
    vue-router路径计算问题
    前端跨域新方案尝试
    Vue 单文件原件 — vCheckBox
    JavaScript 功能类 Url.js
    Vue 学习笔记 — 组件初始化
  • 原文地址:https://www.cnblogs.com/mrclr/p/15560035.html
Copyright © 2011-2022 走看看