BZOJ1063道路设计

9 篇文章 0 订阅

提示:
1. 这是一棵树
2. 还记得树链剖分关于轻边logn的证明吗 , 对这个题有没有什么启示呢?

思路留给大家思考 ,
代码后我将为两种做法作出详细解释:

No.1 :

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <deque>
#include <queue>
#include <list>
#include <algorithm>

using namespace std;
typedef long long ll;
const int maxn = 110000;
__inline int re() { int n; scanf("%d" , &n); return n; }

ll  n , m , modu;
vector<int> g[maxn];
ll  d[maxn][15][3];
int c[maxn][15][3];

bool dp(int u , int m , int fa)
{
    d[u][m][0] = 1;
    c[u][m][0] = 1;
    d[u][m][1] = d[u][m][2] = 0;

    if(g[u].size()==1 && g[u][0]==fa) return true;

    for(int i=0;i<g[u].size();i++)
    {
        int v = g[u][i];
        if(v == fa) continue;
        dp(v , m , u);

        ll f = (d[v][m-1][0] + d[v][m-1][1] + d[v][m-1][2])%modu;
        ll r = (d[v][m][0]+d[v][m][1])%modu;

        bool p = (c[v][m-1][0] | c[v][m-1][1] | c[v][m-1][2]);
        bool k = (c[v][m][0] | c[v][m][1]);


        d[u][m][2] = (d[u][m][2]*f + d[u][m][1]*r)%modu;
        c[u][m][2] = (c[u][m][2]&p) | (c[u][m][1]&k);

        d[u][m][1] = (d[u][m][1]*f + d[u][m][0]*r)%modu;
        c[u][m][1] = (c[u][m][1]&p) | (c[u][m][0]&k);

        d[u][m][0] = (d[u][m][0] * f)%modu;    c[u][m][0] &= p;
    }
    return c[u][m][0] || c[u][m][1] || c[u][m][2];
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    #endif
    n = re(); m = re(); scanf("%lld" , &modu);

    if(m!=n-1) { puts("-1\n-1"); return 0; }
    while(m--)
    {
        int a = re() , b = re();
        g[a].push_back(b);
        g[b].push_back(a);
    }


    for(int i=1;;i++) if(dp(1 , i , -1)) { printf("%d\n%lld\n" , i-1 , (d[1][i][0]+d[1][i][1]+d[1][i][2])%modu); break; }

    return 0;
}

No.2 :

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <deque>
#include <queue>
#include <list>
#include <algorithm>

using namespace std;
typedef long long ll;
const int maxn = 110000;
__inline int re() { int n; scanf("%d" , &n); return n; }

ll  n , m , modu;
vector<int> g[maxn];

int ch[maxn][2];
ll d[15][maxn][3];
int c[15][maxn][3];
int v[15][maxn][3];

void dfs(int u , int F)
{
    int last = 0;
    for(int i=0;i<g[u].size();i++)
    {
        int v = g[u][i];
        if(v==F) continue;
        ch[u][0] = v;
        ch[v][1] = last;
        last = v;
        dfs(v , u);
    }
}


void tangent(ll& a , ll b) { a = (a+b)%modu; }

ll dp(int m , int u , int l)
{
    ll& res = d[m][u][l];
    int& b = c[m][u][l];

    if(v[m][u][l]) return res;
    v[m][u][l] = 1;

    if(!u) { b = (m>=0 && !l); return res = (m>=0 && !l); }

    int b1 = 0 , b2 = 0;
    if(l) 
    {
        ll s = 0;
        for(int i=0;i<2;i++) tangent(s , dp(m , ch[u][0] , i)) , b1 |= c[m][ch[u][0]][i];
        res = (s*dp(m , ch[u][1] , l-1))%modu; b1 &= c[m][ch[u][1]][l-1];
    }
    if(m)
    {
        ll s = 0;
        for(int i=0;i<3;i++) tangent(s , dp(m-1 , ch[u][0] , i)) , b2 |= c[m-1][ch[u][0]][i];
        s = (s * dp(m , ch[u][1] , l ))%modu , b2 &= c[m][ch[u][1]][l];
        res = (res + s)%modu;
    }

    b = b1 | b2;
    return res;
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    #endif
    n = re(); m = re(); scanf("%lld" , &modu);

    if(m!=n-1) { puts("-1\n-1"); return 0; }
    while(m--)
    {
        int a = re() , b = re();
        g[a].push_back(b);
        g[b].push_back(a);
    }
    dfs(1 , -1);

    for(int i=1;;i++) if(dp(i , 1 , 0) || c[i][1][0]) { printf("%d\n%lld\n" , i-1 , d[i][1][0]); break; }
    return 0;
}

这个题是树形DP ,根据树链剖分的证明 , 分成的链数不得多于logn条 , 所以我们可以枚举第一问所求的答案 , 如果此时是可行的那么一定是最小的。 还有一个往往被忽视的原因是 , 如果不枚举链数 , 那么其实无法进行DP , 因为总体最优的状态并不要求局部最优。

第一种做法是网上比较流行的 , 在此可以将状态表示为:

f[i][j][k] 编号为 i 的结点 , 在最大链深度(也就是此时的答案)不超过j , 向下连出 k 条链时的方案数。 (但注意两份代码下标的顺序不同。 )

那么转移根据代表的意义是不难写出的:

我们设

Tu=0k2f[u][j1][k]Gu=0k1f[u][j][k]
那么就有:

f[i][j][0]=usonsTu

f[i][j][1]=usonsGu×vsons,vuTv

f[i][j][2]=usonsvsons,vuGu×Gv×ksons,ku,kvTv

这个式子是巨难求 , 所以要优化求法。 可以考虑在每个结点的儿子间进行背包DP来优化到线性。也就是用目前的 f[i][j][] 去互相影响。 这其实不难理解。

下面谈谈这个题的另一种实现方法 , 左儿子右兄弟表示一棵树:
这里就不多讲这种表现形式了 , 我们直接看状态:

f[i][j][k] 对于结点 i , 此时其链深度(就是此时以i为根的答案)为 j i 这一代结点还能连 k 条链的方案数。

     解释一下什么叫做一代结点 , 因为左儿子右兄弟的表示法并不能体现原来图的父子关系 , 我们称在原图中有同样父亲的结点为一代结点。

     每一次决策是讨论结点 i 是否与自己的父亲连边 , 然后决定自己的儿子们要跟自己连接几条边。 这有点像父亲结点分配任务然后儿子结点们逐步实现任务。

对于每次决策 , 连边与不连边的转移方程如下:

G1=0i1f[leftSon][j][i]f[rightSon][j][k1]

G2=0i2f[leftSon][j1][i]f[rightSon][j][k]

那么此时 , 答案为 G1+G2

不难发现这样做简化了很多 , 左儿子右兄弟是一种表示方法 , 这种表示方法可以简化DP的转移。 这是由于这种表示方法充分的应用了DP的状态。

最后值得一提的是 , 这个题在判断可行的时候并不能直接看此时的答案是否为0(可能本身余数就是0) , 因而我用了一个数组表示此时的可行性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值