首页 > 其他分享 >【NOI2023】合并书本

【NOI2023】合并书本

时间:2023-08-22 12:12:36浏览次数:44  
标签:ch min int 合并 分裂 叶子 NOI2023 p1 书本

Description

传送门

Solution

Part 1

考虑一棵合并树,令 \(ls_u,rs_u\) 表示 \(u\) 的左右儿子,\(d_u\) 表示 \(u\) 子树的最大深度,\(c_u\) 表示 \(u\) 被合并的次数,令所有非叶非根节点对应 \(2^d - 1\) 的和为 \(S\),则答案为 \(\sum_{i=1}^n c_iw_i + S\)。

可以发现,我们只关心 \(c\) 的可重集及其对应的 \(\min\{S\}\)。当确定 \(c\) 的可重集后,为最小化 \(\sum_{i=1}^n c_iw_i\),我们将 \(c\) 从小到大排序,\(w\) 从大到小排序,再一一对应即可。

下面,我们的目标是:求出所有可能作为最优解的可重集 \(c\) 及其对应的 \(\min\{S\}\)。

Part 2

先考虑怎么暴力求出所有的可重集 \(c\) 及其对应的 \(\min\{S\}\)。

考虑使用分裂叶子的方法。具体来说,刚开始树上只含一个点,即根,其 \(c = 0\)。每次,我们找到树上的某个叶子 \(u\),建出 \(ls_u,rs_u\),\(u\) 不再是叶子的同时得到两个新的叶子。

但是,每当分裂一个叶子后,新的 \(\min\{S\}\) 与树形态有关,难以进一步优化。

