正睿2020提高组十连测 选做

更新中...

day1-T3-random

题目链接

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

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

看本题。首先,期望是吓唬你的,因为期望乘上阶乘,其实就是求和。于是我们要求【所有排列下【操作后所有点的权值和】的和】。因此暴力做法就是枚举一个排列,模拟操作,时间复杂度(O(n!cdot ext{poly}(n)))。值得一提的是,操作时,枚举子树里所有的点,可以通过( ext{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 ext{ is an ancestor of }v\d= ext{dep}[v]- ext{dep}[u]+1}}a_u imes f_d imes {nchoose d} imes(n-d)! ]

其中({nchoose d} imes (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!)的。因为(dleq ext{maxdep}=O(sqrt{n})),所以暴力预处理的复杂度(O((sqrt{n})!cdot ext{poly}(sqrt{n})))。也可以改成状压DP。都能比(n!)纯暴力多拿一些分。


递推求出系数

考虑递推求(f_{1dots ext{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) ((0leq i<d))。得到如下递推式:

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

解释一下这个式子:

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

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

操作到(1)时,会令(x_2dots 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)!) 种操作顺序)。并且前后两部分之间互不影响,各自确定了顺序后可以穿插在一起(({ichoose j}) 种穿插情况)。

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

定义:

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

则:

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

每算出来一个(f_d)就更新一下它对(g_{ddots 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) 时,答案一定是 ( ext{YES})。证明如下:

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

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

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

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

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

通过考虑每个点的入边,我们不重不漏地考虑到了所有的边。因此,现在可以保证整张图里,两端颜色不同的边数 (geq) 两端颜色相同的边数。并且只要存在一个点,它的 (x eq 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;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/13585506.html