Hetao P1169 点集 题解 [ 黄 ][ 线性dp ]

点集

题解

一道非常傻逼,非常傻逼的暴力题,一点都不优雅,这能放普及 T4 真是开了眼了。

本题难点主要就是在时间复杂度的计算上,只要算对了并且有勇气去打就能 AC 。

首先发现能形成一个点集,当且仅当所有点从小到大排序后,后面的点是前面所有点的倍数

因此,我们只要保证点集中的数 \(b_i = k * b_{i-1}\) 即可。

于是,状态转移方程就这样推出来了。

\(b_i = max(k * b_{i-1}), (2\le k\le \frac{10^6}{b_{i-1}})\) , 枚举 \(k\)\(b_{i-1}\) 即可。

注意要提前离散一下所有数,然后开个 map 或 数组 判断某个数是否存在。

几个重要的性质:

  1. 一个点集最多有 $log n $ 种数。

  2. 由于对于所有 \(b\) 枚举 \(k\) 的时间为 $ \frac{10^6}{1} + \frac{10^6}{2} + \frac{10^6}{3} + \text{...}$ $ + \frac{10^6}{1000000-1} + \frac{10^6}{1000000} $ ,即它是一个调和级数,时间为 $O(ln n) \approx O(log n) $,可以过。

同样可以理解为一个反比例函数,然后分段求下和也可以证明这个复杂度正确。

注意,状态转移要从小往大转移。

赛时早就想出做法但不敢写,在比赛最后 20min 的时候才A掉这题的我是个傻逼

#include <bits/stdc++.h>
using namespace std;
int n,a;
vector<int> v;
struct num{
	int val,t=0,dp=0;//t为其出现次数,dp为动态规划
};
vector<num> vct;
int m[1000005];
int main()
{
	freopen("set.in","r",stdin);
	freopen("set.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a;
		v.push_back(a);
	}
	//离散化
	sort(v.begin(),v.end());
	memset(m,-1,sizeof(m));
	int tmp=0;
	for(auto i:v)
	{
		if(tmp==0 || v[tmp-1]!=i)
		{
			vct.push_back({i,1});
			m[i]=vct.size()-1;
		}
		else vct[vct.size()-1].t++;
		tmp++;
	}
	//动态规划
	int maxans=-1;
	for(int i=0;i<vct.size();i++)
	{
		vct[i].dp=max(vct[i].dp,vct[i].t);
		int x=vct[i].val,y=vct[i].t,z=vct[i].dp;
		for(int j=2;j*x<=1000000;j++)
		{
			if(m[j*x]!=-1)
			{
				vct[m[j*x]].dp=max(vct[m[j*x]].dp,vct[m[j*x]].t+z); 
			}
		}
		maxans=max(maxans,vct[i].dp);
	}
	cout<<maxans;
	return 0;
}
posted @ 2024-03-31 21:35  KS_Fszha  阅读(78)  评论(0编辑  收藏  举报