篮球

在这里插入图片描述
换一个理解方式:

有一个由N(N<=1000)个数字组成的序列,我们每次取出两个子序列S1和S2,满足以下条件:

1.每个数字只出现一次,可以放在S1或者S2里,也可以都不放,但S1和S2不能为空,且S2中的任意一个数字,在原序列中的位置必须在S1任何一个数字之后。

2.S1中所有数字异或(^)和必须等于S2中所有数字的与(&)和。

3.对于一组{S1,S2},当且仅当其中任意一个的数字有变化时,为一组新的{S1,S2},两个数字不相同当且仅当他俩在原序列中位置不相同 。

求{S1,S2}的种类数

这题首先明确搜出所有{S1,S2}是不理智的。
那么我们根据条件2转变思路,我们搜出 异或和 和 与和 为K(K<=1024)的S1和S2各有多少个,相乘不就是我们所要的方案数吗。
于是就有了一种类似折半爆搜的写法
我们先按题目所说的把原序列分为两部分,在前半部分搜S1,在后半部分搜S2,这样保证了条件1成立。
搜出来的结果我们对K(=0~1024)暴力累加。
因为分法有N-1种,所以跑N-1次即可。
听起来挺棒的,预计得分:0 。

爆搜既然不行,我们思考能否换一种方式求出所需的方案数
我考场上想了挺久,最后脑子里突然蹦出了四个字
异或背包!
这东西好像不叫背包……不过实现过程和背包差不多
我们定义f(I,j)表示前i个数中,异或和为j的选数方案有多少种
那么对于一个j,有三种转移方式
1.由f[ i-1 ][ j ^ num[i] ]转移来,因为j ^ num[i] ^ num[i]==j
2.由f[ i-1 ][ j ]转移来,代表着不选num[i]
3.特殊情况,f[ i ][ num[i] ]转移前应++,代表着只选这一个数,因为方程式不包含前面一个不选的情况

	for(int i=1;i<=n;i++){
		f[i][val[i]]++;
		for(int j=0;j<=1024;j++)
			f[i][j]=(f[i-1][j^val[i]]+f[i][j])%mod;
		for(int j=0;j<=1024;j++)f[i][j]=(f[i-1][j]+f[i][j])%mod;
	
	for(int i=n;i>=1;i--){
		g[i][val[i]]++;
		for(int j=0;j<=1024;j++)
			g[i][j&val[i]]=(g[i][j&val[i]]+g[i+1][j])%mod;
		for(int j=0;j<=1024;j++)g[i][j]=(g[i][j]+g[i+1][j])%mod;
	

愉快的写完DP加统计,发现连样例2都过不去,没事,别着急,想一想是哪里出现了问题

我们回想一下条件三,{S1,S2}不同的条件是什么,有数字不同
再想想我们找{S1,S2}的方法,虽然一定满足S2>S1
但是一定保证{S1,S2}不一样吗

当然不!
我们举个栗子(滚回样例2)。

1,2,3,3
如果分为1,2,3 | 3 有一种方式是{ {1,2} , {3} }
如果分为1,2 | 3,3 有两种,分别为{ {1,2} , {3} }和{ {1,2} , {3} }
最后一个三被是不是被重复选取了?

如何解决呢?
我们考虑统计方式的问题
我是按1到n-1的顺序枚举的
那么在红色和(绿色+蓝色)时,红色部分和绿色部分的贡献已经统计过了
那么我们继续枚举断点,我们在统计(红色+蓝色)和绿色时,红色和绿色的部分会被重复统计一次,如何解决呢,我们直接减去就好了

	for(int i=1;i<=n-1;i++){
		for(int j=0;j<=1024;j++){
			ans=(ans+1ll*f[i][j]*g[i+1][j]-1ll*f[i-1][j]*g[i+1][j])%mod;
		}

这题差不多完结了,完整代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib> 
#include<queue>
using namespace std;
const int maxn = 2124;
const int mod = 1000000007;
int n,val[maxn],f[maxn][maxn],g[maxn][maxn];
long long ans;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&val[i]);
	for(int i=1;i<=n;i++){
		f[i][val[i]]++;
		for(int j=0;j<=1024;j++)
			f[i][j]=(f[i-1][j^val[i]]+f[i][j])%mod;
		for(int j=0;j<=1024;j++)f[i][j]=(f[i-1][j]+f[i][j])%mod;
	}
	for(int i=n;i>=1;i--){
		g[i][val[i]]++;
		for(int j=0;j<=1024;j++)
			g[i][j&val[i]]=(g[i][j&val[i]]+g[i+1][j])%mod;
		for(int j=0;j<=1024;j++)g[i][j]=(g[i][j]+g[i+1][j])%mod;
	}
	for(int i=1;i<=n-1;i++){
		for(int j=0;j<=1024;j++){
			ans=(ans+1ll*f[i][j]*g[i+1][j]-1ll*f[i-1][j]*g[i+1][j])%mod;
		}
	}
	printf("%lld",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值