20210503
考场
一开场ycx就说T3是原题,还高兴了一下,结果发现是超级树。。。
T1由于前天刚考过一次欧拉路,很快想到先连 \(2m\) 条边在计算删两条的合法方案数,30min写完拍上,第二组就挂了,发现没有考虑自环,加上后没拍几组又挂了,仔细想了想重写了自环的部分就能过对拍了。T1大概用了40min。
然后去上厕所,路上想到T2的二分做法,感觉不难写,T3虽然很难,但毕竟做过,在加上大量时间应该能搞出来 那岂不是要AK了
有了mod一题的教训,没有直接写T2的二分,用暴力打了一下表,发现没有单调性。gg。胡了个退火上去。
开始想T3,发现自己仅存的记忆是通过两个 \(k-1\) 树加一个根转移到 \(k\)、状态转移方程有4个、第二维要用奇怪的优化。。。没想到第二维的状态是什么,就回去写了T2 \(k\le1\) 的部分分,又调了一会退火让它能过 \(a\le 5\times10^5\) 的对拍。
最后不到1.5h想T3。先写了暴力打表,\(k=5\) 大约跑了7min,但 \(k=6\) 到结束都没跑出来。尝试DP。第二维为链长,时空复杂度 \(O(k 2^{2k})\), 能过40pts,但是写挂了。(应该是因为没有将子树内部的路径累加到当前树中)
尝试了一下新学的科技:
#ifdef
#ifndef
#endif
res
\(80+50+15\), rk1
hkh \(30+60+0\), rk2
T1没有特判图不连通,挂了两个点(貌似有人因判错只过了2个点)。前天T3还有针对连通性的容斥,结果今天又忘了,“不知道你们每天在干什么——物理老师”
T2退火过了1个点。最高分hkh \(60\),数据大时二分过了2个
T3只有打表的分,数据水(有 \(k=5\) 的点)多过了1个点
星际旅行
根据欧拉路对度数的要求和组合数随便推一推就行了,注意自环的部分(一定要拍)
由于是欧拉路,所以连通性是对于边而言,不能直接判断点是否在一个连通块(可能有孤点),应判断有连边的点是否在一个连通块。
code
const int N = 1e5+5;
int n,m;
int anc,fa[N];
bool vis[N];
LL deg[N],cir,ans;
int find(int x) { return fa[x]==x ? x : fa[x]=find(fa[x]); }
void merge(int x,int y) {
vis[x] = vis[y] = 1;
x = find(x), y = find(y);
if( x == y ) return;
fa[x] = y;
}
int main() {
read(n,m);
For(i,1,n) fa[i] = i;
For(i,1,m) {
int x,y; read(x,y);
if( x == y ) ++cir;
else { ++deg[x], ++deg[y]; merge(x,y); }
}
For(i,1,n) {
if( !vis[i] ) continue;
if( !anc ) anc = find(i);
else if( anc != find(i) ) { anc = 0; break; }
}
if( !anc ) { putchar('0'); return 0; }
For(i,1,n) ans += deg[i] * (deg[i]-1) / 2;
ans += cir * (cir-1) / 2 + cir * (m-cir);
printf("%lld",ans);
return 0;
}
砍树
在确定 \(d\) 后,要满足
设右边为 \(s\),由于左边是整数,则
发现 \(d\) 增大时,左边不增,即右边值一定时,\(d\) 越大越容易满足上式,同时也越优。右边显然可以整除分块,而 \(d\) 的最优值一定在每一段的右端点。时间复杂度 \(O(n\sqrt{k+\sum_{i=1}^na_i})\)
另:不难发现 \(d\) 的上界为 \(k+\max\{a_i\}\)
code
const int N = 105;
int n;
LL k,a[N];
LL mxd,ans;
LL cost(LL d) {
LL res = 0;
For(i,1,n) res += (a[i]+d-1) / d;
return res * d;
}
int main() {
read(n,k);
mxd = k;
For(i,1,n) read(a[i]), k += a[i];
mxd += *max_element(a+1,a+n+1);
for(LL d = 1; ; d = k/(k/(d+1))) { // k即为sol中的s
if( cost(d) <= k ) ans = d;
if( d >= mxd ) break;
}
printf("%lld",ans);
return 0;
}
超级树
比较难的是状态:\(f[i,j]\) 为 \(i\)-超级树中存在 \(j\) 条不相交路径的方案数。
转移分4种:
- 不用根
- 根单独作为一条路径
- 根与一条路径相连(注意可以连起点或终点)
- 根与两条路径相连(找两条路径,连第一条的终点、根、第二条起点。由于这两条路径有序,因此不用考虑起点或终点)
另一种做法是分5类(本质一样):DeepinC
答案为 \(f[k,1]\)。发现朴素DP的第二维高达 \(2^{2k}\),但观察转移方程,\(f[i,j]\) 只能更新到 \(f[i+1,j+1],f[i+1,j],f[i+1,j-1]\),即第二维每次最多下降 \(1\),因此第二维只需要计算到 \(k-i+1\)。时空复杂度 \(O(k^3)\)
关于状态如何想到,首先一个 \(k\)-超级树由两个 \(k-1\)-超级树+根组成不难想到,根据数据范围猜时间复杂度为 \(O(k^3)\),进一步,代码应该是先枚举 \(k\),再分别枚举左右子树中的一些东西加到整棵树上。
一维肯定无法转移,需要两维。如果第二维是路径长,则时空复杂度过大。发现我们并不关心具体的路径或长度,这样统计了一些无用信息,考虑直接统计尽可能多的路径条数。
根据题目中要求1条路径的方案数,而前面枚举了左右子树,可以想到通过拼接左右子树的2条路径来算,子树的2条路径又可以由它的子树的3条路径求得,于是想到设第二维为有 \(i\) 条不相交路径的方案数。(不相交是为了避免算重)
非常牵强。大家多看看其他大佬的blog吧。。。
code
const int N = 305;
int n,mod;
LL f[N][N];
int main() {
read(n,mod);
f[1][0] = f[1][1] = 1;
for(int i = 1; i < n; ++i)
For(l,0,n-i+1) For(r,0,n-i+1-l+1) {
LL mul = f[i][l] * f[i][r] %mod;
f[i+1][l+r] = (f[i+1][l+r] + mul) %mod;
f[i+1][l+r+1] = (f[i+1][l+r+1] + mul) %mod;
f[i+1][l+r] = (f[i+1][l+r] + (l+r)*2 * mul) %mod;
f[i+1][l+r-1] = (f[i+1][l+r-1] + (l+r)*(l+r-1) * mul) %mod;
}
printf("%lld",f[n][1]%mod); // mod可能为1
return 0;
}