雅礼集训 Day10 T1 数字重排 【bitset优化DP】

雅礼集训 Day10 T1 数字重排 【bitset优化DP】

题目分析:

容易发现将Ci从小到大排序后,若c1!=c2c_1!=c_2,直接输出c1c_1即可
c1==c2c_1==c_2,那么答案一定小于c1c_1
容易发现当一个数xx模了一个较小的数后,再模一个大数就没有意义了

于是在这个基础上想到DP,设fif_i表示i能否在模的过程中被取到,最后的合法答案就是fc11f_{c_1-1}f1f_1中最大的为1的数,显然应该由大到小转移,于是枚举iicnc_n到1

  • i==cki==c_k,那么fi=1f_i=1,同时给i的倍数打上标记
  • ii是由某个数模后得到的,就需要存在一个jj使得fj==1f_j==1jij-i是前面某个ckc_k的倍数(被标记过)

暴力转移的时间复杂度是O(nlnn+n2)O(n\ln n+n^2),105的范围无法承受
我们把标记数组记为gg,那么第二种情况fi=1f_i=1的条件就是存在fj==1&&gji==1f_j==1\&\&g_{j-i}==1,这样的条件约束是典型的bitset优化(典型到我闻所未闻。。。),等价于((f>>i) & g).any()==1((f>>i) ~\&~g).any()==1,any表示存在某一位为1

听说bitset常用于跑105O(n2)O(n^2)算法。。。
所以算法的时间复杂度就是O(nlnn+n2w)O(n\ln n+\frac {n^2}w)

代码巨短:

#include<cstdio>
#include<bitset>
#include<algorithm>
#define maxn 100005
using namespace std;
int n,a[maxn];
bitset<maxn>f,g;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	if(a[1]!=a[2]) printf("%d",a[1]),exit(0);
	n=unique(a+1,a+1+n)-a-1;
	f[0]=1;
	for(int i=a[n],j=n;i>=1;i--)
		if(a[j]==i){
			f[i]=1;
			for(int k=j;k<=a[n];k+=j) g[k]=1;
			j--;
		}
		else if(((f>>i)&g).any()) f[i]=1;
	for(int i=a[1]-1;i>=0;i--) if(f[i]) printf("%d",i),exit(0); 
}

PS:也可以让C由大到小排序,令f[i][j]f[i][j]表示前i个数能否得到j,转移就先继承前面的,再枚举k,f[i] =f[i1]&gt;&gt;kcif[i]~|=f[i-1]&gt;&gt;k*c_i,复杂度。。。似乎有点炸

贴一个bitset常用函数