形如求某一数字的倍数的方案数的题
例题:https://ac.nowcoder.com/acm/contest/95928/D
题意简析:在数组中选取两个数 \(a_i ,a_j\),使得两数乘积为495的倍数,同时可以进行一次(仅一次)的操作:使某个\(a_i\)加1,求出最大方案数
思路:通常遇到这种题目,需要对目标数进行质因数分解,分解后可以采用二进制表达质因子,同时也可以用二进制来查找所需的质因子
//通过质因子分解,可将495分解为3,3,5,11
//因此可以用四位二进制位来表示这四个质因子
#include<bits/stdc++.h>
#define endl '\n'
#define lowbit(x) (x&-x)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const double pi=acos(-1);
void solve(){
int n;cin>>n;
vector<int> a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
//二进制转换为标志数,用以确定当前已有/未有的因子
auto cg=[](int x){
int res=0;
if(x%3==0) res|=1;
if(x%9==0) res|=2;
if(x%5==0) res|=4;
if(x%11==0) res|=8;
return res;
};
//已有/未有的因子判断
auto ck=[&](int x,int y){
return (x|y)==15 || ((x|y)==13 && x&1 && y&1);//后面表示缺少一个3的情况
};
//对于例题,可以先维护在没有修改状态下的所有方案
//对于唯一一次修改,可以通过枚举数组+维护前后缀来快速求出修改后对方案数的影响
ll ans=0;
vector<int> val(n+1);
vector pre(n+1,vector<int>(20)),suf(n+2,vector<int>(20));
//维护前缀,同时求出无修方案数
for(int i=1;i<=n;i++){
int t=cg(a[i]);
val[i]=t;
for(int j=0;j<20;j++) if(ck(j,val[i])) ans+=pre[i-1][j];
pre[i]=pre[i-1];
pre[i][val[i]]++;
}
//维护后缀
for(int i=n;i>=1;i--){
suf[i]=suf[i+1];
suf[i][val[i]]++;
}
ll mx=0;
//枚举修改位置,通过前后缀求出最终方案数,同时求最大值
for(int i=1;i<=n;i++){
ll res=0;
int t=a[i];
t++;
int tem=cg(t);
for(int j=0;j<20;j++) if(ck(j,tem)) res+=pre[i-1][j];
for(int j=0;j<20;j++) if(ck(j,tem)) res+=suf[i+1][j];
for(int j=0;j<20;j++) if(ck(j,val[i])) res-=pre[i-1][j];
for(int j=0;j<20;j++) if(ck(j,val[i])) res-=suf[i+1][j];
mx=max(mx,res);
}
cout<<mx+ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}