【2022noip多校】异或

【题目描述】

对于一个元素介于 \([0,2^m)\) 且互不相同的长度为 \(n\) 的序列 \(a_1, a_2 ...,a_n\) ,定义它的特征序列为 \(p_0,p_1,...,p_{2^m-1}\) ,其中 \(p_i\) 表示使得 \(a_{p_i}\)\(i\) 的异或值最大的下标。

​ 形式化地,定义 $ p_i=\arg \max\limits_{1<=j<=n} a_j \oplus i $

给定一个特征序列 \(p\) ,求有多少个满足要求的原序列 \(a\) 可以得到这个特征序列。

​ 答案对 \(10^9+7\) 取模

【样例】

【样例输入1】
6 3
1 1 2 2 3 4 5 6
【样例输出1】
4

【解析】

首先明确,合法的 \(p\) 中,必定出现完整的 \([1,n]\)

\([0,2^m-1)\) 的二进制形式依次写出(取 \(m=3\)

000 001 010 011 100 101 110 111

发现每次取一半,在第 \(k\) 次时,会使从高到底第 \(k\) 为发生 \(0,1\) 分割

所以考虑分治:

设状态 \([l,r]\) 表示在 \([l,r]\) 中,任意 \(a_{p_i} \oplus i\) 的前 \(dep\) 位相同, \(dep\) 表示分治深度。

其实这也代表了 \(a_{p_i}\)\(dep\) 位相同,这个可以从之前的例子中看出。

接下来考虑下一层:

  1. 若集合 \(p_{l,..,mid}=p_{mid+1,..,r}\) 则表示下一位这两边也相同,则将方案数乘二,问题规模减半

  2. 若集合 \(p_{l,..,mid}!=p_{mid+1,..,r}\) 则代表下一位不同,且可以得出 \(a_{l,..,mid}\)\(dep\) 位一定为 \(1\)\(a_{mid+1,..,r}\)\(dep\) 位一定为 \(0\),因为要保持最大且左区间的 \(dep\) 位均为 \(0\) ,右区间均为 \(1\) 所以有以上结论。之后这两种情况就独立了,乘法原理直接乘起来就可以了。

    考虑无解,即集合 \(p_{l,..,mid}!=p_{mid+1,..,r}\)\(p_{l,..,mid}\cap p_{mid+1,..,r}!=\varnothing\) 就无解。

【CODE】

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn = (1<<16)+33;
inline ll read_int(){
	ll a=0;bool f=0;char g=getchar();
	while(g<'0'||'9'<g) {if(g=='-') f=1;g=getchar();}
	while('0'<=g&&g<='9') a=a*10+g-'0',g=getchar();
	return f ? -a : a;
}

inline void write(ll a,bool b=1){
	if(a<0) a=-a,putchar('-');
	ll lin[30],top=0;
	while(a) lin[++top]=a%10,a/=10;
	if(top==0) lin[++top]=0;
	while(top) putchar(lin[top--]+'0');
	if(b) putchar('\n');
}

ll n,m,mod=1e9+7;
ll sj[maxn];
ll vis[maxn];

inline ll fz(ll l,ll r){
    // cout<<l<<" "<<r<<endl;
    if(l==r) return 1;
    ll mid=(l+r)>>1;
    int bt=0,xd=0;
    set<int> a,b;
    int f=0;
    for(int i=l;i<=mid;i++) a.emplace(sj[i]);
    for(int i=mid+1;i<=r;i++) b.emplace(sj[i]),f= (f||(a.find(sj[i])!=a.end()) ? 1 : 0);
    if(f&&a!=b) return 0;
    if(a==b) return (ll)2*fz(l,mid)%mod;
    else return fz(l,mid)*fz(mid+1,r)%mod;
}

inline void read(){
    n=read_int(),m=read_int();
    for(int i=1;i<=(1<<m);i++) sj[i]=read_int(),vis[sj[i]]=1;
    for(int i=1;i<=n;i++){
        if(vis[i]==0) {write(0);return;}
    }
    write(fz(1,(1<<m)));
}

int main (){
    // freopen(".out","w",stdout);
    read();
    // while(1) getchar();
}

【后记】

如有大佬知道之前那个合法的 \(p\) 中必有 \([i,n]\) 是如何证明的,请留言

posted @ 2022-10-17 23:52  轩Demonmaster  阅读(39)  评论(0编辑  收藏  举报