NOIP 2014 Senior 2

来源:互联网 发布:阿里云cdn价格下调25 编辑:程序博客网 时间:2024/06/05 17:19

题目

思路1
很明显,题目给定的是一棵树,所以想到使用树形DP,所以首先,将无根树转换成有根树。可以记录一个结点的所有孙结点(子结点的子结点)的最大权值以及它们的权值和(当然要求余了)。对于某个结点和它的所有孙子结点来说,它们的最大联合权值就是孙子结点的最大权值乘以该结点的权值,联合权值和就是孙子结点与该结点的权值之积的和,再乘以2。对于求联合权值之和,可以用乘法分配律合并,变成求孙子结点权值之和与该结点的权值的积。
可以说这是树形DP的常规思路:从下往上更新信息。但这还不够,因为有些点对没有计算再内。不要忘了任意一棵子树的所有(直系)子结点两两也成联合点对。当然,这要求子结点至少有2个。对这些结点来说,求最大联合权值需要记录前两大的权值,求联合权值之和就稍微考了下效率:如果使用平方级时间复杂度的算法,那么将会超时。这里仍然使用乘法分配律:对于一棵子树的子结点两两形成的联合点对,其权值和为

Σni=1(Σcostcosti)(costi)
。这样,就能以线性时间复杂度求出和了。

参考代码

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;typedef int INT;inline INT readIn(){    bool minus = false;    INT a = 0;    char ch = getchar();    while (!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar();    if (ch == '-')    {        minus = true;        ch = getchar();    }    while (ch >= '0' && ch <= '9')    {        a *= 10;        a += ch;        a -= '0';        ch = getchar();    }    if (minus) a = -a;    return a;}const int maxn = 200005;const INT mod = 10007;const int root = 1;int n;std::vector<std::vector<int> > edges;INT weight[maxn];INT parent[maxn];INT grand[maxn];INT fMax[maxn]; //孙子结点的最大权值INT fSum[maxn]; //孙子结点权值之和INT fCSum[maxn]; //子结点权值之和INT fCMajor[maxn]; //子结点最大权值INT fCMinor[maxn]; //子结点次大权值INT maxAns;INT sumAns;void dfs(int node = root){    if (parent[node]) //更新父节点    {        fCSum[parent[node]] += weight[node];        fCSum[parent[node]] %= mod;        if (weight[node] > fCMinor[parent[node]])        {            if (weight[node] > fCMajor[parent[node]])            {                fCMinor[parent[node]] = fCMajor[parent[node]];                fCMajor[parent[node]] = weight[node];            }            else            {                fCMinor[parent[node]] = weight[node];            }        }    }    if (grand[node]) //更新爷节点 = =    {        fMax[grand[node]] = std::max(fMax[grand[node]], weight[node]);        fSum[grand[node]] += weight[node];        fSum[grand[node]] %= mod;    }    for (int i = 0; i < edges[node].size(); i++)    {        int& to = edges[node][i];        if (to == parent[node]) continue;        parent[to] = node;        if (parent[node]) grand[to] = parent[node];        dfs(to);    }    //更新在下面的    maxAns = std::max(maxAns, fMax[node] * weight[node]);    sumAns += fSum[node] % mod * weight[node] % mod * 2; //记住乘以2    sumAns %= mod;    //更新同级的    if (edges[node].size() - bool(parent[node]) >= 2) //需要至少有2个子结点    {        INT& sum = fCSum[node];        maxAns = std::max(maxAns, fCMajor[node] * fCMinor[node]);        for (int i = 0; i < edges[node].size(); i++)        {            int& to = edges[node][i];            if (to == parent[node]) continue;            sumAns += (sum + mod - weight[to]) % mod * weight[to] % mod; //这里不乘以2            sumAns %= mod;        }    }}void run(){    n = readIn();    edges.resize(n + 1);    for (int i = 2; i <= n; i++)    {        int from = readIn();        int to = readIn();        edges[from].push_back(to);        edges[to].push_back(from);    }    for (int i = 1; i <= n; i++)    {        weight[i] = readIn();    }    dfs();    sumAns %= mod;    cout << maxAns << " " << sumAns << endl;}int main(){    run();    return 0;}

有没有觉得很麻烦,随时要超时的感觉?事实上,如果题目给出极端数据:一条20万个结点的链,这个程序将直接爆栈。在Windows中最多能递归约3万层(即使只传一个参数也是这样,所以不把parent和grand当作参数传入的原因是因为我在测试),而在NOI Linux中也仅能递归约13万层(以上数据不保证准确)。还好数据中的层数都很少,侥幸全过。
这种算法麻烦的原因就在于:你能想到最后还要单独更新子结点,你就不能直接用这种方法更新所有结点吗?所以就有了好写好懂好过的思路2。

思路2
枚举每个结点,与结点i相邻的结点一定两两成联合点对,并且枚举每个结点时不会出现重复的联合点对,当然也不会遗漏。求题目中的两个问题的方法就是思路1中求解子结点的两个问题的方法。

参考代码

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;typedef int INT;inline INT readIn(){    bool minus = false;    INT a = 0;    char ch = getchar();    while (!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar();    if (ch == '-')    {        minus = true;        ch = getchar();    }    while (ch >= '0' && ch <= '9')    {        a *= 10;        a += ch;        a -= '0';        ch = getchar();    }    if (minus) a = -a;    return a;}const int maxn = 200005;const INT mod = 10007;int n;std::vector<std::vector<int> > edges;INT weight[maxn];INT maxAns;INT sumAns;void run(){    n = readIn();    edges.resize(n + 1);    for (int i = 2; i <= n; i++)    {        int from = readIn();        int to = readIn();        edges[from].push_back(to);        edges[to].push_back(from);    }    for (int i = 1; i <= n; i++)    {        weight[i] = readIn();    }    for (int i = 1; i <= n; i++)    {        if (edges[i].size() < 2) continue;        INT iMajor = 0;        INT iMinor = 0;        INT iSum = 0;        for (int j = 0; j < edges[i].size(); j++)        {            if (weight[edges[i][j]] > iMinor)            {                if (weight[edges[i][j]] > iMajor)                {                    iMinor = iMajor;                    iMajor = weight[edges[i][j]];                }                else                {                    iMinor = weight[edges[i][j]];                }            }            iSum += weight[edges[i][j]];            iSum %= mod;        }        maxAns = std::max(maxAns, iMajor * iMinor);        for (int j = 0; j < edges[i].size(); j++)        {            sumAns += (iSum + mod - weight[edges[i][j]]) % mod * weight[edges[i][j]] % mod;            sumAns %= mod;        }    }    cout << maxAns << " " << sumAns << endl;}int main(){    run();    return 0;}

总的来说,这道题看了思路2后。。。真的一点也不复杂,就考考你知不知道树。所以说啊。。。还是太弱,有些时候经常在危险的时间上徘徊,只要稍加分析,就能得到更优的算法。
另外,思路一当初我只想到了第一步,第二步还是我在测试时发现的,看来写完后检查真的很重要啊!

原创粉丝点击