1 条题解
-
0
自动搬运
来自洛谷,原作者为

tzc_wk
**搬运于
2025-08-24 22:27:08,当前版本为作者最后更新于2020-12-26 21:19:11,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题意:
给出集合 $S=[l_1,r_1]\cup[l_2,r_2]\cup[l_3,r_3]\cup\dots\cup[l_n,r_n]$ 和整数 ,求有多少个三元组 满足:
-
,
-
两两异或得到的值均
答案对 取模。
,$0\leq l_1\leq r_1\lt l_2\leq r_2\lt\dots\lt l_n\leq r_n\leq 10^9$
Yet another 1e9+7
Yet another 计数 dp
Yet another 我做不出来的题
阿巴细节题。
首先考虑优雅的暴力,也就是 那一档部分分。
建一棵包含 所有元素的 trie 树。
设 表示选择的三个数均在 的子树中的方案数。假设我们当前考虑到从高到低的第 位。
可以分为四种情况转移:
- 选择的三个数均在 的左子树内。这种情况又可分为两种情况:如果 的从高到低的第 位为 ,那么不论选哪三个点,它们两两异或起来从高到低第 位都为 。故不论选哪三个点都满足条件,贡献为 ;如果 从高到低的第 位为 ,那么贡献就是 。
- 选择的三个数均在 的右子树内,与第一种情况几乎一样。
- 选择的三个数中,其中两个数 在 的左子树内,一个数 在 的右子树内。由于 从高到低的第 位为 ,故这种情况只有当 的从高到低的第 位为 的情况下才能发生。
- 选择的三个数中,其中一个数 在 的左子树内,一个数 在 的右子树内,与第三种情况几乎一样。
不难发现,在这四种情况中前两种情况都是可以直接转移的,而后两种无法直接表示出来,需要引入另一个状态。
再设 表示选择的三个数中两个数在 的子树中,一个在 的子树中的方案数。
继续分情况讨论,可以分为六种情况:
-
在 子树中的两个数都在 的左子树中,在 子树中的数在 的左子树中。这种情况又可分为两种情况:如果 的从高到低的第 位为 ,那么不论选哪三个点,它们两两异或起来从高到低第 位都为 。故不论选哪三个点都满足条件,贡献为 。 如果 从高到低的第 位为 ,那么贡献就是 。
-
在 子树中的两个数 都在 的左子树中,在 子树中的数 在 的右子树中。由于 从高到低的第 位为 ,所以这种情况只有在当 的从高到低的第 位为 的情况下才能发生。这种情况产生的贡献为
-
在 子树中的两个数 中 在 的左子树中, 在 的右子树中,在 子树中的数 在 的左子树中。由于 从高到低的第 位为 ,所以这种情况只有在当 的从高到低的第 位为 的情况下才能发生。我们还注意到,不论 为何值都有 , 也就是说,不论 取何值,只要 满足条件, 一定满足条件。求出满足条件的 个数后乘一个 就行了。
-
在 子树中的两个数 中 在 的左子树中, 在 的右子树中,在 子树中的数 在 的右子树中,与第三种情况几乎一样,只不过变成了求出满足条件的 的个数 。
-
在 子树中的两个数 都在 的右子树中,在 子树中的数 在 的左子树中,与第二种情况几乎一样。
-
在 子树中的两个数都在 的右子树中,在 子树中的数在 的右子树中,与第一种情况几乎一样。
但是碰到这里我们又犯难了,情况 1,2,5,6 可以直接转移,但是情况 3,4 无法通过已有状态求出满足条件的 的个数,这是我们又需要引入一个新状态。
再设 表示两个数 中一个数在 的子树中,一个在 的子树中的方案数。
又可以分四种情况:
-
在 的左子树中, 在 的左子树中,这种情况又可分为两种情况:如果 的从高到低的第 位为 ,那么不论选哪两个点,它们两两异或起来从高到低第 位都为 。故不论选哪三个点都满足条件,贡献为 。 如果 从高到低的第 位为 ,那么贡献就是 。
-
在 的左子树中, 在 的右子树中。由于 从高到低的第 位为 ,所以这种情况只有在当 的从高到低的第 位为 的情况下才能发生。
-
在 的右子树中, 在 的左子树中,与第二种情况几乎一样。
-
在 的右子树中, 在 的右子树中,与第一种情况几乎一样。
算下时间复杂度: 的时间复杂度肯定是没问题的,加个记忆化每个 最多被计算一次。关键是 和 , 和 状态是二维的。可合法的状态数真的是 吗?非也。拿 举例,只有当 的第 位为 的时候才会调用 和 ,当 的第 位为 的时候才会调用 和 。稍微观察下即可发现, 与 表示的数异或起来肯定是 的一个前缀。这意味着对于每个 有唯一的 与之对应,故合法状态数只有 ,其中 为 trie 数上的点数。故这个“优雅的暴力”是没问题的~~(真 nm 优雅)~~。
最后考虑 的情况。其实想到这一步本题就已经做完了 了,虽然到这一步只包含了本题 的部分分。
我们发现待插入的数很多,高达 ,但是这些数都是一段一段区间,区间个数只有 。于是我们可以想到一个东西叫做线段树,可以通过线段树的思想将每个区间拆分成 个长度为 的整数次幂的区间插入 trie 树。这样一来我们可以得到这样的 trie 树:trie 树上每个叶子节点代表一个大小为 的满二叉树。
这样说有些抽象,举个例子,譬如我们要插入区间 ,如果按照之前的暴力我们会这样插:

