• UOJ#370. 【UR #17】滑稽树上滑稽果 动态规划


    原文链接www.cnblogs.com/zhouzhendong/p/UOJ370.html

    题解

    首先易知答案肯定是一条链,因为挂在链的最下面肯定比挂在其他节点上赚。

    问题被转化成了从一个集合中不断选数加入到当前序列尾端,使得序列的所有前缀 AND 之和最小。

    我们发现,假如加入一个数后可以使序列的 AND 值变小,那么必然不会去加一个使 AND 值不变的。

    假设 $v = a_1 { m and} a_2 { m and} cdots { m and} a_n$,先使 $a'_i = a_i { m XOR} v$ ,然后对于 a' 来求答案,最后答案加上 $ncdot v$ 。

    由于在序列的 AND 值变成 0 之前,每次都会使 AND 值变小,所以不可能加入相同的数。

    于是我们可以得到一个 $O(n^2)$ 的 dp。

    设 dp[i] 表示加入若干个数使得当前 AND 值为 i 的最小花费。

    转移暴力枚举下一个填什么数。

    注意到状态 i 能转移到的状态一定是 i 的子集。而枚举所有子集的复杂度是 $O(a_i^{log_2 3})$ 的,所以我们可以考虑从这里找到本题的突破口。

    我们现在要做的是判断 i 是否能转移到 j 。也就是是否存在一个 k ,使得 $i { m and} a_k = j$ 。

    由于 i>j ,所以上式等价于:(~i) and (~a[k]) > 0, i xor ((~i) and (~a[k])) = j 。

    于是我们考虑预处理出每一个值 v 是否满足 “存在一个 k ,使得 a[k] and v = v” 。于是 dp 转移的时候就枚举一下自己判定一下就可以了。

    但是这样转移可能会导致一些本来没有的转移被转移了,但是显然这个不影响最优解。

    时间复杂度: 

    $$O(a_i ^{log_2  3})$$

    代码

    #pragma GCC optimize("Ofast","inline")
    #include <bits/stdc++.h>
    #define clr(x) memset(x,0,sizeof (x))
    #define For(i,a,b) for (int i=a;i<=b;i++)
    #define Fod(i,b,a) for (int i=b;i>=a;i--)
    #define pb(x) push_back(x)
    #define mp(x,y) make_pair(x,y)
    #define fi first
    #define se second
    #define _SEED_ ('C'+'L'+'Y'+'A'+'K'+'I'+'O'+'I')
    #define outval(x) printf(#x" = %d
    ",x)
    #define outvec(x) printf("vec "#x" = ");for (auto _v : x)printf("%d ",_v);puts("")
    #define outtag(x) puts("----------"#x"----------")
    #define outarr(a,L,R) printf(#a"[%d...%d] = ",L,R);
    						For(_v2,L,R)printf("%d ",a[_v2]);puts("");
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef vector <int> vi;
    LL read(){
    	LL x=0,f=0;
    	char ch=getchar();
    	while (!isdigit(ch))
    		f|=ch=='-',ch=getchar();
    	while (isdigit(ch))
    		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	return f?-x:x;
    }
    const int N=1<<18;
    const LL INF=1e18;
    int n,t=18;
    int a[N],vis[N];
    int And=(1<<t)-1;
    LL dp[N];
    int main(){
    	n=read();
    	For(i,1,n){
    		a[i]=read();
    		And&=a[i];
    	}
    	For(i,1,n)
    		a[i]^=And;
    	For(i,1,n)
    		vis[(N-1)^a[i]]=1;
    	For(i,0,t-1)
    		For(j,0,N-1)
    			if (~j>>i&1)
    				vis[j]|=vis[j|1<<i];
    	For(i,0,N-1)
    		dp[i]=INF;
    	For(i,1,n)
    		dp[a[i]]=a[i];
    	Fod(i,N-1,0){
    		if (dp[i]>=dp[0])
    			continue;
    		for (int j=i;j>0;j=(j-1)&i)
    			if (vis[j])
    				dp[i^j]=min(dp[i^j],dp[i]+(i^j));
    	}
    	cout<<(LL)And*n+dp[0]<<endl;
    	return 0;
    }
    

      

  • 相关阅读:
    js如何判断访问来源是来自搜索引擎(蜘蛛人)还是直接访问
    thinkphp AOP(面向切面编程)
    crontab命令详解 含启动/重启/停止
    直播协议的选择:RTMP vs. HLS
    说一下PHP中die()和exit()区别
    宝塔Linux常用命令
    阿里云Redis公网连接的解决办法
    DMA及cache一致性的学习心得 --dma_alloc_writecombine【转】
    DMA内存申请--dma_alloc_coherent 及 寄存器与内存【转】
    内核中container_of宏的详细分析【转】
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/UOJ370.html
Copyright © 2020-2023  润新知