BZOJ4693 雪中送温暖

传送门

题目大意

定义$K$维信号灯$(K\leq 9)$有$K$个非负整数$X_1,X_2...X_k$组成。

定义一个信号灯的$K$个前驱信号灯由$K$个$X$分别$-1$得到。

当$\prod\limits_{i=1}^K X_i=0$时信号灯为绿色,当$X_1=X_2=...=X_K=1$时信号灯为红色。

若一个信号灯的$K$个前驱中有偶数个是红色,则该信号灯是绿色,否则是红色。

给定$K$组区间$[L_i,R_i]$,求满足$X_i\in[L_i,R_i]$的红色信号灯的数量$(1\leq L_i\leq R_i\leq 10^{15})$。

 

题解

思考一下这个递推的过程实际上是在干什么

想像一个信号灯是在$K$为空间的一个坐标,初始在$X_i=1$,每次可以选择一维坐标$+1$,红色表示能到达这个坐标的方案为奇数,绿色表示到达这个坐标的方案为偶数。坐标必须为正,所以出现$X_i=0$表示这个坐标不可被到达,方案数为$0$,那么初始时在$\{X_i\}$,方案数为$1$,考虑如何计算到达每个点的坐标的方案数。

我们令$X_i=X_i-1$,这样就求出了每个维度坐标需要增加的数量,红绿情况就是

$$\prod\limits_{i=1}^{K}\binom{\sum\limits_{j=1}^{i}X_j}{X_i}(\mod\space 2)$$

用卢卡斯定理大力推一波得到出在二进制意义下$X_i$之间的加法没有进位。

即$X_i$异或和对于$\sum X_i$,二进制下每一位上在所有的$X_i$至多只出现一个$1$。

然后就可以数位$DP$了,将$K$个数卡位情况压成两个二进制状态。

$F_{(i,s_1,s_2)}$表示对于前$i$位,下界被卡住情况为$s_1$,上界被卡住情况为$s_2$的方案数。

枚举第$i$位不填或填在某一个$X_i$上,结合对卡位情况的改变进行转移。

其实用意义的状态很少,复杂度大概是$O(T(2^{2K}+K3^K)\log_2 R_i)$。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 52
#define mod 998244353
using namespace std;
LL read(){
	LL nm=0,fh=1; int cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10ll+(cw-'0');
	return nm*fh;
}
LL L[M],R[M]; 
int K,m,F[M][540][540],MAXN,vis[11],ans;
int add(int x,int y){return (x+y)>=mod?x+y-mod:x+y;}
void upd(int &x,int y){x=add(x,y);}
void write(int x){if(x>9)write(x/10);putchar(x%10+'0');}
int main(){
	for(int T=read();T;--T,write(ans),putchar('\n')){
		K=read(),m=ans=0,memset(F,0,sizeof(F)),MAXN=(1<<K)-1;
		for(int i=0;i<K;i++) L[i]=read()-1;
		for(int i=0;i<K;i++){
			R[i]=read()-1;
			while((1ll<<m)<=R[i]) m++;
		} F[m][MAXN][MAXN]=1;
		for(int i=m;i>=0;i--){
			for(int s1=0;s1<=MAXN;s1++){
				for(int s2=0;s2<=MAXN;s2++){
					if(!F[i][s1][s2]) continue; if(!i){upd(ans,F[i][s1][s2]);continue;}
					int v=i-1,ct=0,t1=s1,t2=s2,ot; memset(vis,0,sizeof(vis));
					for(int k=0;k<K;k++){
						if((s1>>k)&(L[k]>>v)&1) ct++,vis[k]=1;
						if((s2>>k)&1){if(!((R[k]>>v)&1)) vis[k]=2;else t2^=(1<<k);}
					}
					if(ct>1) continue; if(!ct) upd(F[i-1][s1][t2],F[i][s1][s2]);
					for(int k=0;k<K;k++){
						if(!(vis[k]==1||(!vis[k]&&!ct))) continue;
						t1=((s1>>k)&((L[k]>>v)^1)&1)?s1^(1<<k):s1;
						ot=((s2>>k)&(R[k]>>v)&1)?t2^(1<<k):t2;
						upd(F[i-1][t1][ot],F[i][s1][s2]);
					}
				}
			}
		}
	}
	return 0;
}

转载于:https://www.cnblogs.com/OYJason/p/9756775.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值