zoukankan      html  css  js  c++  java
  • hihoCoder挑战赛19 A.Rikka with Sequence(状压DP)

    题目链接
    比赛链接

    (Description)

    (Solution)

    参考:https://www.cnblogs.com/SovietPower/p/9781573.html

    暴力:(f[i][j][k])表示前(i)个数,与起来为(j),异或和为(k)的方案数。复杂度(O(n*4^{13}))

    考虑位运算的性质,最后怎么得到某一位的1:&要求所有数这一位为1,^只需判这一位为1的数的奇偶性。
    所以我们用13位三进制s表示13位01的状态(2表示全1,0/1表示奇偶性),再存一下选的数的个数。
    这样DP就是(O(n*3^{13}))了。

    但是直接(f[i][s][0/1])不会写啊,求路过dalao教。。(拆状态好像也挺麻烦)

    记异或和为(x),位与和为(y),因为是与,所以(x)再与(y)(y)是有关系的,也就是当选了奇数个数时,(x&y=y);否则(x&y=0)
    那么暴力中的合法的(j,k)实际没有(2^{13}*2^{13})那么多。
    所有合法状态满足(x&y=y)或是(x&y=0),也就是(y)要么是(x)的子集,要么与(x)没有交集(别忘这种情况啊)。
    因为有第二种情况所以只求异或和的所有子集不行。但再求一遍补集存状态也不对(不知道为什么)。

    (xx=x&(sim y)),我们发现(xx)还是确定的?而且因为(x,y)的关系,选奇数个时(x)就是(xx|y),否则(x=xx)
    我们枚举(y),再枚举(sim y)的子集(要(&8191))得到(xx)。(我也不知道怎么会想到用(xx)。。好神啊)
    在DP的时候根据奇偶性把(x)转化出来就行了(得状态再(&(sim y)))。然后就可以同暴力直接转移。
    状态数为(O(3^{13}))

    答案是(f[n][status(0,0)][0]+sum_s f[n][status(s,s)][1])
    复杂度也是(O(n*3^{13}))

    DP数组也要longlong(随机的话倒也爆不了int)。
    id[][]按枚举顺序确定下标会快近一倍。

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <algorithm>
    #define gc() getchar()
    #define all 8191
    #define cnt 1594323
    typedef long long LL;
    const int N=8192+3,M=1594323+3;
    
    int And[M],XX[M],id[N][N];
    LL F[M][2],G[M][2];
    
    inline int read()
    {
    	int now=0;register char c=gc();
    	for(;!isdigit(c);c=gc());
    	for(;isdigit(c);now=now*10+c-'0',c=gc());
    	return now;
    }
    int Init()
    {
    	int n=0;
    	for(int y=0; y<=all; ++y)//y
    	{
    		int ss=(~y)&all;
    		for(int x=ss; ; x=(x-1)&ss)
    		{
    			id[y][x]=++n;
    			XX[n]=x, And[n]=y;
    			if(!x) break;
    		}
    	}
    	return n;
    }
    
    int main()
    {
    //	const int all=8191;
    //	const int cnt=1594323;
    	Init();
    	int n=read(); LL (*f)[2]=F,(*g)[2]=G;
    	f[id[all][0]][0]=1;
    	for(int i=1,ai; i<=n; ++i)
    	{
    		ai=read(), std::swap(f,g);
    		memcpy(f,g,sizeof F);//f[i][s]=f[i-1][s]
    		for(int j=1; j<=cnt; ++j)
    			for(int k=0; k<2; ++k)
    			{
    				if(!g[j][k]) continue;
    				int x=XX[j],y=And[j];
    				k && (x|=y);
    				x^=ai, y&=ai;
    				x&=(~y);
    				f[id[y][x]][k^1]+=g[j][k];
    			}
    	}
    	LL ans=f[id[0][0]][0];
    	for(int i=1; i<=cnt; ++i) if(!XX[i]) ans+=f[i][1];//x==y xx=0
    	printf("%lld
    ",ans);
    
    	return 0;
    }
    
  • 相关阅读:
    线程
    unix架构
    Unix命令
    可重入函数reentrant function
    Eclipse 中 program arguments 与 VM arguments 的区别
    Java中Generics的使用
    Java的Reflection机制
    Java按值传递、按引用传递
    Java label
    LeetCode Merge Intervals
  • 原文地址:https://www.cnblogs.com/SovietPower/p/9781573.html
Copyright © 2011-2022 走看看