zoukankan      html  css  js  c++  java
  • Rikka with Intersections of Paths(Gym102012G)(树上差分)

    题意:

    题目给出一棵树,再给出 (m) 条树上路径,问取 (k) 条树上路径使这些路径至少有一个公共点的方法数量。


    想法:

    • 我们考虑怎么取就可以让路径有公共点,很显然只要有一个点相同即可,那么我们已知所有可取路径,也就可以把每条路径经过的点求出,也就可以把每个点被经过几次求出即可。
    • 怎么求每个点被经过几次,考虑复杂度,暴力显然不行。想到树上点差分,这样可以在 (O(n)) 复杂度内求出每个点的被经过几次。设每个点被经过 (ans[i]) 次。
    • 我们很容易想到对于设某个点为公共点,那么取法就是 (C^{ k}_{ans[i]}),但由于某些路径不止一个公共点,显然会有重边。
    • 考虑一个性质:如果树上两条路径有公共点,那么其中一定有一个公共点是某一条路径的两个端点的 (lca)
    • 看到上述性质,那么可以对于某个点,我们只求某个点作为公共点且有公共点是某条路径的 (lca) ,这样子就可以保证没有重复方案。
      这样子答案就是
    $sum^{n}_{i=1} (C^{ k}_{ans[i]}-C^{ k}_{ans[i]-sumlca[i]})$

    代码:

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N=1000000+200;
    const int MOD=1000000007;
    int n,m,k;
    ll fact[N+5];	//阶乘 
    ll a[N+10];	// 乘法逆元 
    //LL inv[maxn+10];	//快速幂 
     
    ll pow(ll x)
    {
    	ll n = MOD-2;
        ll res=1;
    	while(n>0)
    	{
    	   if(n%2==1)	
    	   	 res=res*x%MOD;
    	   x=x*x%MOD;
    	   n>>=1;
    	}
    	return res;	
    }
     
    void init(){
        a[0] = a[1] = 1;
        fact[0] = fact[1] = 1;
    //  inv[1] = 1;
        for(int i = 2; i <= 1000005; i++)
        {
            fact[i] = fact[i-1] * i % MOD;
    		a[i] = a[i-1] * pow(i) % MOD;	//m!的MOD次方 = (m-1)!的MOD次方 * m的MOD次方 
    //      inv[i] = (MOD - MOD/i)*inv[MOD%i]%MOD;
    //      a[i] = a[i-1] * inv[i] % MOD;	
        }
    }
     ll C(int n, int m){	//乘法逆元 
    	if(n<0||m<0||n<m)return 0;
        return fact[n]*a[n-m]%MOD*a[m]%MOD;
    }
    struct LCA
    {
    	int head[N<<1],Next[N<<1],edge[N<<1],ver[N<<1],tot;
    	int deep[N],fa[N][22],lg[N],date[N],ans[N];
        int sumlca[N];
    	inline void init()
    	{
    	      memset(head,0,sizeof(head));
    	      memset(deep,0,sizeof(deep));
                  memset(sumlca,0,sizeof(sumlca));
                  memset(ans,0,sizeof(ans));
                  memset(date,0,sizeof(date));
                  memset(deep,0,sizeof(deep));
                  memset(fa,0,sizeof(fa));
    	      tot=0;
    	}
    	inline void add_edge(int a,int b,int c)
    	{
    		edge[++tot]=b;
    		ver[tot]=a;
    		Next[tot]=head[a];
    		head[a]=tot;
    	}
    	inline void dfs(int x,int y)
    	{
    		deep[x]=deep[y]+1;//深度是父亲节点+1
    		fa[x][0]=y;//2^0=1,也就是父亲节点
    		for(int i=1; (1<<i)<=deep[x]; i++) //2^i<=deep[x]也就是别跳出根节点了
    			fa[x][i]=fa[fa[x][i-1]][i-1];
    		for(int i=head[x]; i; i=Next[i]) //遍历所有的出边
    			if (edge[i]!=y)//避免回到父亲节点
    				dfs(edge[i],x);//自己的儿子节点, 自己是父亲节点
    		return ;
    	}
    	inline int Lca(int x,int y)//Lca过程
    	{
    		if (deep[x]<deep[y])//x节点默认深度深一些,在下面
    			swap(x,y);//交换
    		while(deep[x]>deep[y])//还没有同一高度
    			x=fa[x][lg[deep[x]-deep[y]]-1];//往上面跳跃,deep[x]-deep[y]是高度差.-1是为了防止deep[x]<deep[y]
    		if(x==y)//意外发现,y就是(x,y)的Lca
    			return x;
    		for(int i=lg[deep[x]]; i>=0; i--)
    			if (fa[x][i]!=fa[y][i])//没有跳到Lca
    			{
    				x=fa[x][i];//旋转跳跃
    				y=fa[y][i];//我闭着眼
    			}
    		return fa[x][0];//父亲节点就是Lca,因为本身是离着Lca节点最近的节点,也就是Lca的儿子节点.
    	}
        //点差分
        inline void query(int x)
        {
            int sum=0;
            for(int i=head[x];i;i=Next[i])
            {
                int j=edge[i];
                if(deep[x]>deep[j])continue;
                query(j);
                sum+=ans[j];
            }
            sum+=date[x];
            ans[x]=sum;
        }
    	inline void update(int x,int y)//修改操作,x,y节点构成的路径统一+1
    	{
                  int now=Lca(x,y);
    	      date[x]++;
    	      date[y]++;
    	      date[now]--;
                  if(now!=1)date[fa[now][0]]--;
                  sumlca[now]++;
            }
    }G;
    int main()
    {
        int T;
        init();
        cin>>T;
        while(T--){
            scanf("%d%d%d",&n,&m,&k);
            G.init();
            for(int i=1; i<n; i++)
            {
                int a,b;
                scanf("%d%d",&a,&b);
                G.add_edge(a,b,0);//加边
                G.add_edge(b,a,0);//无向图
            }
            for(int i=1; i<=n; i++)
                G.lg[i]=G.lg[i-1]+(1<<G.lg[i-1]==i);//处理log数组的关系
            G.dfs(1,0);
            for(int i=1; i<=m; i++)
            {
                int a,b;
                scanf("%d%d",&a,&b);
                G.update(a,b);//附加边
            }
            G.query(1);
            //for(int i=1;i<=n;i++)cout<<G.ans[i]<<" "<<G.sumlca[i]<<endl;
            ll fans=0;
            for(int i=1;i<=n;i++){
                //printf("xx%d
    ",G.ans[i]);
                fans=(((fans+(C(G.ans[i],k)-C(G.ans[i]-G.sumlca[i],k)+MOD)%MOD+MOD)%MOD)%MOD+MOD)%MOD;
            }
            cout<<fans<<endl;
        }
    	return 0;
    }
    
    越自律,越自由
  • 相关阅读:
    openmp
    opencv 读写矩阵
    string to const char*
    c++ string to number
    HDU 1520 Anniversary Party
    ZOJ 1003 Crashing Balloon
    HDU 4171 Paper Route
    ZOJ 2067 White Rectangles
    TOJ 1690 Cow Sorting (置换群)
    TOJ 2814 Light Bulb
  • 原文地址:https://www.cnblogs.com/ha-chuochuo/p/14383055.html
Copyright © 2011-2022 走看看