正睿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\)。则答案就是:
其中\({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\))。得到如下递推式:
解释一下这个式子:
显然,在操作到\(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)\)。
定义:
则:
每算出来一个\(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;
}