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\) 后,要满足

\[\sum_{i=1}^n\lceil\frac{a_i}d\rceil d-a_i\le k \]

\[\sum_{i=1}^n\lceil\frac{a_i}d\rceil d\le k+\sum_{i=1}^na_i \]

设右边为 \(s\),由于左边是整数,则

\[\sum_{i=1}^n\lceil\frac{a_i}d\rceil\le\lfloor\frac sd\rfloor \]

发现 \(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;
}
posted @ 2021-05-06 16:26  401rk8  阅读(78)  评论(0编辑  收藏  举报