bzoj5315: [Jsoi2018]防御网络 【树形dp】

本文介绍了一种解决特殊图结构问题的方法,针对一棵树加上若干不相交环的情况,通过树形DP技术和环上的动态规划来求解最小路径覆盖问题,详细解析了算法实现,并给出了具体的时间复杂度。

传送门

解题思路:

注意到该图是一棵树上多了几个不相交的环。
考虑每条边的贡献。
对于一条树边(桥),只要两边有点那就要选。
对于一个环,其贡献的长度就是环长减去最长的空段,可以枚举最长空隙和起点dp,详见代码。

时间复杂度 O(n3) O ( n 3 )

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=205,mod=1e9+7;
int n,m,dep[N],size[N],fa[N],mark[N];
vector<int>g[N];
ll ans,bin[N],a[N],dp[N][2],sum[N][2];
ll Pow(ll x,ll y)
{
    ll res=1;
    for(;y;y>>=1,x=x*x%mod)
        if(y&1)res=res*x%mod;
    return res;
}
void calc(int st,int len)
{
    for(int i=st-1;i<=m;i++)dp[i][0]=dp[i][1]=sum[i][0]=sum[i][1]=0;
    dp[st][0]=sum[st][0]=a[st];
    for(int i=st+1;i<=m;i++)
    {
        if(i-len>=st)dp[i][1]=(dp[i][1]+a[i]*(dp[i-len][0]+dp[i-len][1]))%mod;
        int l=max(st,i-len+1)-1;
        dp[i][0]=(dp[i][0]+a[i]*(sum[i-1][0]+mod-sum[l][0]))%mod,dp[i][1]=(dp[i][1]+a[i]*(sum[i-1][1]+mod-sum[l][1]))%mod;
        sum[i][0]=(sum[i-1][0]+dp[i][0])%mod;
        sum[i][1]=(sum[i-1][1]+dp[i][1])%mod;
        ans=(ans+min(i-st,m-len)*dp[i][1])%mod;
    }
}
void dfs(int x)
{
    size[x]=1;
    for(int i=0;i<g[x].size();i++)
    {
        int y=g[x][i];if(y==fa[x])continue;
        if(dep[y])
        {
            if(dep[y]>dep[x])
            {
                m=0;int last=0;
                for(int k=y;k!=x;last=k,k=fa[k])
                    a[++m]=bin[size[k]-size[last]],mark[k]=1;
                a[++m]=bin[n-size[last]];
                for(int len=1;len<m;len++)
                    for(int st=1;st<=m-len;st++)
                        calc(st,len);
            }
            continue;
        }
        fa[y]=x,dep[y]=dep[x]+1;
        dfs(y),size[x]+=size[y];
    }
}
int main()
{
    //freopen("defense.in","r",stdin);
    //freopen("defense.out","w",stdout);
    n=getint(),m=getint();
    bin[0]=1;
    for(int i=1;i<=n;i++)bin[i]=bin[i-1]*2%mod;
    for(int i=1;i<=n;i++)bin[i]--;
    while(m--)
    {
        int x=getint(),y=getint();
        g[x].push_back(y),g[y].push_back(x);
    }
    dep[1]=1,dfs(1);
    for(int i=2;i<=n;i++)if(!mark[i])ans=(ans+bin[size[i]]*bin[n-size[i]])%mod;
    ans=ans*Pow(bin[n]+1,mod-2)%mod;
    cout<<ans<<'\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值