E.恶心心的题
题意:
给一个序列 ai,q次询问,求每次LCM(al…ar,x)的值,对p取模。
思路:
- 先对每个数都唯一分解吧,考虑一下怎么求多个数的 lcm;举个例子
2 ^ 3 * 3 ^ 1* 5 ^ 7
2 ^ 2 * 3 ^ 2 * 5 ^ 3
2 ^ 1 *3 ^ 3 * 5 ^ 2
这三个数的 lcm 就是 2^3 * 3^3 *5^7 ,也就是各个质数指数的最大值了。因为总共只有60多个质数,可以用 60棵线段树维护最大值(还可以用 rmq)。
线段树做法:
#include<iostream>
#include<cstdio>
#include<map>
#include<math.h>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+20;
int isprime[301];
int pri[70];
int a[maxn][65],sum[maxn<<2][65],k,s[65];
ll poww(int a,int b){//快速幂
long long ans=1;
while(b>0){
if(b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
void sieve(){
for(int i=2;i<=300;i++){
if(isprime[i]==0){
for(int j=i*i;j<=300;j+=i){
isprime[j]=1;
}
pri[++k]=i;
}
}
}
void pushup(int rt,int x){
sum[rt][x]=max(sum[rt<<1][x],sum[rt<<1|1][x]);
}
void build(int l,int r,int rt,int x){
if(l==r){
sum[rt][x]=a[l][x];
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1,x);
build(m+1,r,rt<<1|1,x);
pushup(rt,x);
}
void query(int L,int R,int l,int r,int rt){
if(L<=l&&R>=r){
for(int i=1;i<=62;i++){
s[i]=max(s[i],sum[rt][i]);
}
return ;
}
int m=(l+r)>>1;
if(L<=m) query(L,R,l,m,rt<<1);
if(R>m) query(L,R,m+1,r,rt<<1|1);
}
int main (){
int p,q,x,n;
cin>>n>>p>>q;
sieve();
for(int i=1;i<=n;i++){
scanf("%d",&x);
for(int j=1;j<=62&&x!=1;j++){
int cnt=0;
while(x%pri[j]==0){
cnt++;
x/=pri[j];
}
a[i][j]=cnt;
}
}
for(int i=1;i<=62;i++){
build(1,n,1,i);
}
while(q--){
int l,r,x,ans;
ll anss=1;
scanf("%d%d%d",&l,&r,&x);
memset(s,0,sizeof(s));
query(l,r,1,n,1);
for(int j=1;j<=62;j++){
int cnt=0;
while(x%pri[j]==0){
cnt++;
x/=pri[j];
}
ans=max(s[j],cnt);
anss=anss*poww(pri[j],ans)%p;
}
printf ("%lld\n",anss*x%p);
}
return 0;
}
st表:
#include<iostream>
#include<cstdio>
#include<map>
#include<math.h>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+20;
int isprime[300];
int pri[70],n,ans[70],k;
short int a[65][maxn],dpma[65][maxn][17];
const int mod=1e9+7;
long long poww(long long a,long long b){//快速幂
long long ans=1;
while(b>0){
if(b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
void sieve(){
for(int i=2;i<=300;i++){
if(isprime[i]==0){
for(int j=i*i;j<=300;j+=i){
isprime[j]=1;
}
pri[++k]=i;
}
}
}
void init(){
for(int i=1;i<=62;i++){
for(int j=1;j<=n;j++){
dpma[i][j][0]=a[i][j];
}
}
for(int x=1;x<=62;x++){
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
dpma[x][i][j]=max(dpma[x][i][j-1],dpma[x][i+(1<<(j-1))][j-1]);
}
}
}
}
int qa(int x ,int l, int r){
int k=log((r-l+1)*1.0)/log(2.0);
return max(dpma[x][l][k],dpma[x][r-(1<<k)+1][k]);
}
int main (){
int p,q,x;
cin>>n>>p>>q;
sieve();
for(int i=1;i<=n;i++){
scanf("%d",&x);
for(int j=1;j<=62&&x!=1;j++){
if(x%pri[j]==0){
while(x%pri[j]==0){
a[j][i]++;
x/=pri[j];
}
}
}
}
init();
while(q--){
int l,r,x,ans;
ll anss=1;
scanf("%d%d%d",&l,&r,&x);
for(int j=1;j<=62;j++){
int cnt=0;
if(x%pri[j]==0){
while(x%pri[j]==0){
cnt++;
x/=pri[j];
}
}
int t=qa(j,l,r);
ans=max(t,cnt);
anss=anss*poww(pri[j],ans)%p;
}
printf ("%lld\n",anss*x%p);
}
return 0;
}
需要注意的:
- 线段树查询的时候,不能像st表一样查询60次,这样会t,仔细想一下线段树查询的实质,其实就是找出所有需要比较的节点的下标,如果查询60次,就会重复这个步骤。所以我们只要查询一次,每到达一个区间就维护一下60个数的最大值,用一个数组记录一下。
- st表会卡内存,所以用了 short int.
- 最后X的范围是 1e9 ,还能分解出300以外的质数,所以最后还要乘 x 分解出300以内的质数后的值。
ps:虽然hdw聚聚每次的题坑点都挺多的,但写完收获也很大,吹爆hdwdl.
I.摸鱼的tomjobs2
题意:
给n个数,每一个连续区间的按位与,作为一个交叉值,求有多少个不同的交叉值。
思路:
- 考虑以每一个数为左端点,考虑每一个二进制位,要想使得这个二进制位发生改变,就要找到右边第一个不为1的二进制位作为右端点,然后记录这一段的值。这个方法必须先用 st表预处理 区间值,至于为什么能用 st 表,聚聚告诉我这是个可覆盖的区间,豁然开朗。然后找右边第一个不为1的位置,虽然也可以预处理出来,但貌似不太会,,我用了 前缀和+二分。
- 考虑dp的思路,每次都往右扩展一位,把每次能到达的状态塞进 set 然后不断地转移。但set中的元素为什么不会超过 62个 本菜鸡始终没想明白。
法1:
#include<iostream>
#include<cstdio>
#include<map>
#include<math.h>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
map<ll,int >mp;
inline ll read(){
char c=getchar();
ll f=1,x=0;
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x*f;
}
ll a[maxn],dp[maxn][32];
int n,pre[64][maxn];
void init(){
for(int i=1;i<=n;i++) dp[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
dp[i][j]=dp[i][j-1]&dp[i+(1<<(j-1))][j-1];
}
}
}
ll qi(int l,int r){
int k=log((r-l+1)*1.0)/log(2.0);
return dp[l][k]&dp[r - (1 << k) + 1][k];
}
int main (){
int ans=0;
n=read();
for(int i=1; i<=n; i++){
a[i]=read();
if(mp[a[i]]==0){
mp[a[i]]=1;
ans++;
}
}
init();
for(int i=0;i<61;i++){
for(int j=1;j<=n;j++){
pre[i][j]=pre[i][j-1]+(((a[j]>>i)&1)==0);
}
}
for(int i=1; i<=n; i++){
for(int j=0;j<61;j++){
int l=i,r=n,mid,t=i-1;
while(l<=r){
mid=(l+r)/2;
if(pre[j][mid]-pre[j][i-1]==0) t=mid,l=mid+1;
else r=mid-1;
}
if(t<n) t++;
ll x=qi(i,t);
if(mp[x]==0) mp[x]=1,ans++;
}
}
printf ("%d\n",ans);
}
法2:
#include<iostream>
#include<cstdio>
#include<map>
#include<math.h>
#include<set>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
string s1,s2;
set < ll > dp[maxn];
ll a[maxn];
map<ll ,int >mp;
int main (){
int n,ans=0;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
dp[i].insert(a[i]);
if(!mp[a[i]]){
mp[a[i]]=1;
ans++;
}
}
for(int i=1;i<n;i++){
set<ll>::iterator it;
for(it=dp[i].begin();it!=dp[i].end();it++){
dp[i+1].insert(*it&a[i+1]);
if(!mp[*it&a[i+1]]){
mp[*it&a[i+1]]=1;
ans++;
}
}
}
printf ("%d\n",ans);
}
F.打扑克牌
题意:
给你一个长度为n的数字串,你可以任意打乱顺序,求有多少个不同的数字串可以被m整除。(n<=15,m<=50)
思路:
- 一开始想的裸的 dfs(状压没入门,可怜),交了一发 t 了,仔细想想 15! 不T 就怪了。然后又想到数字只能是 0-9,只要记录 0-9数字的数量,再进行 dfs 会有一些优化,比如 11111,就只会搜索一次,而原来要搜索 5! 次相同状态。然而还是会 t .
- 还是别挣扎了,还是需要记录状态呀,不然会 t 傻的。
- 状压dp 就是把状态用二进制压缩作为dp的状态。例如
大概是这样的
dp[3] 011 //代表你已经选了 第一个数和第二个数。
dp[2] 010 //代表选了第二个数
dp[1] 001 //代表选了第一个数
dp[3] 可以由 dp[2]和dp[1]转移
dp[2|(1<<0)]+=dp[2];
dp[1|(1<<1)]+=dp[1];
1.由 dp[2]转移相当于第一次选了第二个数第二次选第一个数。
2.由 dp[1]转移相当于第一次选了第一个数第二次选第二个数。
这个题需要再加一个模数的状态。
大概就是这样了
for(int i=0; i<(1<<n); i++) {
for(int j=0; j<n; j++){
if((i>>j)&1) continue; //表示第j个数已经选过了
for(int k=0; k<m; k++){
dp[i|(1<<j)][(k*10+a[j])%m]+=dp[i][k];
}
}
}
最后的答案就是 dp[(1<<n)-1][0],因为会出现重复的数,所以最后还要除以每个数重复次数的阶乘。
#include<iostream>
#include<cstdio>
#include<map>
#include<math.h>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
string s;
ll m,a[20],dp[1<<16][60],n,cnt[20];
ll num[20];
int main ()
{
cin>>s>>m;
n=s.size(),num[0]=1,dp[0][0]=1;
for(int i=0; i<n; i++)
a[i]=s[i]-'0',cnt[a[i]]++,num[i+1]=num[i]*(i+1);
for(int i=0; i<(1<<n); i++)
{
for(int j=0; j<n; j++)
{
if((i>>j)&1)
continue;
for(int k=0; k<m; k++)
{
dp[i|(1<<j)][(k*10+a[j])%m]+=dp[i][k];
}
}
}
ll ans=dp[(1<<n)-1][0];
for(int i=0;i<=9;i++){
ans/=num[cnt[i]];
}
printf ("%lld\n",ans);
}
最后放上一张偷来的图
也算是第一次写 状压dp 吧