[THUPC2024] 分治乘法

本文讨论了如何通过分治方法结合哈夫曼编码来高效构建目标集合,通过分块和操作优化,达到代价复杂度O(n√logn)。
摘要由CSDN通过智能技术生成

[THUPC 2024 初赛] 分治乘法

题目描述

小艾想要挑战分治乘法。TA 将策略抽象成了如下问题:

现在给定一个目标集合 \(T\),该集合是 \(\{1,\dots,n\}\) 的一个子集(\(1\leq n\leq 5\times 10^5\))。你需要通过一系列操作构造一些集合最后得到 \(T\),具体来说有以下三种操作:

  • 创造一个大小为一的集合 \(|S|=1\)
  • 将已经被构造出的两个不交集合 \(A, B\) 并起来,得到 \(A\cup B\)
  • 将已经被构造出的一个集合 \(A\) 进行平移,也即 \(A+x = \{ a+x : a\in A \}\)

一个已经被构造出的集合可以在之后被使用多次。同时你需要保证操作过程中出现的所有集合都是 \(\{1,\dots,n\}\) 的子集。

你的代价是构造出的所有集合的大小之和,你不需要最小化代价,只需要让代价控制不超过 \(5\times 10^6\) 即可。你用的操作数量也不应超过 \(10^6\)

输入格式

第一行输入一个正整数 \(n\)

接下来一行输入一个 01 串,长度为 \(n\),第 \(x\) 位为 1 表示 \(x\in T\),否则 \(x\notin T\),保证 \(T\) 非空。

输出格式

第一行输出一个正整数 \(m\) 表示你使用的操作数量。

接下来 \(m\) 行,每行描述一个操作,设第 \(i\) 次操作得到的集合为 \(T_i\)

  • 1 x 表示创造一个大小为一的集合 \(\{x\}\)
  • 2 x y 表示将不交集合 \(T_x, T_y\) 并起来。
  • 3 x y 表示将已经被构造出的一个集合进行平移,也即 \(T_x+y\)

你需要保证所有操作满足题目要求,并且最后一次操作产生的集合是 \(T\)

赛时被蔡神秒了,%%%

参考了官方题解。

首先考虑分治,代价是 \(O(nlog n)\) 的,大概多了一倍。而且没用操作3.

平移想到什么?分块!

考虑 B 个为一块,那么每一块总共有 \(2^B\) 种形态。设第 \(i\) 种形态出现位置序列为 \(f_i\),那么 \(\sum|f_i|=\frac nB\),用分治做法得到所有的 \(f_i\),代价为 \(O(\frac nB\log n)\),然后再平移得到所有的位置。

然后再用合并果子合并上去。集合大小的和是 \(n\),总共 \(B2^B\) 个集合,复杂度是 \(O(nlog(B2^b))=O(nB)\) 的。

代价复杂度可以平衡到 \(O(n\sqrt {\log n})\)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,B=5,M=5e6+5;
int n,p[M],sz[M],r,idx,op[M],aa[M],bb[M];
char s[N];
vector<int>g[N];
int haffman()
{
	if(!r)
		return 0;
	priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
	for(int i=1;i<=r;i++)
		q.push(make_pair(sz[p[i]],p[i]));
	while(q.size()>1)
	{
		pair<int,int>u=q.top(),v;
		q.pop();
		v=q.top();
		q.pop();
		op[++idx]=2,aa[idx]=u.second,bb[idx]=v.second;
		q.push(make_pair(u.first+v.first,idx));
	}
	return q.top().second;
}
int slv(vector<int>&g,int l,int r)
{
	if(l==r)
	{
		op[++idx]=1,aa[idx]=g[l]+1;
		sz[idx]=1;
		return idx;
	}
	int md=l+r>>1;
	int u=slv(g,l,md);
	int v=slv(g,md+1,r);
	sz[++idx]=sz[u]+sz[v];
	op[idx]=2,aa[idx]=u,bb[idx]=v;
	return idx;
}
int main()
{
	scanf("%d%s",&n,s);
	for(int i=0;i<n;i+=B)
	{
		int t=0;
		for(int j=0;j<B&&i+j<n;j++)
			t|=s[i+j]-48<<j;
		g[t].push_back(i);
	}
//	puts("NO!!!");
	for(int i=0;i<(1<<B);i++)
	{
		if(g[i].empty())
			continue;
		int x=slv(g[i],0,g[i].size()-1);
		if(i&1)
			p[++r]=x;
		for(int j=1;j<B;j++)
			if(i>>j&1)
				p[++r]=++idx,op[idx]=3,aa[idx]=x,bb[idx]=j,sz[idx]=sz[x];
	}
	int k=haffman(); 
	printf("%d\n",idx);
	for(int i=1;i<=idx;i++)
	{
		printf("%d %d ",op[i],aa[i]);
		if(op[i]^1)
			printf("%d",bb[i]);
		puts("");
	 } 
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值