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