正睿2020提高组十连测 选做

更新中...

day1-T3-random

题目链接

理解题意,打出第一个暴力

首先需要知道,随机生成的树(如果不特别说明随机方法,就是指在所有\(n^{n-2}\)棵树里随机),最大深度期望是\(O(\sqrt{n})\)的。

看本题。首先,期望是吓唬你的,因为期望乘上阶乘,其实就是求和。于是我们要求【所有排列下【操作后所有点的权值和】的和】。因此暴力做法就是枚举一个排列,模拟操作,时间复杂度\(O(n!\cdot \text{poly}(n))\)。值得一提的是,操作时,枚举子树里所有的点,可以通过\(\text{dfs}\)序实现不重不漏的枚举,比较优美。


深度对应系数,从树上问题到序列问题

继续优化。首先发现,一个点\(u\)的权值\(a_u\),在完成所有操作后,能贡献到另一个点\(v\)的权值上,当且仅当\(u\)\(v\)的祖先(其实这里强调的是必要性。充分性你可以理解为贡献为\(0\)也是贡献)。于是我们枚举每一对这样的\(u,v\)。考虑\(u,v\)的距离,也就是深度差。发现当深度差相等时,\(u\)\(v\)贡献的系数是一样的!

具体来说,假设\(u,v\)之间的路径上,共有\(d\)个点(含\(u\)\(v\))。我们预处理出一个系数\(f_d\)。则答案就是:

\[\sum_{\substack{u,v\\u\text{ is an ancestor of }v\\d=\text{dep}[v]-\text{dep}[u]+1}}a_u\times f_d\times {n\choose d}\times(n-d)! \]

其中\({n\choose d}\times (n-d)!\)是因为,除\(u,v\)路径以外的点,不会影响\(u\)\(v\)的贡献,所以可以任意排列。

考虑预处理出\(f_d\)。先看是什么,再看怎么求。\(f_d\)是什么呢?可以考虑一个长度为\(d\)的序列\(x_1,x_2,\dots ,x_d\),初始时等于\(1,0,0,\dots 0\)。我们枚举\(d!\)个排列,对每个排列\(p_1,p_2,\dots ,p_d\),依次对每个位置\(p_i\),把\(x_{p_i},x_{p_i+1},\dots ,x_{d}\)加上\(x_{p_i}\)。把每个排列下,最后得到的\(x_d\)的值加起来,就是\(f_d\)了。

也就是说,朴素求\(f_d\)的复杂度是\(d!\)的。因为\(d\leq \text{maxdep}=O(\sqrt{n})\),所以暴力预处理的复杂度\(O((\sqrt{n})!\cdot \text{poly}(\sqrt{n}))\)。也可以改成状压DP。都能比\(n!\)纯暴力多拿一些分。


递推求出系数

考虑递推求\(f_{1\dots \text{maxdep}}\)。特别地,我们定义\(f_1=1\)。可能有人会认为\(f_1=2\)(因为题目说了,子树加的时候包括自己)。但递推时,我们只考虑第一个点对最后一个点贡献了多少,既然是贡献就不算它原本自己有的一份。不过这样在上面求答案的那个式子里,可能\(u=v\)时就要特判一下了。

\(f_{d-1}\)递推到\(f_d\)时,考虑在\(1\)之后被操作到的点有几个。也就是说,假设排列里\(p_t=1\),那么在\(1\)之后被操作到的点就有\(d-t\)个,设\(i=d-t\),枚举\(i\) (\(0\leq i<d\))。得到如下递推式:

\[f_d=\sum_{i=0}^{d-1}{d-1\choose i}(d-i-1)!\left(i!+\sum_{j=1}^{i}f_j{i\choose j}(i-j)!\right) \]

解释一下这个式子:

显然,在操作到\(1\)之前,前面的操作都是无效的。因为它们的初始值都是\(0\)。这些东西可以任意排列,并且要选出哪几个位置排在\(1\)之前,于是乘上系数\({d-1\choose i}(d-i-1)!\)

接下来只考虑后\(i\)个被操作的点,它们的不同排列顺序下,产生的贡献之和。

操作到\(1\)时,会令\(x_2\dots x_d\)全部变成\(1\)。那么在所有情况下,\(1\)\(x_d\)会先有一个\(i!\)的贡献(因为后面的数有这么多种排列方式,每次的贡献都是\(1\))。

接下来,会依次对\(i\)个数进行操作,且它们初始值都是\(1\)。可以再用一次算贡献的思想,分别计算这\(i\)\(1\)的贡献,那么就是\(f_1+f_2+\dots +f_i\)。其中 \(f_j\) 是离 \(x_d\)\(j\) 近的、未被操作过的位置。对于每个 \(j\)\(f_j\) 里已经包含了它和它后面的数(以位置来说的“后面”)的所有排列方式,但它前面的、未被操作过的数,可以按任意顺序操作(\((i - j)!\) 种操作顺序)。并且前后两部分之间互不影响,各自确定了顺序后可以穿插在一起(\({i\choose j}\) 种穿插情况)。

朴素地按这个式子递推,复杂度是\(O(\text{maxdep}^3)\approx O(n\sqrt{n})\)。我没试过,不确定能不能AC。不过我们很容易将它优化到\(O(n)\)

定义:

