NOIP模拟(10.22)T3 树

2023-11-02 11:32
文章标签 模拟 t3 noip 10.22

本文主要是介绍NOIP模拟(10.22)T3 树,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


题目背景:

10.22 NOIP模拟作业T3

分析:状压DP

首先证明一些东西。

1、显然,编号越小的点深度越深越好

证明:显然,如果一个点的编号很小,它的深度越高,意味着它影响的叶子节点越多,那么显然就会很不优。

2、对于以一个点为根的子树,它的编号一定是连续的。

证明:假设对于一个点x它子树的sizey,但是编号为1 ~ y - 1,和y + 1,那么必然有另一个地方的编号为y,因为我们已经说过,如果,将编号y放在x当中,因为y为整个子树当中的最大编号,那么它不会影响x子树中叶子节点的路径最小值,但是将y放在其他地方因为它是目前最小的编号,所以一定会影响一些叶子节点的取值,那么显然是不优秀的,所以对于一个子树它的编号一定是连续的。

 

说到这里想必思路就比较清晰了,我们的编号策略就是,从小到大编号,从叶子节点标起,一个非叶结点被标记,当且仅当,它的整个子树均被标记,那么现在影响答案的因素就只有一个了,即叶子节点的标号顺序,我们可以用f[i]状压表示,当前标记了哪些叶子节点,这些叶子节点的最优答案是多少。因为对于每一个状态我们都需要dfs判定,当前被确定了哪些编号,那么复杂度是O(2 ^ 叶子节点数 * 树的大小)这样显然复杂度偏高,考虑该怎么优化一下,我们发现,对于一个非叶非根节点,如果它只有一个儿子,那么它是可以和它的儿子放在一起的,儿子一旦标记,它就需要标记,那么我们可以在一开始的时候,把这种情况的节点,也就是一条链缩到一起,这样树的大小就很小了,(最大的情况为满二叉树),可以搞定。

 

具体实现:

g[i]表示,在状态i中存在的叶子节点全部标记之后,这i个权值之积最大为g[i],枚举到i状态时,dfs找寻当前能够被确定的已经标记的节点有sum个,那么下一个被标记的叶子节点的值一定为sum + 1(上面已经证明过了),所以我们可以将g[i] à g[i | (1<< j)] (i & (1 << j) == 0)然后最后的g[i]就是答案啦。

注意:因为答案过大需要取模,所以,我们可以存储两个值g[i]f[i]g[i]为答案本身的值,用double存储,来用于比较,f[i]int即取好模的答案。

 

Source

