题目大意
\(n\)个点的完全图标号\((0\sim n-1)\),\(i\)和\(j\)连边权值为\(i\bigoplus j\),求\(\rm{MST}\)的值
题解
挺有意思的一道题,但网上好像没有证明?
我来证一发。
首先,利用\(\rm{Kruskal}\)的思想我们想到先选最小边。
那显然是\(1\)。
我们不难发现边权为一的边都要选,因为打过Dinic的都知道\(0\bigoplus 1 = 1, 1\bigoplus 1 = 0, 2\bigoplus 1 = 3, 3\bigoplus 1 = 2, \cdots\)(因为\(a\bigoplus b = c\implies a\bigoplus c = b\),所以反一下是一样的)。我们发现\(0\)只与\(1\)联通,\(2\)只与\(3\)联通,\(2n\)只与\(2n+1\)联通。
我们发现联通块少了一半(确切来说是从\(n\)个连通块变成了\(\lceil\frac{n}{2}\rceil\)块),连了\(\lfloor\frac{n}{2}\rfloor\)条边。
我们考虑继续连边。我们发现如果有点对\((2n, 2n+1)\)与点对\((2m, 2m+1)\)连边:
1101010 1101011
1011100 1011101
比如上面这个例子,出最后一位外前面几位已经确定了,唯有最后一位(因为异或没有进位,而\(2n\)和\(2n+1\)只有最后一位不同)。显然\(1\)比\(0\)劣,我们考虑尽量往\(0\)选。我们发现两个点对都有\(0\)和\(1\)(一奇一偶嘛),所以一定可以凑成\(0\)(虽然最后一块可能只有一个数,但由于其他块都有两个,所以仍能凑成)。所以我们不妨把所有点对的全部除\(2\),最终统计答案是乘\(2\)即可,因为一位最后一位肯定是\(0\)。
这样递归下去就可以了。
代码并不长:
#include <iostream>
using namespace std;
typedef long long LL;
inline LL sol(LL n)
{
if(n == 1LL)
return 0LL;
else
return (sol((n + 1) >> 1) << 1) + (n >> 1);
}
int main()
{
LL n;
cin >> n;
cout << sol(n);
return 0;
}