\[g_i=\sum_{j=1}^{i}f_j{i\choose j}(i-j)! \]

则:

\[f_d=\sum_{i=0}^{d-1}{d-1\choose i}(d-i-1)!(i!+g_i) \]

每算出来一个\(f_d\)就更新一下它对\(g_{d\dots n}\)的贡献即可。

时间复杂度\(O(n)\)

参考代码:

//problem:ZR1534
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MAXN=1e5;
const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int fac[MAXN+5],ifac[MAXN+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim=MAXN){
	fac[0]=1;
	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	ifac[lim]=pow_mod(fac[lim],MOD-2);
	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}

int n,a[MAXN+5];
struct EDGE{int nxt,to;}edge[MAXN*2+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}

int dep[MAXN+5],dfn[MAXN+5],ofn[MAXN+5],rev[MAXN+5],cnt_dfn;
void dfs(int u,int fa){
	dfn[u]=++cnt_dfn;
	rev[cnt_dfn]=u;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa) continue;
		dep[v]=dep[u]+1;
		dfs(v,u);
	}
	ofn[u]=cnt_dfn;
}
int f[MAXN+5],g[MAXN+5];

int main() {
	cin>>n;
	facinit(n);
	for(int i=1;i<n;++i){
		int u,v; cin>>u>>v;
		add_edge(u,v);
		add_edge(v,u);
	}
	for(int i=1;i<=n;++i)
		cin>>a[i];
	dfs(1,0);
	int maxd=0;
	for(int i=1;i<=n;++i)
		ckmax(maxd,dep[i]+1);
	
	f[1]=1;
	for(int i=1;i<maxd;++i){
		g[i]=(ll)f[1]*comb(i,1)%MOD*fac[i-1]%MOD;
	}
	for(int d=2;d<=maxd;++d){
		for(int i=0;i<d;++i){
			f[d]=((ll)f[d] + (ll)comb(d-1,i)%MOD*fac[d-i-1]%MOD*(fac[i]+g[i])) %MOD;
		}
		for(int i=d;i<maxd;++i){
			g[i]=((ll)g[i] + (ll)f[d]*comb(i,d)%MOD*fac[i-d])%MOD;
		}
	}
	//for(int i=1;i<=maxd;++i)cout<<f[i]<<" ";cout<<endl;
	/*
	// 暴力
	static int ff[100],p[100],val[100];
	ff[1]=1;
	for(int d=2;d<=maxd;++d){
		for(int i=1;i<=d;++i)p[i]=i;
		do{
			for(int i=2;i<=d;++i)val[i]=0;
			val[1]=1;
			for(int i=1;i<=d;++i){
				int u=p[i];
				int x=val[u];
				for(int j=u;j<=d;++j){
					add(val[j],x);
				}
			}
			add(ff[d],val[d]);
		}while(next_permutation(p+1,p+d+1));
	}
	for(int i=1;i<=maxd;++i)cout<<ff[i]<<" ";cout<<endl;
	*/
	for(int i=1;i<=maxd;++i)f[i]=(ll)f[i]*comb(n,i)%MOD*fac[n-i]%MOD;
	int ans=0;
	for(int u=1;u<=n;++u){
		int sum=2*fac[n]%MOD;
		for(int i=dfn[u]+1;i<=ofn[u];++i){
			int v=rev[i];
			add(sum,f[dep[v]-dep[u]+1]);
		}
		ans=((ll)ans + (ll)sum*a[u])%MOD;
	}
	cout<<ans<<endl;
	return 0;
}

day3-T1-cut

题目链接

结论:\(m>0\) 时,答案一定是 \(\text{YES}\)。证明如下:

将每条边定向:从编号较小的点,连向编号较大的点。那么会得到一个 DAG。

按拓扑序确定每个点的颜色(\(0\)\(1\),分别代表工业城市/农业城市)。初始时,我们让入度为 \(0\) 的点,颜色全部为 \(0\)

考虑其它任意一点 \(u\) 的入边。假设有 \(x\) 条边来自 \(0\)\(y\) 条边来自 \(1\)

  • 如果 \(x\geq y\),令 \(u\) 的颜色等于 \(1\)
  • 如果 \(x<y\),令 \(u\) 的颜色等于 \(0\)

那么可以保证,任意一点 \(u\) 的所有入边里,两端颜色不同的边数 \(\geq\) 两端颜色相同的边数。当且仅当 \(x=y\) 时取等号。

通过考虑每个点的入边,我们不重不漏地考虑到了所有的边。因此,现在可以保证整张图里,两端颜色不同的边数 \(\geq\) 两端颜色相同的边数。并且只要存在一个点,它的 \(x\neq y\),整张图就能取大于号。

因为初始时,我们让入度为 \(0\) 的点全部染了相同的颜色,所以发现所有“第二层”的点,一定是:\(x>0,y=0\)。因此存在 \(x>y\) 的点,即整张图里 两端颜色不同的边数 \(>\) 两端颜色相同的边数。

参考代码:

#include <bits/stdc++.h>
int main() {
	int n, AMD;
	std :: cin >> n >> AMD;
	return std :: cout << (AMD ? "Yes" : "No") << std :: endl, 0;
}
posted @ 2020-08-30 16:05  duyiblue  阅读(710)  评论(5编辑  收藏  举报