Codeforces Round #537 (Div. 2) E. Tree(DP+树状数组统计+LCA)*

题目链接:http://codeforces.com/contest/1111/problem/E

题目大意:

给定一棵树,和若干个询问,
k,m,r和k个数,问把这k个点分成至多m组
且每组中的点集没有父节点与子节点的关系,
其方案数有多少种。

题目分析: 

这道题我是看了别人的思路才懂的,
果然还是自己太弱了呀。。。
先简化问题,如果根是固定的如何考虑,
那么观察到m的数据范围去考虑DP计数,
dp(i,j)代表i个数分成j组有多少种方案,
转移方程:dp(i,j)=dp(i-1,j-1)+dp(i-1,j)*(j-f[i]),
其中f[i]代表的是i节点对于1根有多少个父节点,
因为这f[i]个父节点肯定安排在不同的集合中,
那么根据DFS序性质,我们可以利用树状数组统计父节点的个数,
但只能对于特定的根,如果根变换呢?
考虑路径差,特定的点其到特定的根的路径上的权重,
即树上任意两点之间路径的权值和,我们利用LCA来辅助转换。
所以就是对于每个查询,我们不难统计出每个点的f[i],这样状态转移方程的
条件就有了。
我的想法是直接按DFS再搜一遍,遇到其k个点之一就刷新状态数组,
但是我看的这位博主另有想法,它直接把f数组排序然后对这个数组进行DP了,
DP的性质是无后效性,而子节点不会比父节点的f值来的大,所以对于
任意一个互斥对都满足了更新顺序。

最后一些细节就是滚动数组,还有DP数组开int再类型转换取模优化下,
我开long long被T了。

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define ll long long
#define mst(a,b) memset((a),(b),sizeof(a))
const int mod=1e9+7;
const int maxn=1e5+10;
const int ub=1e6;
const double inf=1e-4;
ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
/*
题目大意:
给定一棵树,和若干个询问,
k,m,r和k个数,问把这k个点分成至多m组
且每组中的点集没有父节点与子节点的关系,
其方案数有多少种。

题目分析:
这道题我是看了别人的思路才懂的,
果然还是自己太弱了呀。。。
先简化问题,如果根是固定的如何考虑,
那么观察到m的数据范围去考虑DP计数,
dp(i,j)代表i个数分成j组有多少种方案,
转移方程:dp(i,j)=dp(i-1,j-1)+dp(i-1,j)*(j-f[i]),
其中f[i]代表的是i节点对于1根有多少个父节点,
因为这f[i]个父节点肯定安排在不同的集合中,
那么根据DFS序性质,我们可以利用树状数组统计父节点的个数,
但只能对于特定的根,如果根变换呢?
考虑路径差,特定的点其到特定的根的路径上的权重,
即树上任意两点之间路径的权值和,我们利用LCA来辅助转换。
所以就是对于每个查询,我们不难统计出每个点的f[i],这样状态转移方程的
条件就有了。
我的想法是直接按DFS再搜一遍,遇到其k个点之一就刷新状态数组,
但是我看的这位博主另有想法,它直接把f数组排序然后对这个数组进行DP了,
DP的性质是无后效性,而子节点不会比父节点的f值来的大,所以对于
任意一个互斥对都满足了更新顺序。

最后一些细节就是滚动数组,还有DP数组开int再类型转换取模优化下,
我开long long被T了。
*/
int n,q,x,y;
///前式链向星
vector<int> g[maxn];
int pl[maxn],pr[maxn],bit[maxn],tot;///深度
inline void refresh(int x,int d){
    for(;x<maxn;bit[x]+=d,x+=x&-x);
}
inline int sum(int x){
    int ret=0;for(;x;ret+=bit[x],x-=x&-x);return ret;
}
int st[maxn][20],dep[maxn];
inline void dfs(int u,int pre){
    pl[u]=++tot;
    dep[u]=dep[pre]+1;
    st[u][0]=pre;rep(i,1,20) st[u][i]=st[st[u][i-1]][i-1];
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==pre) continue;
        dfs(v,u);
    }
    pr[u]=tot;
}
inline int lca(int u,int v){
    if(u==v) return u;
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;i>=0;i--) if(dep[st[u][i]]>=dep[v])
        u=st[u][i];
    if(u==v) return u;
    for(int i=19;i>=0;i--) if(st[u][i]!=st[v][i])
        u=st[u][i],v=st[v][i];
    return st[u][0];
}
int dp[maxn];
int k,m,r,a[maxn],f[maxn],vis[maxn];
int main(){
    scanf("%d%d",&n,&q);
    rep(i,0,n-1){
        scanf("%d%d",&x,&y);
        g[x].push_back(y);
        g[y].push_back(x);
    }
    tot=0;dfs(1,0);
    rep(i,0,q){
        scanf("%d%d%d",&k,&m,&r);
        rep(i,1,k+1){
            scanf("%d",&a[i]);
            vis[a[i]]=1;
            refresh(pl[a[i]],1);
            refresh(pr[a[i]]+1,-1);
        }
        int tmp=sum(pl[r]);
        rep(i,1,k+1){
            int LCA=lca(r,a[i]);
            f[i]=sum(pl[a[i]])+tmp-2*sum(pl[LCA])+vis[LCA]-1;///减去自己
        }
        rep(i,1,k+1){
            vis[a[i]]=0;
            refresh(pl[a[i]],-1);
            refresh(pr[a[i]]+1,1);
        }
        if(f[k]>=m) {puts("0");continue;}
        sort(f+1,f+1+k);
        mst(dp,0),dp[0]=1;
        rep(i,1,k+1) for(int j=min(i,m);j>=0;j--){
            if(j<=f[i]) dp[j]=0;
            else dp[j]=((ll)dp[j-1]+(ll)dp[j]*(j-f[i])%mod)%mod;
        }
        ll ans=0;rep(i,1,m+1) (ans+=dp[i])%=mod;
        printf("%lld\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值