zoukankan      html  css  js  c++  java
  • 「杂录」CSP-S 2019 爆炸记&题解

    考试状况

    (Day1)

    (8:30)
    解压,先打个含头文件和(freopen)的模板程序,准备做题。

    (8:35)
    开题,心想着按顺序做吧,毕竟难度一般是按顺序排的。
    第一题,一眼看过去。
    标题:格雷码
    描述:格雷码是(balabala),有个方法可以生成格雷码(balabala)
    数据范围:(long long)
    (n)位格雷码第(k)项?第一位看一下在前半还是后半,第二位递归下去……复杂度(O(n)),没什么大问题,直接开打。

    (8:45)
    打完了,测小样例、大样例,手造两个小数据,测一下极限数据……等等,极限数据是(63)还是(64)位?看一下数据范围,(n<=64),可以等于……那就不能用(long long)了,赶紧改成(unsigned long long),发现不知道怎么读入……改成(cin)。测一下最大数据,能过,好的下一道。

    (8:50)
    第二题好像蛮套路的,树上做合法括号序列……考虑到括号序列的合法判断,只需要把(()当成(1)())当成(-1),求前缀和,满足没有任何一个位置小于(0),并且最后的位置等于(0)就一定合法。那么考虑在树上(Dfs)维护前缀和,统计方案,显然有(f_i=f_{fa_i}+ans_i)。那么计算以(i)结尾的方案数方法也挺简单的,只需要求出到祖先路径上第一个小于自己的前缀和(这个位置的前缀和为负,之前都不可选)之后的与自己前缀和相等的位置数量即可。

    嗯……怎么求第一个小于自己的数的位置?关键是怎么求在此基础上的与(i)前缀和相等的数量?由于每次前缀和只会(+1)(-1),那么第一个比(x)小的必然是(x-1),可以用一个数组存一下位置。同理,当某一个前缀和为(x)时,把(x+1)的数量全都清空就可以求出在某个位置之后的答案……这样似乎就没有问题了,好的,开打。

    (9:30)
    打完了,测一下小样例,过了……测一下大样例,没出结果……由于有(Dfs),大概是栈爆了,那就手动扩栈吧……但是扩栈的编译命令是啥啊……心血来潮准备翻一下(MinGW)的文件找找有没有哪里记录了编译命令大全……用(ctrl+F)宛如智障般在各种庞大的文件里找了(20)分钟,找到几个看起来像的,加上去,还是都编译不过。

    (9:50)
    放弃扩栈了,输出中间结果看看,毕竟大样例本身也很水,就是一条左括号右括号轮流来的链……除了有点臭以外。中间结果似乎没有大问题……算了,做第三题去吧……

    (10:00)
    第三题数据范围(nleq 2000)(5)组数据,明显要你(n^2)做一次,可是乍一看我怎么只会个(n!)暴力啊……看来难度不低(后来发现,应该是今年最难的一题)。

    别慌,考虑第一位,显然应该让数字(1)去到最小的编号,那不就是(1)号位吗?那么这条路径上的边全都要被选,并且数字(1)一开始在的位置的其他边,都不能先选,一旦选了就会把(1)换走。目标节点的其他边必须在(1)到达之前全都选完,不然最终(1)不能停留在目标点。中间路径上的边有一定的选择次序,并且支出去的边不能在(1)号点被换到这个位置的时候选。

    诶等等,数字(1)还不一定能去(1)号点啊,如果自己就在(1)号点,一定会被换出去……然后数字(1)去了该去的位置,又留下了一堆限制,数字(2)的移动大大受限……

    想了(1)小时……还是想不到正解……

    (11:00)
    算了算了想暴力吧。
    (nleq 10),阶乘枚举边换的顺序即可;
    链,(1)号点去除了自己以外的最小位置,中间这条路的显然是一路换过去,但是第(2)个呢?中间的边都被换了不能走,那么就把被换了的边打个标记,只能访问到没换的边……等等,(1)也不一定是最先换的,目标位置后面的边要先选,不然(1)会被换走……啊啊啊啊没时间了,直接开打吧。

    (11:20)
    阶乘枚举为什么也这么难打啊,输入不是按树上的数给出,而是给出数字的标号,还要转化一次,(Dfs)还要记录(vis),时间不够用啊……小样例调试过了,但时间不够了。打链甚至要先找一下端点,又是多组数据还要清空度数数组……没时间了,调不出来……

    (11:55)
    放弃第三题的链,留了个错误算法看能骗多少分吧……检查了前面的文件,没什么问题,时间非常赶。

    (After)
    问了大家的成绩,好像都差不多,勉强心态平衡了,(210)大众分,回去看了一下午的老番。

    (Day2)

    (8:30)
    还是按顺序做。第一题,感觉蛮套路的,容斥原理呼之欲出了,不过超过一半这个条件似乎不知道总数就没有办法?那么还要枚举总数,复杂度一算(O(mn^3)),过不了。

    思索良久,发现一半的限制有点微妙,首先这会使得最多只有一种菜不合法,其次还等价于一种菜比其他所有菜加起来还要多。那么只需要一维状态记录差值,就可以不需要枚举总数了。思考大概花了20分钟,代码也挺好打的,细节不多,直接打。

    (9:20)
    打完代码,依然是测小样例和大样例,都能过,数组也没爆,还是计数问题,不太需要对拍,那就先这样了,去看第二题。

    看一眼数据范围,好大,只能(O(n)),而且还会爆(long long),需要手写高精度。再看,应该是个(DP),平方的费用有点像斜率优化,但又不是,因为还有一个段长递增的限制。感觉可以贪心,想了一个(O(n))的简单的错误算法,心想毕竟是第二题,有了高精度剩下的难度应该不会很高,还虚假地证明了这个算法的正确性。然后就直接开打了,打完一测大样例,瞬间挂掉,算上想这个错算法的时间相当于浪费了(30)分钟……幸好没有直接开打高精度,否则浪费的时间更多。

    (9:50)
    在想是代码问题还是思路问题,结果构造了一个小样例,也挂了,瞬间明白之前的证明不严谨,这个算法是错的,而且没法修补……一看时间还有,继续刚,又废了(40)分钟。

    (10:30)
    不行,再刚就没时间做第三题了……先看一下第三题,割边后求重心标号?好像更不可做,那就留点时间打暴力就行了吧……继续想,仍然没有想法,转头想暴力,想到了一个(O(n^2))的,真的没有时间了,拿完(64)分就跑吧。到调完程序,花了(40)分钟。

    (11:10)
    上来就准备打暴力,小的时候暴力断边求重心,链可以在序列上做,满二叉树情况很简单,断开的边下方的点必然是,剩下的重心肯定是根和根的两个儿子之一。一共(75)分的部分分,后悔没有早点看到。

    不过几个暴力也不是很好写……打到最后只剩(10)分钟了,链和暴力都搞定了,满二叉树打完了死活调不过去,只能注释掉求稳,把前面的都分都留住,去检查文件名和(freopen)了。

    出考场,发现第二题有一个单调性,很好拿到(88)分,人均(88)就我没有。并且第三题满二叉树的方法也挺好写的,我写复杂了。心情不太好,回去测试,(100+64+55=219),基本没有可波动的余地了。总分大概是(429),靠民间数据大致测出省内(13)左右,凉凉。

    赛后总结

    总算是付出代价了。

    划水不会有好下场,知识点理解不够深入,(D2T2)大家都能看出来的单调性优化(DP)没有看出来,(D2T3)树重心的性质理解不够深入,让我面对(Day2)几乎(AK)(t3 100pts)(t2)只是没写高精度才(88pts))的各校巨佬们无法望其项背;考试策略大大失误,导致从本应还算稳的名次((100+100+10+100+88+75=473))跌到如此境地((473-24-20=429)),进省队都悬。

    逆水行舟,不进则退,每个人都在努力着,我再不脚踏实地继续向前,只会输得更惨。在一个比较弱的省,名次还能看,必须引以为戒,省选必夺名次。

    补题情况

    (D1t1)

    递归,或者找规律:(koplus(k>>1))
    (Code:)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define ull unsigned long long
    int n;
    ull k, s, t;
    int main()
    {
    	cin >> n >> k;
    	for (int i = 1; i <= n; i++)
    		s = s << 1 | 1;
    	for (int i = n; i >= 1; i--)
    	{
    		t = s >> 1;
    		if (k <= t)
    			putchar('0');
    		else
    		{
    			putchar('1');
    			k = s - k;
    		}
    		s = t;
    	}
    	putchar(10);
    }
    

    (D1t2)

    (Dfs)序求每个点为右端点的合法括号序列个数。用一个(sum)数组记录根到(i)的路径和,(num)数组记录当前缀和为(x)的有(num_x)个。每次(f_i=f_{fa_i}+num_{sum_i})。当(sum_i=x)时,递归到子树内时将(num_{x+1})清零,退栈时还原。
    (Code:)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define N 500005
    #define ll long long
    void Read(int &p)
    {
    	p = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar());
    	for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
    }
    int n, fat[N], tar[N], nex[N], fir[N], cnt;
    int sum[N], num[N << 1];
    ll f[N], ans;
    char S[N];
    void Add(int a, int b)
    {
    	++cnt;
    	tar[cnt] = b;
    	nex[cnt] = fir[a];
    	fir[a] = cnt;
    }
    void Dfs(int r)
    {
    	sum[r] = sum[fat[r]] + (S[r] == '(' ? 1 : -1);
    	f[r] = f[fat[r]] + num[sum[r] + n];
    	ans = ans ^ (r * f[r]);
    	int s = num[sum[r] + n + 1];
    	num[sum[r] + n]++;
    	num[sum[r] + n + 1] = 0;
    	for (int i = fir[r]; i; i = nex[i])
    	{
    		int v = tar[i];
    		Dfs(v);
    	}
    	num[sum[r] + n]--;
    	num[sum[r] + n + 1] = s;
    }
    int main()
    {
    	Read(n);
    	scanf("%s", S + 1);
    	for (int i = 2; i <= n; i++)
    	{
    		Read(fat[i]);
    		Add(fat[i], i);
    	}
    	num[n] = 1;
    	Dfs(1);
    	printf("%lld
    ", ans);
    }
    

    (D1t3)

    从数字(1)(n)依次确定每个数最终到达的节点。

    考虑要从(x)节点到(y)节点,则对于路径上连续的三个点((i,j,k))必须要求((i,j))这条边要比((j,k))先使用,并且在((i,j))((j,k))之间没有任何与(j)连接的边被使用。同时,要求从(x)出发的边是(x)相连的所有边中第一个被使用的,到达(y)的边是与(y)相连的所有边中最后一个被使用的。

    考虑中间比较一般化的条件,虽然((i,j))((j,k))之间不能先用与(j)相连的边,但仍然可以使用与(j)无关的边,导致问题复杂化。不妨考虑一个点(j)相连的所有边的次序,由于与(j)无关的边不在考虑范围内,条件变成选择了一条边后立刻选择另一条边,可以用一条边((j,i)->(j,k))表示选择关于(j)((i,j))这条边后要马上选择((j,k))

    单独对于这个点来说,只要附近的边不连成环,且没有点出度或入度超过(1),就合法了。考虑不同的点之间的边,发现彼此之间互不影响。也即是我们只需要保证每个点的连接都合法,就能保证全局合法。

    在考虑加入第一和最后的限制。可以对每个点虚拟两条边(st_i)(ed_i),表示第一个选择某条边,如(st_i)指向((i,j))表示((i,j))这条边是关于(i)的所有边中第一个选的(需要注意的是,每条边应该拆成两部分分别给连接到两个端点,彼此之间不应互相影响)(ed_i)同理。此时还需再加一个条件,若存在一条(st_i o (i,j) ocdots o(i,k) o ed_i)的链,必须把与(i)相连的所有边都用上,否则会导致剩余的边没有地方加入。

    那么中途做的时候,按照(Dfs)序寻找最小的能够作为终点的节点,中途假装连边判断是否合法,最后选择最小的合法终点作为答案,进行真实连边,复杂度(O(n^2)),代码流畅自然。
    (Code:)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define N 2005
    int t, n, A[N];
    int tar[N << 1], nex[N << 1], fir[N], cnt = 1;
    int lef[N << 2], rig[N << 2], bel[N << 2], siz[N << 2];
    int deg[N], fat[N], lin[N], st[N], ed[N], chs;
    void Read(int &p)
    {
    	p = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar());
    	for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
    }
    void Add(int a, int b)
    {
    	++cnt;
    	tar[cnt] = b;
    	nex[cnt] = fir[a];
    	fir[a] = cnt;
    	deg[a]++;
    }
    int Getbel(int x)
    {
    	if (!bel[x])
    		return x;
    	return bel[x] = Getbel(bel[x]);
    }
    int Check(int r, int p1, int p2)
    {
    	if (rig[p1] == p2 && lef[p2] == p1)
    		return 1;
    	if (rig[p1] || lef[p2])
    		return 0;
    	int a = Getbel(p1), b = Getbel(p2);
    	int c = Getbel(st[r]), d = Getbel(ed[r]);
    	if (a == b)
    		return 0;
    	if (a == c && b == d)
    		if (siz[a] + siz[b] != deg[r] + 2)
    			return 0;
    	return 1;
    }
    void Link(int a, int b)//a -> b
    {
    	rig[a] = b;
    	lef[b] = a;
    	int x = Getbel(a), y = Getbel(b);
    	bel[x] = y;
    	siz[y] += siz[x];
    }
    void Dfs(int r, int lst)
    {
    	if (Check(r, lst, ed[r]))
    		chs = min(chs, r);
    	lin[r] = lst;
    	for (int i = fir[r]; i; i = nex[i])
    	{
    		int v = tar[i];
    		if (v != fat[r])
    			if (Check(r, lst, i))
    			{
    				fat[v] = r;
    				Dfs(v, i ^ 1);
    			}
    	}
    }
    int main()
    {
    	Read(t);
    	for (; t--; )
    	{
    		cnt = 1;
    		memset(fir, 0, sizeof(fir));
    		memset(deg, 0, sizeof(deg));
    		memset(lef, 0, sizeof(lef));
    		memset(rig, 0, sizeof(rig));
    		Read(n);
    		for (int i = 1; i <= n; i++)
    			Read(A[i]);
    		for (int i = 1; i < n; i++)
    		{
    			int u, v;
    			Read(u), Read(v);
    			Add(u, v), Add(v, u);
    		}
    		for (int i = 1; i <= n; i++)
    		{
    			st[i] = ++cnt;
    			ed[i] = ++cnt;
    		}
    		for (int i = 1; i <= cnt; i++)
    			bel[i] = 0, siz[i] = 1;
    		for (int i = 1; i <= n; i++)
    		{
    			chs = n + 1;
    			fat[A[i]] = 0;
    			Dfs(A[i], st[A[i]]);
    			printf("%d%c", chs, i == n ? 10 : 32);
    			int now = ed[chs];
    			for (; ; )
    			{
    				Link(lin[chs], now);
    				if (chs == A[i])
    					break;
    				now = lin[chs] ^ 1;
    				chs = fat[chs];
    			}
    		}
    	}
    }
    

    (D2t1)

    首先求出没有第三个限制的答案。容斥减去不合法的方案。由于不合法的方案要求某一种菜用了一半以上,因此最多只有一种菜不合法。

    考虑(i)不合法:
    (i)超过所有菜的一半(iff)(i)比其他菜加起来还多(iff)(i)(-)其他菜之和(>0)

    于是枚举菜(x)后轻松(DP)(f_{i,j})表示前(i)种烹饪方式,(x)的数量(-)其他菜的数量(=j)的方案数,转移分为:做(x o f_{i+1,j+1}),做其他( o f_{i+1,j-1}),不做菜( o f_{i+1,j})
    (Code:)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define N 105
    #define M 2005
    #define ll long long
    #define mod 998244353
    void Read(int &p)
    {
    	p = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar());
    	for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
    }
    int A[N][M], S[N];
    int f[2][N << 1];
    int n, m, ans;
    int main()
    {
    	Read(n), Read(m);
    	ans = 1;
    	for (int i = 1; i <= n; i++)
    	{
    		for (int j = 1; j <= m; j++)
    		{
    			Read(A[i][j]);
    			S[i] = (S[i] + A[i][j]) % mod;
    		}
    		ans = (ll)ans * (S[i] + 1) % mod;
    	}
    	ans = (ans - 1) % mod;
    	for (int i = 1; i <= m; i++)
    	{
    		memset(f, 0, sizeof(f));
    		f[0][n] = 1;
    		int z = 0;
    		for (int j = 1; j <= n; j++)
    		{
    			for (int k = 0; k <= 2 * n; k++)
    			{
    				if (!f[z][k])continue;
    				f[z ^ 1][k] = (f[z ^ 1][k] + f[z][k]) % mod;
    				if (k)
    					f[z ^ 1][k - 1] = (f[z ^ 1][k - 1] + (ll)f[z][k] * (S[j] - A[j][i])) % mod;
    				if (k < 2 * n)
    					f[z ^ 1][k + 1] = (f[z ^ 1][k + 1] + (ll)f[z][k] * A[j][i]) % mod;
    				f[z][k] = 0;
    			}
    			z ^= 1;
    		}
    		for (int k = n + 1; k <= 2 * n; k++)
    			ans = (ans - f[z][k]) % mod;
    	}
    	if (ans < 0)ans += mod;
    	printf("%d
    ", ans);
    }
    

    (D2t2)

    一个结论,最后一段在合法的情况下尽量小时,总答案也一定最小。理性感知,感性理解。

    (f_i)表示前(i)个划分,最后一段最小为(f_i)。记(S_i)(A_i)的前缀和,则(f_i=min(S_i-S_j))满足(S_i-S_jgeq f_j)。这个直接单调队列优化(DP)就行了。

    由于要高精度,而且卡内存,不能中途记录平方和,否则会(MLE),应该最后用一个高精度数复现过程,累加答案。
    (Code:)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define N 40000005
    #define ll long long
    int n, t;
    int g[N], q[N], head, tail;
    ll f[N], S[N];
    struct Big
    {
    	int A[5];
    	Big(){memset(A, 0, sizeof(A));}
    	Big(ll x)
    	{
    		A[0] = x % 1000000000;
    		A[1] = x / 1000000000;
    		A[2] = A[3] = A[4] = 0;
    	}
    	void operator += (Big &x)
    	{
    		for (int i = 0; i < 5; i++)
    		{
    			A[i] += x.A[i];
    			if (A[i] >= 1000000000)
    			{
    				A[i + 1] += A[i] / 1000000000;
    				A[i] %= 1000000000;
    			}
    		}
    	}
    	void operator *= (Big &x)
    	{
    		Big c;
    		ll now = 0;
    		for (int i = 0; i < 5; i++)
    		{
    			for (int j = 0; j <= i; j++)
    				now += (ll)A[j] * x.A[i - j];
    			c.A[i] = now % 1000000000;
    			now /= 1000000000;
    		}
    		for (int i = 0; i < 5; i++)
    			A[i] = c.A[i];
    	}
    	void Output()
    	{
    		int c = 0;
    		for (int i = 4; i >= 0; i--)
    			if (!c)
    			{
    				if (A[i])
    					printf("%d", A[i]), c = 1;
    			}
    			else
    				printf("%09d", A[i]);
    		putchar(10);
    	}
    }ans, val;
    void Read(int &p)
    {
    	p = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar());
    	for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
    }
    int main()
    {
    	Read(n), Read(t);
    	if (!t)
    	{
    		for (int i = 1; i <= n; i++)
    		{
    			int now;
    			Read(now);
    			S[i] = S[i - 1] + now;
    		}
    	}
    	else
    	{
    		int x, y, z, b1, b2, m;
    		Read(x), Read(y), Read(z);
    		Read(b1), Read(b2), Read(m);
    		int s = 1;
    		for (int i = 1; i <= m; i++)
    		{
    			int p, l, r;
    			Read(p), Read(l), Read(r);
    			while (s <= p)
    			{
    				int b3;
    				if (s == 1)
    					b3 = b1;
    				else
    					if (s == 2)
    						b3 = b2;
    					else
    						b3 = ((ll)b2 * x + (ll)b1 * y + z) % (1 << 30);
    				S[s] = b3 % (r - l + 1) + l;
    				S[s] += S[s - 1];
    				if (s > 2)
    					b1 = b2, b2 = b3;
    				s++;
    			}
    		}
    	}
    	q[head = tail = 1] = 0;
    	for (int i = 1; i <= n; i++)
    	{
    		while (head < tail && f[q[head + 1]] + S[q[head + 1]] <= S[i])head++;
    		f[i] = S[i] - S[q[head]];
    		g[i] = q[head];
    		while (head <= tail && f[q[tail]] + S[q[tail]] >= f[i] + S[i])tail--;
    		q[++tail] = i;
    	}
    	int now = n;
    	for (; now; )
    	{
    		val = Big(f[now]);
    		val *= val;
    		ans += val;
    		now = g[now];
    	}
    	ans.Output();
    }
    

    (D2t3)

    先任意指定一个根,考虑如何快速求每个点为根的子树的重心。
    由于可能有(2)个重心,我们保存深度更大的一个,加答案的时候只需要判断父亲是否也是重心就好了。

    一个很显然的结论:一棵树的重心必然在根的重链上(如果走轻儿子,绝不会比重儿子更优)。设(f_i)(i)为根的子树的重心,(w)(i)的重儿子,则(f_i)必然是(f_w)的祖先,因为在同一条重链上,并且外部的点增多,重心必然上移,暴力往上跳到第一个可以为重心的点,最终复杂度(O(n)),因为每个点只会出现在一条重链上。

    所以我们可以求出断掉一条边后下方的重心了。然后想办法求上方的重心。考虑全局根节点的重链,若断掉的是根节点的轻儿子方向的边,则根节点的重链没有变化,仅仅只让总点数变少了,于是可以预处理出,总点数减少(x),根节点的重链上重心变成了哪个点,可以让(x)(1)(n)递增处理,重心会渐渐下移,处理复杂度是(O(n))

    若断开的是根的重儿子方向的边,则根的重儿子可能会变成第二大的儿子,若变了,那么预处理一个次重链(根节点走次重儿子,其他点都走重儿子的链)的答案,类似于重链的方法。

    最关键的地方,若断开后重儿子还是原来的重儿子?此时虽然重儿子没有变,但重链可能在某些地方歪掉了,不好求。

    那么我们在一开始选根的时候不乱选,而是选择原树的一个重心为根,重儿子没有变化的情况下,重心还在重链上,并且由于重儿子变轻了,重心只可能上移,于是新的重心还是根!
    (Code:)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define N 300005
    #define ll long long
    void Read(int &p)
    {
    	p = 0;
    	char c = getchar();
    	for (; c < '0' || c > '9'; c = getchar());
    	for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
    }
    int w, n, t;
    int tar[N << 1], nex[N << 1], fir[N], cnt;
    int siz[N], val[N], fat[N], son[N], moe, oth;
    int f[N], h[N][2];
    ll ans;
    void Add(int a, int b)
    {
    	++cnt;
    	tar[cnt] = b;
    	nex[cnt] = fir[a];
    	fir[a] = cnt;
    }
    void Findroot(int r, int fa)
    {
    	siz[r] = 1, val[r] = 0;
    	for (int i = fir[r]; i; i = nex[i])
    	{
    		int v = tar[i];
    		if (v != fa)
    		{
    			Findroot(v, r);
    			siz[r] += siz[v];
    			val[r] = max(val[r], siz[v]);
    		}
    	}
    	if (max(val[r], n - siz[r]) * 2 <= n)
    		t = r;
    }
    void Dfs(int r)
    {
    	siz[r] = 1, val[r] = 0;
    	son[r] = 0;
    	for (int i = fir[r]; i; i = nex[i])
    	{
    		int v = tar[i];
    		if (v != fat[r])
    		{
    			fat[v] = r;
    			Dfs(v);
    			siz[r] += siz[v];
    			val[r] = max(val[r], siz[v]);
    			if (siz[v] > siz[son[r]])
    				son[r] = v;
    		}
    	}
    	if (r != t)
    	{
    		if (son[r])
    		{
    			f[r] = f[son[r]];
    			while (max(val[f[r]], siz[r] - siz[f[r]]) * 2 > siz[r])
    				f[r] = fat[f[r]];
    			ans += f[r];
    			if (f[r] != r && max(val[fat[f[r]]], siz[r] - siz[fat[f[r]]]) * 2 <= siz[r])
    				ans += fat[f[r]];
    		}
    		else
    		{
    			f[r] = r;
    			ans += f[r];
    		}
    	}
    }
    void Dfs2(int r, int s)
    {
    	if (r != t)
    	{
    		if (s == moe)
    		{
    			if (siz[moe] - siz[r] >= siz[oth])
    				ans += t;
    			else
    				ans += h[siz[r]][1];
    		}
    		else
    			ans += h[siz[r]][0];
    	}
    	for (int i = fir[r]; i; i = nex[i])
    	{
    		int v = tar[i];
    		if (v != fat[r])
    			Dfs2(v, s ? s : v);
    	}
    }
    int main()
    {
    	Read(w);
    	for (; w--; )
    	{
    		Read(n);
    		cnt = 0;
    		memset(fir, 0, sizeof(fir));
    		for (int i = 1; i < n; i++)
    		{
    			int u, v;
    			Read(u), Read(v);
    			Add(u, v), Add(v, u);
    		}
    		ans = 0;
    		Findroot(1, 0);
    		fat[t] = 0;
    		Dfs(t);
    		moe = son[t], oth = 0;
    		for (int i = fir[t]; i; i = nex[i])
    		{
    			int v = tar[i];
    			if (v != son[t] && siz[v] > siz[oth])
    				oth = v;
    		}
    		int now = t;
    		for (int i = 1; i <= siz[oth]; i++)
    		{
    			while (max(val[now], n - i - siz[now]) * 2 > n - i)
    				now = son[now];
    			h[i][0] = now;
    			if (son[now] && max(val[son[now]], n - i - siz[son[now]]) * 2 <= n - i)
    				h[i][0] += son[now];
    		}
    		if (oth)
    		{
    			son[t] = oth;
    			val[t] = siz[oth];
    			now = t;
    			for (int i = siz[moe] - siz[oth] + 1; i <= siz[moe]; i++)
    			{
    				while (max(val[now], n - i - siz[now]) * 2 > n - i)
    					now = son[now];
    				h[i][1] = now;
    				if (son[now] && max(val[son[now]], n - i - siz[son[now]]) * 2 <= n - i)
    					h[i][1] += son[now];
    			}
    		}
    		Dfs2(t, 0);
    		printf("%lld
    ", ans);
    	}
    }
    
  • 相关阅读:
    解决使用gomod后goland导包报红问题
    Golang写文件的坑
    Golang去除字符串前后空格
    Golang通过结构体解析和封装XML
    Golang获取CPU、内存、硬盘使用率
    Golang数组和切片的区别
    Golang修改操作系统时间
    Golang中GBK和UTF8编码格式互转
    Golang中的各种时间操作
    Golang十六进制字符串和byte数组互转
  • 原文地址:https://www.cnblogs.com/ModestStarlight/p/11918411.html
Copyright © 2011-2022 走看看