「杂录」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\)
第三题数据范围\(n\leq 2000\),\(5\)组数据,明显要你\(n^2\)做一次,可是乍一看我怎么只会个\(n!\)暴力啊……看来难度不低(后来发现,应该是今年最难的一题)。
别慌,考虑第一位,显然应该让数字\(1\)去到最小的编号,那不就是\(1\)号位吗?那么这条路径上的边全都要被选,并且数字\(1\)一开始在的位置的其他边,都不能先选,一旦选了就会把\(1\)换走。目标节点的其他边必须在\(1\)到达之前全都选完,不然最终\(1\)不能停留在目标点。中间路径上的边有一定的选择次序,并且支出去的边不能在\(1\)号点被换到这个位置的时候选。
诶等等,数字\(1\)还不一定能去\(1\)号点啊,如果自己就在\(1\)号点,一定会被换出去……然后数字\(1\)去了该去的位置,又留下了一堆限制,数字\(2\)的移动大大受限……
想了\(1\)小时……还是想不到正解……
\(11:00\)
算了算了想暴力吧。
\(n\leq 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\)
递归,或者找规律:\(k\oplus(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\n", 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\to (i,j)\to\cdots\to(i,k)\to 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\to f_{i+1,j+1}\),做其他\(\to f_{i+1,j-1}\),不做菜\(\to 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\n", ans);
}
\(D2t2\)
一个结论,最后一段在合法的情况下尽量小时,总答案也一定最小。理性感知,感性理解。
设\(f_i\)表示前\(i\)个划分,最后一段最小为\(f_i\)。记\(S_i\)为\(A_i\)的前缀和,则\(f_i=min(S_i-S_j)\)满足\(S_i-S_j\geq 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\n", ans);
}
}
「杂录」CSP-S 2019 爆炸记&题解的更多相关文章
- 「杂录」CQOI 2018 背板记
背景 经过一天天的等待,终于迎来了\(CQOI2018\),想想\(NOIp\)过后到现在,已经有了快要半年了,曾经遥遥无期,没想到时间一转眼就过去了-- 日志 \(Day0\) 因为明天就要考试了, ...
- noip2017爆炸记——题解&总结&反省(普及组+提高组)
相关链接: noip2018总结 noip2017是我见过的有史以来最坑爹的一场考试了. 今年北京市考点有一个是我们学校,我还恰好被分到了自己学校(还是自己天天上课的那个教室),于是我同时报了普及提高 ...
- [LOJ 2022]「AHOI / HNOI2017」队长快跑
[LOJ 2022]「AHOI / HNOI2017」队长快跑 链接 链接 题解 不难看出,除了影响到起点和终点的射线以外,射线的角度没有意义,因为如果一定要从该射线的射出一侧过去,必然会撞到射线 因 ...
- 「ZJOI2019」&「十二省联考 2019」题解索引
「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...
- LOJ #3049. 「十二省联考 2019」字符串问题
LOJ #3049. 「十二省联考 2019」字符串问题 https://loj.ac/problem/3049 题意:给你\(na\)个\(A\)类串,\(nb\)个\(B\)类串,\(m\)组支配 ...
- 【LOJ】#3051. 「十二省联考 2019」皮配
LOJ#3051. 「十二省联考 2019」皮配 当时我在考场上觉得这题很不可做... 当然,出了考场后再做,我还是没发现学校和城市是可以分开的,导致我还是不会 事实上,若一个城市投靠了某个阵营,学校 ...
- 「编程羽录」上线,程序员必备的这些技能你能get到嘛?
大家好,我是小羽. 好久不见,给大家带来个好消息,小羽的全新专题「编程羽录」系列正式上新,主要是介绍一些关于面试题和经验总结的文章. 会为大家提供一些技术栈之外,程序员还需要的其他方面硬核知识,做到全 ...
- 「十二省联考 2019」皮配——dp
题目 [题目描述] #### 题目背景一年一度的综艺节目<中国好码农>又开始了.本季度,好码农由 Yazid.Zayid.小 R.大 R 四位梦想导师坐镇,他们都将组建自己的梦想战队,并率 ...
- 「十二省联考 2019」字符串问题——SAM+DAG
题目 [题目描述] Yazid 和 Tiffany 喜欢字符串问题.在这里,我们将给你介绍一些关于字符串的基本概念. 对于一个字符串 $S$, 我们定义 $\lvert S\rvert$ 表示 $S$ ...
随机推荐
- Spring AOP(面向切面编程)
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理.日志管理.权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性. 简单例子: ...
- iOS开源库分类
语言库 rx aop kvo 功能库 UI network data-model-map cache 跨平台库 wkjscorebridge jspatch 性能监控库:友盟 部署库:jspathc ...
- log4j.properties的配置信息
吃了没日志的亏,以前总以为日志没用,以后要重视起来了,很多错误服务器不会显示,但是页面上就是出错,这个时候就要显示日志了. 日志的代码如下,创建日志文件,文件名为log4j.properties,把这 ...
- 树上背包DP Luogu P2014 选课
#include <cstdio> #include <cctype> #include <cstring> #include <algorithm> ...
- orm-1
- ORM什么是? 类名 ---> 数据库表 对象 ---> 记录 对象.属性 ---> 字段 - ORM的优缺点: 优点: 可跨平台,可以通过对象.属性取值,对象.方法,让该方法内 ...
- 传统IT容量估算思路
参考: https://www.cnblogs.com/zhangweizhong/p/5844961.html https://www.cnblogs.com/zhangweizhong/p/577 ...
- About me recently
About me recently Recently I fell that memory has always been problematic.Maybe I hava bee too tired ...
- Spring中@Bean与@Configuration
一.Spring中Bean及@Bean的理解[1] Bean在Spring和SpringMVC中无所不在,将这个概念内化很重要,下面分享一下我的想法: 一.Bean是啥 1.Java面向对象,对象有方 ...
- leetcode 221. 最大正方形
题目描述: 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积. 思路分析: 一道动态规划的题.由于是正方形,首先单一的‘1’即为最小的正方形,接下来需要考察其外围区域 ...
- Three.js 快速上手以及在 React 中运用[转]
https://juejin.im/post/5ca22692f265da30a53d6656 github 的地址 欢迎 star! 之前项目中用到了 3D 模型演示的问题,整理了一下之前学习总结以 ...