【BZOJ1063】【NOI2008】道路设计(动态规划)

57 篇文章 0 订阅

题面

BZOJ

题解

发现每个点最多只能被修一次等价于每个点最多只能和两条铁路相邻
考虑一个 dp d p
f[i][0/1/2] f [ i ] [ 0 / 1 / 2 ] 表示以 i i 为根,当前点与他的儿子已经有0/1/2条铁路相邻的方案数
转移也很简单,考虑每个儿子,枚举是修还是不修就行了
这样的复杂度是 O(n) O ( n )

这样的前提是不需要计算答案的方案数,我们可以很容易想出来
现在考虑如何计算方案数。
考虑一下答案的范围,如果我们把这棵树进行树链剖分
重链视为修铁路,那么任意一个点跳轻边的次数不会超过 log l o g
所以,答案一定不会超过 log l o g
这样子在做的时候把答案限制也加上就好了
f[i][j][k] f [ i ] [ j ] [ k ] 表示以 i i 为根的子树,答案为j
当前点与 k k 条铁路相邻的方案数,其中k{0,1,2}
转移和前面没有太多的区别,只需要注意一下答案那一位的变化就行了
时间复杂度 O(nlogn) O ( n l o g n )
注意一下这道题目要怎么取模

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 100100
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,m,MOD;
int f[MAX][20][3];
void add(int &x,int y){x+=y;if(x>MOD)x-=MOD;}
int mod(ll x){if(x!=0&&x%MOD==0)return MOD;return x%MOD;}
void dfs(int u,int ff)
{
    for(int i=0;i<20;++i)f[u][i][0]=1;--n;
    for(int i=h[u];i;i=e[i].next)
    {
        int v=e[i].v;if(v==ff)continue;
        dfs(v,u);
        for(int ans=0;ans<19;++ans)
        {
            int f0=0,f1=0;
            if(ans)f0=mod(1ll*f[v][ans-1][0]+f[v][ans-1][1]+f[v][ans-1][2]);
            f1=mod(1ll*f[v][ans][0]+f[v][ans][1]);
            f[u][ans][2]=mod(1ll*f[u][ans][2]*f0+1ll*f[u][ans][1]*f1);
            f[u][ans][1]=mod(1ll*f[u][ans][1]*f0+1ll*f[u][ans][0]*f1);
            f[u][ans][0]=mod(1ll*f[u][ans][0]*f0);
        }
    }
}
int main()
{
    n=read();m=read();MOD=read();
    for(int i=1;i<=m;++i)
    {
        int u=read(),v=read();
        Add(u,v);Add(v,u);
    }
    dfs(1,0);
    if(n){puts("-1");puts("-1");return 0;}
    int ans=1e9,tot=0;
    for(int i=0;i<20;++i)
        if(f[1][i][0]||f[1][i][1]||f[1][i][2]){ans=i;break;}
    tot=mod(1ll*f[1][ans][0]+f[1][ans][1]+f[1][ans][2])%MOD;
    printf("%d\n%d\n",ans,tot);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值