description

T次询问,每次问L,L+1...R有多少种子集满足子集中乘积为完全平方数。

solution

50pt

首先双倍经验
通常的思路是:平方数即每个质因子指数为偶
奇偶性有关问题用异或!
用二进制(位数大,这里用bitset)每个质因子代表一位,表示该质因子指数的奇偶性。
就相当于问所有数对应bitset异或起来为0的方案数。
线性基中的个数为c,方案数为(算空集)2nc
首先不在线性基里面的个数(nc)为自由元个数,也可以理解为线性基外的无论怎么选,都能用线性基里的唯一构造出异或和为0。(线性基外的选了,再选线性基里面异或和等于它的几个)。
因此完成了问题的转化。
回到这道题,暴力的50pt可以过了。一次插入复杂度π2(n)
当然可以优化,到π2(n)
每个数质因数分解最多只会有一个>n的质因子。
bitset和线性基只需要维护<=n的质数即可。
对于>n位的线性基,开一个unordered_map<质因子,对应bitset>。
挺妙的,感觉自己的大脑根本没有创造力!

100pt

50pt到100pt的桥梁只是一个结论:质因子nrl+1>n[l,r]中存在数含有质因子nn一定会被加入线性基
证明?
这样根号分治一下
1.len<=107,暴力线性基
2.len>107,枚举每个质数p判断[l,r]是否存在它的倍数(rpl1p)

贺的 code:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int SN=455;
typedef bitset<SN> bit;
typedef long long ll;
const int Sq=3200;
const int mod=998244353;
const int N=1e7+1;
bool is_p[N];
int p0[N],p[N],ptot,tot0;
void _xxs() {
	is_p[1]=1;
	for(int i=2;i<N;i++) {
		if(!is_p[i]) {
			if(i<=Sq)p0[++tot0]=i;
			p[++ptot]=i;
		}
		for(int j=1,x;j<=ptot&&(x=p[j]*i)<N;j++) {
			is_p[x]=1;
			if(i%p[j]==0)break;
		}
	}
//	printf("ptot = %d  tot0 = %d\n",ptot,tot0);
//	for(int i=1;i<=10;i++)printf("%d ",p0[i]);puts(""); 
}
int S;
bit a[SN];		//线性基 
unordered_map<int,bit> mp;
void Insert(int x) {
	bit v;
	for(int i=1;i<=tot0;i++) {
		if(x%p0[i])continue;
		int w=0;
		while(x%p0[i]==0) {x/=p0[i];w^=1;}
		if(w)v[i]=1;
	}
	if(x>1) {
		if(!mp.count(x)) {mp[x]=v;S++;return;}
		else {v^=mp[x];}
	}
	for(int i=tot0;i>=1;i--) {
		if(!v[i])continue;
		if(!a[i].any()) {a[i]=v;S++;return;}
		v^=a[i];
	}
}
ll ksm(ll x,ll y) {ll mul=1;for(;y;y>>=1,x=x*x%mod)if(y&1)mul=mul*x%mod;return mul;} 
void Clear() {S=0;for(int i=tot0;i>=1;i--)a[i].reset();mp.clear();}
int main() {
	_xxs();
	int T;scanf("%d",&T);
	while(T--) {
		Clear();
		int l,r;scanf("%d%d",&l,&r);
		int len=r-l+1;
		if(len<=7000) {
			for(int i=l;i<=r;i++) Insert(i);
		}
		else {
			for(int i=1;i<=ptot&&p[i]<=r;i++) {
				if(r/p[i]!=(l-1)/p[i]) {S++;}
			}
		}
		printf("%lld\n",ksm(2,len-S));
	}
	return 0;
}