zoukankan      html  css  js  c++  java
  • 正睿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;
    }
    
  • 相关阅读:
    USACO2008 Cow Cars /// oj23323
    USACO2008 Roads Around The Farm /// queue oj23321
    USACO2007 捕牛记 /// queue+桶 oj1503
    哈理工多校算法赛一
    USACO2012 Haybale stacking /// 区间表示法 oj21556
    USACO2012 Broken necklace /// DP oj10103
    USACO2004 cube stacking /// 带权并查集 oj1302
    ACM-ICPC 2018 南京赛区网络预赛 J sum (找一个数拆成两个无平方因子的组合数)
    分层图 (可以选择K条路的权为0,求最短路)
    POJ 3537 Crosses and Crosses(sg博弈)
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13585506.html
Copyright © 2011-2022 走看看