但我们发现,黄色部分和绿色部分都是满二叉树,根本不用把它们建出来,所以我们索性把它们缩成一个“大点”:

但是这样还是不太行啊?如果你 到一个“大点”,对应到 值怎么计算呢?
可以考虑额外建 个节点,编号为 ,节点 的左右儿子都是节点 ,这样大小为 的子树就等价于节点 , 的时候直接在这 个节点上记录 值就可以了。
代码不长,也就 100 行而已,不过细节实在是太太太太太太多了,这篇题解也写了整整 1 个小时。。。
#include <bits/stdc++.h> using namespace std; #define fi first #define se second #define fz(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++) #define fill0(a) memset(a,0,sizeof(a)) #define fill1(a) memset(a,-1,sizeof(a)) #define fillbig(a) memset(a,63,sizeof(a)) #define pb push_back #define ppb pop_back #define mp make_pair template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;} template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;} typedef pair<int,int> pii; typedef long long ll; template<typename T> void read(T &x){ x=0;char c=getchar();T neg=1; while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();} while(isdigit(c)) x=x*10+c-'0',c=getchar(); x*=neg; } const int MAXN=2e4; const int LOG_N=30; const int MAXT=1e6; const int MAX=(1<<LOG_N)-1; const int MOD=1e9+7; const int TWO=5e8+4; const int SIX=166666668; int n,k,siz[MAXT+5],ch[MAXT+5][2],ncnt=LOG_N+1,rt=LOG_N+1,dep[MAXT+5]; bool isend[MAXT+5]; void update(int &k,int l,int r,int nl,int nr,int d){ if(l==nl&&nr==r){k=LOG_N-d;return;} if(!~k) k=++ncnt,dep[k]=d;int mid=(l+r)>>1; // printf("%d %d %d %d %d %d\n",k,l,r,nl,nr,d); if(nr<=mid) update(ch[k][0],l,mid,nl,nr,d+1); else if(nl>mid) update(ch[k][1],mid+1,r,nl,nr,d+1); else update(ch[k][0],l,mid,nl,mid,d+1),update(ch[k][1],mid+1,r,mid+1,nr,d+1); siz[k]=siz[ch[k][0]]+siz[ch[k][1]]; } int dp1[MAXT+5]; map<int,int> dp2[MAXT+5],dp3[MAXT+5]; int calc1(int x); int calc2(int x,int y); int calc3(int x,int y); //calc1: 3 nodes in subtree of x //calc2: 2 nodes in subtree of x and 1 node in subtree of y //calc3: 1 nodes in subtree of x and 1 node in subtree of y int calc1(int x){ // if(x==0||x==1) return 0; if(~dp1[x]) return dp1[x]; dp1[x]=0; if(~ch[x][0]){ if(k>>(LOG_N-dep[x]-1)&1) dp1[x]=(dp1[x]+1ll*siz[ch[x][0]]*(siz[ch[x][0]]-1)%MOD*(siz[ch[x][0]]-2)%MOD*SIX%MOD)%MOD; else dp1[x]=(dp1[x]+calc1(ch[x][0]))%MOD; } if(~ch[x][1]){ if(k>>(LOG_N-dep[x]-1)&1) dp1[x]=(dp1[x]+1ll*siz[ch[x][1]]*(siz[ch[x][1]]-1)%MOD*(siz[ch[x][1]]-2)%MOD*SIX%MOD)%MOD; else dp1[x]=(dp1[x]+calc1(ch[x][1]))%MOD; } if(~ch[x][0]&&~ch[x][1]) if(k>>(LOG_N-dep[x]-1)&1){ dp1[x]=(dp1[x]+calc2(ch[x][0],ch[x][1]))%MOD; dp1[x]=(dp1[x]+calc2(ch[x][1],ch[x][0]))%MOD; } // printf("%d %d\n",x,dp1[x]); return dp1[x]; } int calc2(int x,int y){ // if(!x) return 0; if(dp2[x].find(y)!=dp2[x].end()) return dp2[x][y]; dp2[x][y]=0; if(~ch[x][0]&&~ch[y][0]){ if(k>>(LOG_N-dep[x]-1)&1) dp2[x][y]=(dp2[x][y]+1ll*siz[ch[x][0]]*(siz[ch[x][0]]-1)%MOD*TWO%MOD*siz[ch[y][0]]%MOD)%MOD; else dp2[x][y]=(dp2[x][y]+calc2(ch[x][0],ch[y][0]))%MOD; } if(~ch[x][0]&&~ch[y][1]) if(k>>(LOG_N-dep[x]-1)&1) dp2[x][y]=(dp2[x][y]+calc2(ch[x][0],ch[y][1]))%MOD; if(~ch[x][0]&&~ch[x][1]&&~ch[y][0]) if(k>>(LOG_N-dep[x]-1)&1) dp2[x][y]=(dp2[x][y]+1ll*calc3(ch[x][1],ch[y][0])*siz[ch[x][0]]%MOD)%MOD; if(~ch[x][0]&&~ch[x][1]&&~ch[y][1]) if(k>>(LOG_N-dep[x]-1)&1) dp2[x][y]=(dp2[x][y]+1ll*calc3(ch[x][0],ch[y][1])*siz[ch[x][1]]%MOD)%MOD; if(~ch[x][1]&&~ch[y][0]) if(k>>(LOG_N-dep[x]-1)&1) dp2[x][y]=(dp2[x][y]+calc2(ch[x][1],ch[y][0]))%MOD; if(~ch[x][1]&&~ch[y][1]){ if(k>>(LOG_N-dep[x]-1)&1) dp2[x][y]=(dp2[x][y]+1ll*siz[ch[x][1]]*(siz[ch[x][1]]-1)%MOD*TWO%MOD*siz[ch[y][1]]%MOD)%MOD; else dp2[x][y]=(dp2[x][y]+calc2(ch[x][1],ch[y][1]))%MOD; } // printf("c2 %d %d %d\n",x,y,dp2[x][y]); return dp2[x][y]; } int calc3(int x,int y){ if(!x) return 1; if(dp3[x].find(y)!=dp3[x].end()) return dp3[x][y]; dp3[x][y]=0; if(~ch[x][0]&&~ch[y][0]){ if(k>>(LOG_N-dep[x]-1)&1) dp3[x][y]=(dp3[x][y]+1ll*siz[ch[x][0]]*siz[ch[y][0]]%MOD)%MOD; else dp3[x][y]=(dp3[x][y]+calc3(ch[x][0],ch[y][0]))%MOD; } if(~ch[x][0]&&~ch[y][1]) if(k>>(LOG_N-dep[x]-1)&1) dp3[x][y]=(dp3[x][y]+calc3(ch[x][0],ch[y][1]))%MOD; if(~ch[x][1]&&~ch[y][0]) if(k>>(LOG_N-dep[x]-1)&1) dp3[x][y]=(dp3[x][y]+calc3(ch[x][1],ch[y][0]))%MOD; if(~ch[x][1]&&~ch[y][1]){ if(k>>(LOG_N-dep[x]-1)&1) dp3[x][y]=(dp3[x][y]+1ll*siz[ch[x][1]]*siz[ch[y][1]]%MOD)%MOD; else dp3[x][y]=(dp3[x][y]+calc3(ch[x][1],ch[y][1]))%MOD; } // printf("c3 %d %d %d\n",x,y,dp3[x][y]); return dp3[x][y]; } int main(){ fill1(ch); siz[0]=1;for(int i=1;i<=LOG_N;i++) ch[i][0]=ch[i][1]=i-1,siz[i]=(1<<i),dep[i]=LOG_N-i; memset(dp1,-1,sizeof(dp1));scanf("%d%d",&n,&k); for(int i=1;i<=n;i++){int l,r;scanf("%d%d",&l,&r);update(rt,0,MAX,l,r,0);} // for(int i=0;i<=ncnt;i++) printf("%d %d %d %d\n",ch[i][0],ch[i][1],dep[i],siz[i]); printf("%d\n",calc1(rt)); return 0; } /* 1 3 0 3 1 6 0 5 1 15 0 10 */ -
- 1
信息
- ID
- 6346
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者