[2019CCPC哈尔滨] B Binary Numbers dp

题意比较复杂。
给出 m ≤ 17 m\leq17 m17 表示我现在有 [ 0 , 2 m − 1 ] [0,2^{m}-1] [0,2m1]这样的一个区间,然后现在有 0 ≤ N ≤ 2 m 0\leq N \leq 2^{m} 0N2m表示我将会把这个区间分为 N N N段区间,这 N N N段是覆盖所有的区间并且严格不相交的。
然后在每个区间里面都选一个数 A i ∈ [ L i , R i ] A_{i}\in[L_{i},R_{i}] Ai[Li,Ri],然后要满足对于任意一个区间,都有 A i A_{i} Ai和这个区间中任何数的二进制表示的前缀最大值不小于 A j A_{j} Aj和这个区间中任何数的二进制表示的前缀最大值。
如果存在满足这样的一个序列,它的贡献是 ∏ i = 1 N A i \prod_{i=1}^{N}A_{i} i=1NAi。求所有方案的贡献之和,答案模 1 e 8 + 7 1e8+7 1e8+7
其实冷静思考一下,这个前缀最大值越大,说明这个数和当前的数越接近。
那么对于任何一个区间里面的数,我只要保证它和离它最远的两个端点都比旁边的数和端点的值就可以了。
记录 l c p ( x , y ) lcp(x,y) lcp(x,y)表示 x x x y y y的前缀最大值。那么限制条件为:
l c p ( a i , l i ) ≥ l c p ( a i − 1 , l i ) lcp(a_i,l_i)\geq lcp(a_{i-1},l_i) lcp(ai,li)lcp(ai1,li)
l c p ( a i , r i ) ≥ l c p ( a i + 1 , r i ) lcp(a_i,r_i)\geq lcp(a_{i+1},r_i) lcp(ai,ri)lcp(ai+1,ri)
f i , j , k f_{i,j,k} fi,j,k表示到第 i i i组, A i A_{i} Ai和下一个左端点 l c p lcp lcp j j j,和当前右端点的 l c p lcp lcp k k k的答案。
转移的时候满足合法条件即可。
复杂度是 O ( 2 m m 2 ) O(2^{m}m^{2}) O(2mm2)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e8+7;
const int N=(1<<17)+1;
const int M=19; 
int l[N],r[N]; 
int f[N][M][M]; // f[i][j][k] -> cur=i,lcp(a_i,l_{i+1})=j,lcp(a_i,r_i)=k 
int n,m;
int lcp(int x,int y) {
	int ans=0;
	for(int i=m-1;i>=0;i--) {
		if((x&(1<<i))==(y&(1<<i))) ans++;
		else break;
	}
	return ans;
} 
int main() {
	int T;
	scanf("%d",&T);
	while(T--) {
		scanf("%d%d",&m,&n);
		for(int i=1;i<=n;i++) 
			scanf("%d%d",&l[i],&r[i]);
		for(int i=0;i<=n;i++) 
			for(int j=0;j<=18;j++) 
				for(int k=0;k<=18;k++) 
					f[i][j][k]=0;
		f[0][0][m]=1;
		for(int i=1;i<=n;i++) {
			for(int a=l[i];a<=r[i];a++) {
				int lcpl=lcp(a,l[i]); // left
				int lcpr=lcp(a,r[i]); // right 
				int lstr=(i>=2)?lcp(a,r[i-1]):0;
				int nxtl=(i<n)?lcp(a,l[i+1]):0;
				int sum=0;
				for(int j=0;j<=lcpl;j++) {
					for(int k=lstr;k<=m;k++) {
						sum=(sum+f[i-1][j][k])%mod;
					}
				}
				f[i][nxtl][lcpr]+=(1ll*a*sum)%mod;
				f[i][nxtl][lcpr]%=mod;
			}
		}
		int ans=0;
		for(int i=0;i<=m;i++) {
			for(int j=0;j<=m;j++) {
				ans=(ans+f[n][i][j])%mod;
			}
		}
		printf("%d\n",ans); 
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值