/*created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cctype>
#include <set>
#include <map>
#include <vector>
#include <queue>const int MAXX = 20;
const int MAXN = 100000 + 10;
const int mod = 1000000000 + 7;int n, x, y, sum;
std::vector<int> leaves;
std::vector<int> ori_edge[MAXN], edge[MAXN];
int f[1 << MAXX], dep[MAXN], num[MAXN], size[MAXN];
double g[1 << MAXX];
bool use[MAXN];inline void add_ori_edge(int x, int y) {ori_edge[x].push_back(y), ori_edge[y].push_back(x);
}inline void read_in() {scanf("%d", &n);for (int i = 1; i < n; ++i) scanf("%d%d", &x, &y), add_ori_edge(x, y);
}inline void get_point(int cur, int fa) {/*标记有意义的节点*/dep[cur] = dep[fa] + 1, size[cur] = 1;int cnt = 0;for (int p = 0; p < ori_edge[cur].size(); ++p) {int v = ori_edge[cur][p];if (v != fa) get_point(v, cur), size[cur] += size[v], ++cnt;}if (cnt != 1) use[cur] = true;if (cnt == 0) leaves.push_back(cur);
}inline void build_new_tree(int cur, int fa, int last) {/*建立新的树*/ if (use[cur] && last) edge[last].push_back(cur);if (use[cur]) last = cur;for (int p = 0; p < ori_edge[cur].size(); ++p) {int v = ori_edge[cur][p];if (v != fa) build_new_tree(v, cur, last);}
}inline void dfs(int cur, int fa) {/*查找多少个点的标号被确定*/ if (edge[cur].size() == 0) return (void)(sum += num[cur]);num[cur] = 0;for (int p = 0; p < edge[cur].size(); ++p) {int v = edge[cur][p];if (v != fa) {dfs(v, cur);if (num[v] == size[v]) {num[cur] += num[v] + dep[v] - dep[cur] - 1;sum += dep[v] - dep[cur] - 1;}}}if (num[cur] == size[cur] - 1) num[cur]++, sum++;
}inline void solve() {use[1] = true, get_point(1, 0), build_new_tree(1, 0, 0);for (int i = 0; i < leaves.size(); ++i) f[1 << i] = g[1 << i] = 1; for (int i = 0, s = (1 << int(leaves.size())); i < s; ++i) {for (int j = 0; j < leaves.size(); ++j) num[leaves[j]] = (i & (1 << j)) ? 1 : 0;sum = 0, dfs(1, 0);double max = g[i] * (double)(sum + 1);int ans = (long long)f[i] * (long long)(sum + 1) % mod;for (int j = 0; j < leaves.size(); ++j)if ((i & (1 << j)) == 0 && g[i | (1 << j)] < max)g[i | (1 << j)] = max, f[i | (1 << j)] = ans; }std::cout << f[(1 << int(leaves.size())) - 1];
}int main() {read_in();solve();return 0;
}

这篇关于NOIP模拟(10.22)T3 树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/330330

相关文章

CSS模拟 html 的 title 属性(鼠标悬浮显示提示文字效果)

《CSS模拟html的title属性(鼠标悬浮显示提示文字效果)》:本文主要介绍了如何使用CSS模拟HTML的title属性,通过鼠标悬浮显示提示文字效果,通过设置`.tipBox`和`.tipBox.tipContent`的样式,实现了提示内容的隐藏和显示,详细内容请阅读本文,希望能对你有所帮助... 效

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<

hdu4431麻将模拟

给13张牌。问增加哪些牌可以胡牌。 胡牌有以下几种情况: 1、一个对子 + 4组 3个相同的牌或者顺子。 2、7个不同的对子。 3、13幺 贪心的思想: 对于某张牌>=3个,先减去3个相同,再组合顺子。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOExcepti

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

【算法专场】模拟(下)

目录 前言 38. 外观数列 算法分析 算法思路 算法代码 1419. 数青蛙 算法分析 算法思路 算法代码  2671. 频率跟踪器 算法分析 算法思路 算法代码 前言 在前面我们已经讲解了什么是模拟算法,这篇主要是讲解在leetcode上遇到的一些模拟题目~ 38. 外观数列 算法分析 这道题其实就是要将连续且相同的字符替换成字符重复的次数+

模拟实现vector中的常见接口

insert void insert(iterator pos, const T& x){if (_finish == _endofstorage){int n = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + n;//防止迭代

PHP实现二叉树遍历(非递归方式,栈模拟实现)

二叉树定义是这样的:一棵非空的二叉树由根结点及左、右子树这三个基本部分组成,根据节点的访问位置不同有三种遍历方式: ① NLR:前序遍历(PreorderTraversal亦称(先序遍历)) ——访问结点的操作发生在遍历其左右子树之前。 ② LNR:中序遍历(InorderTraversal) ——访问结点的操作发生在遍历其左右子树之中(间)。 ③ LRN:后序遍历(PostorderT

1 模拟——67. 二进制求和

1 模拟 67. 二进制求和 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 示例 1:输入:a = "11", b = "1"输出:"100"示例 2:输入:a = "1010", b = "1011"输出:"10101" 算法设计 可以从低位到高位(从后向前)计算,用一个变量carry记录进位,如果有字符没处理完或者有进位,则循环处理。两个字符串对