考虑一种特殊的分裂顺序,倒序枚举 \(d_0 = n, n - 1, \cdots, 0\),每次将树上所有 \(d = d_0\) 的点分裂。这样的好处在于,分裂前所有非叶节点、被分裂的叶子的 \(d\) 在分裂后均恰好增加 \(1\),即 \(\min'\{S\} = 2\min\{S\} \ +\) 分裂前树上非叶点数 \(+\) 分裂叶子数,该值与树形态无关。

设计 \(dp_{c}\) 表示,目前分裂得到的可重集为 \(\{c\}\),此时 \(\min\{S\}\) 的值。转移时,分裂任何叶子都是可行的,可以忽略 \(d = d_0\) 的限制;这是因为,不满足限制只会将答案算大,而存在最优解因满足限制而被计入。

显然,我们不需枚举所有叶子的集合,只需枚举 \(c\) 的所有前缀,将其分裂并转移。注意,分裂 \(x\) 会得到 \(x, x + 1\)。

期望得分 \(75\) 分。

Part 3

考虑减枝。对于每个 \(\{c\}\),我们额外记录 \(lim_c\),表示所有转移到 \(c\) 的最优状态里,至少分裂了多少叶子。那么,此时分裂的叶子数 \(\ge lim_{c}\)。

这样一来,分裂叶子数的可能情况大幅减少,转移复杂度大幅降低,可以通过本题。

Code

参考了 Alex_Wei 的实现。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=105,inf=1e18,max_ans=169073741990;

namespace IO{
	inline char nc(){
		static char buf[500001],*p1=buf,*p2=buf;
		return p1==p2&&(p2=(p1=buf)+fread(buf,1,500000,stdin),p1==p2)?EOF:*p1++;
	}
	char out[500001],*pout=out,*eout=out+500000;
	template<typename T> inline T read(){
		char ch=nc(); T sum=0; bool f=false;
		for(;ch<'0'||ch>'9';ch=nc()) if(ch=='-') f=1;
		while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
		return f?-sum:sum;
	}
}
#define read IO::read<int>

int n,w[N]; map<vector<int>,pair<int,int>> states[N];
void init(int maxsz){
	states[1][{0}]=make_pair(0,1);
	for (int n=1;n<=maxsz;n++){
		for (auto &cur:states[n]){
			int cur_cost=cur.second.first;
			for (int cnt=cur.second.second;cnt<=n&&n+cnt<=maxsz;cnt++){
				int new_n=n+cnt,new_cost=(cur_cost<<1)+cnt+(n-2);
				if (new_cost>max_ans)  break;

				vector<int> new_arr=cur.first;
				for (int i=0;i<cnt;i++)  new_arr.emplace_back(cur.first[i]+1);
				sort(new_arr.begin(),new_arr.end());

				auto it=states[new_n].find(new_arr);
				if (it!=states[new_n].end()){
					auto &info=it->second;
					if (new_cost<info.first)  info=make_pair(new_cost,cnt);
					else if (new_cost==info.first)  info.second=min(info.second,cnt);
				}
				else states[new_n][new_arr]=make_pair(new_cost,cnt);
			}
		}
	}
}
signed main(){
	int T=read(); init(N-5);
	while (T--){
		n=read();
		for (int i=0;i<n;i++)  w[i]=read();
		sort(w,w+n,greater<int>());

		int ans=inf;
		for (auto &state:states[n]){
			int sum=state.second.first;
			for (int i=0;i<n&&sum<ans;i++)  sum+=w[i]*state.first[i];
			ans=min(ans,sum);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

标签:ch,min,int,合并,分裂,叶子,NOI2023,p1,书本
From: https://www.cnblogs.com/ducati/p/17648221.html

相关文章

  • eclipse 合并错分支代码还原,合并到本分支但未push到库上
    由于本地分支较多,稍不留神就合并错误,发现合并错误,但未提交push到git库上,此时想要还原。如图  那么需要还原,之前处理方式,删除本地代码,重新从版本库下载。 gitreset0e7d080 --hard......
  • postgresql 查询重复,多行合并
    --postgresql--替换字符串UPDATEtmpSETphone=REPLACE(phone,'myzs','');--查询替换中间4位为*SELECTCONCAT_WS('****',SUBSTR(phone,1,3),SUBSTR(phone,8))asnew_phone_numberFROMtmp;--更新手机号为中间四位为*UPDATEtmpsetnewphone=C......
  • EFCore多数据库合并查询分页
    EFCore多数据库合并查询分页参照:二个表的数据如何做分页?_两个表排序分页_深圳市热心市民市民的博客-CSDN博客基本情况介绍:由于系统迭代,部分收藏表在老系统的数据库,部分在新api接口的数据库,现在有一个需求是在个人中心展示用户收藏的数据,按照收藏时间倒序排列,因为在APP端实际上......
  • 合并文件
    defmerge_file(result_dict):ifnotisinstance(result_dict,dict):raiseValueError('inputparametermustdict!')iflen(result_dict)<=1:raiseValueError('{}cannotbeNoneandatleast2dataframe!'.f......
  • C#合并DLL到exe中
    操作步骤通过NuGet安装Costura.Fody和Fody将根目录下的【FodyWeavers.xml】文件内容替换成下面代码在编译即可<Weaversxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"><Costura><ExcludeAssembl......
  • 代码随想录算法训练营第二十天| 654.最大二叉树 617.合并二叉树 700.二叉搜索树
      654.最大二叉树    卡哥建议:又是构造二叉树,昨天大家刚刚做完 中序后序确定二叉树,今天做这个 应该会容易一些, 先看视频,好好体会一下 为什么构造二叉树都是 前序遍历    题目链接/文章讲解:https://programmercarl.com/0654.%E6%9C%80%E5%A4%A7%E4%BA%8C%E5......
  • 解析BeanDefinitionRegistry与BeanDefinition合并
    本文分享自华为云社区《Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析》,作者:砖业洋__。1.什么是BeanDefinitionRegistry?BeanDefinitionRegistry是一个非常重要的接口,存在于Spring的org.springframework.beans.factory.support包中,它是Spring中注......
  • 区间合并
    Smiling&Weeping----我多想痛哭一场。然而我觉得这颗心,比沙漠还要干燥。题目链接:Problem-4553(hdu.edu.cn)题目大意:就是一道区间合并的......
  • git合并提交记录
    执行变基命令gitrebase-iHEAD~3(以合并3条为例),出现下图所示界面把需要被合并的提交记录由pick改为squash,wq保存退出删除多余commit记录,wq保存......
  • 合并二叉树
    力扣(LeetCode)官网-全球极客挚爱的技术成长平台经验1:程序写在递归函数前面代表压栈的时候实现,也就是说浏览到这个结点的时候实现程序写在递归函数后面代表弹栈的时候实现,也就是下一次递归结束后在本次递归函数中实现那么到底是压栈的时候实现还是弹栈的时候实现呢,这要看对根......