我的第一个想法其实是毫无头绪
根本就想不到dp,直接就写了爆搜
后来讲了才知道。。。
这种dp的状态好像是一类dp的模型,他们的状态都有这样的一维:以第i个数结尾。
这样的dp有什么样的标志呢?
以第i个数为结尾,说明这个状态和第i个数是有关系的,一般是选择数列中的数字
这种状态在于他的状态出来了,转移一般也能够直接显示出来,然后在转移中会枚举前面的状态进行转移,然后就可以在枚举中进行优化。
最后一般是O(n)或者O(n log n)的复杂度.
这里有一道这样的dp题目
http://acm.hdu.edu.cn/showproblem.php?pid=4055
这道题目呢,不要去考虑每一个数的大小,在我们的眼里,它应该是只能是一个大小的关系,至于到底是几,在每一个状态里面根本就没有考虑的必要。也没有意义
设f[i][j]为选到了第i个数,目前以j结尾的可能性。
转移就很明显了
这里就不讲了
这道题目和这个T3是一样的,状态中都有一维是以i结尾。
因为在目前,我们的决策只是取决于上一个数的大小,所以只用记录一下就行了
这道题目呢,我们发现这是异或。
异或有一个奇妙的性质,两个数之间如果他们差的绝对值越小,他们的异或值越小。
这个是我写上一个T3时发现的。。。
艹
那么,我们先拍个序,就可以发现,如果要满足这个任意两个数的异或都要大于x,那就是要满足,任意一个数和其他的数异或的最小值都要大于x
上面已经讲了,一个数异或的最小值就是和它最近的数的异或,在排序后就是他左右的数
所以我们只需要保证目前这个阶段在转移的时候合法即可,这个阶段的决策完全不会影响到其他任何的决策.这其实是我应该想到的。
唉
所以就有了O(n^2)暴力
//¼ÓÓÍ #include<bits/stdc++.h> #define ll long long using namespace std; int f[100001],n,a[100001],x; inline ll read() { char c=getchar();ll a=0,b=1; for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1; for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48; return a*b; } int main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n=read();x=read(); for(int i=1;i<=n;i++) { a[i]=read(); } sort(a+1,a+1+n); f[1]=0; for(int i=1;i<=n;i++) { f[i]++; for(int j=1;j<i;j++) { if((a[j]^a[i])>=x) { f[i]+=f[j]; } } } int ans=0; for(int i=1;i<=n;i++)ans+=f[i]; cout<<ans<<endl; return 0; }
真不戳
我们发现这个转移中,每一次都要用O(n)去查找a[j]使a[i]^a[j]比x大。
我们需要一个东西来快速查找比x大的a[i]^a[j],这时我们想到了trie树.
我们把每一个f[j]都先放进trie数中,每次就是在询问a[i]^a[j]>x的f[j]之和,这个可以直接在trie树上维护子树和。
时间复杂度O(n log max(a[i]))