NOIP 2014 Senior 2
来源:互联网 发布:阿里云cdn价格下调25 编辑:程序博客网 时间:2024/06/05 17:19
思路1
很明显,题目给定的是一棵树,所以想到使用树形DP,所以首先,将无根树转换成有根树。可以记录一个结点的所有孙结点(子结点的子结点)的最大权值以及它们的权值和(当然要求余了)。对于某个结点和它的所有孙子结点来说,它们的最大联合权值就是孙子结点的最大权值乘以该结点的权值,联合权值和就是孙子结点与该结点的权值之积的和,再乘以2。对于求联合权值之和,可以用乘法分配律合并,变成求孙子结点权值之和与该结点的权值的积。
可以说这是树形DP的常规思路:从下往上更新信息。但这还不够,因为有些点对没有计算再内。不要忘了任意一棵子树的所有(直系)子结点两两也成联合点对。当然,这要求子结点至少有2个。对这些结点来说,求最大联合权值需要记录前两大的权值,求联合权值之和就稍微考了下效率:如果使用平方级时间复杂度的算法,那么将会超时。这里仍然使用乘法分配律:对于一棵子树的子结点两两形成的联合点对,其权值和为
参考代码
#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后。。。真的一点也不复杂,就考考你知不知道树。所以说啊。。。还是太弱,有些时候经常在危险的时间上徘徊,只要稍加分析,就能得到更优的算法。
另外,思路一当初我只想到了第一步,第二步还是我在测试时发现的,看来写完后检查真的很重要啊!
- NOIP 2014 Senior 2
- NOIP 2014 Senior 3
- NOIP 2014 Senior 5
- NOIP 2014 Senior 6
- NOIP 2011 Senior 2
- NOIP 2012 Senior 2
- NOIP 2015 Senior 2
- NOIP 2013 Senior 2
- NOIP 2016 Senior 2
- NOIP 2009 Senior 1
- NOIP 2009 Senior 4
- NOIP 2009 Senior 3
- NOIP 2011 Senior 3
- NOIP 2011 Senior 4
- NOIP 2011 Senior 5
- NOIP 2011 Senior 6
- NOIP 2012 Senior 5
- NOIP 2012 Senior 3
- pandas1
- 线段树基础合集
- 大话数据结构(八)Java程序——双向链表的实现 线性链表——双向链表
- 323_棋盘问题
- 带有参数的main函数
- NOIP 2014 Senior 2
- jQuery——使用插件
- python 简单的抓网页
- busybox编译的博客
- python flask安装
- MATLAB中自带的核密度估计函数
- java页面后台数据交互(1)
- kNN与kMeans聚类算法的区别
- 1792_迷宫