CSUST2020集训队选拔赛题解

E.恶心心的题

题意:
给一个序列 ai,q次询问,求每次LCM(al…ar,x)的值,对p取模。
思路

  1. 先对每个数都唯一分解吧,考虑一下怎么求多个数的 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;
}

需要注意的

  1. 线段树查询的时候,不能像st表一样查询60次,这样会t,仔细想一下线段树查询的实质,其实就是找出所有需要比较的节点的下标,如果查询60次,就会重复这个步骤。所以我们只要查询一次,每到达一个区间就维护一下60个数的最大值,用一个数组记录一下。
  2. st表会卡内存,所以用了 short int.
  3. 最后X的范围是 1e9 ,还能分解出300以外的质数,所以最后还要乘 x 分解出300以内的质数后的值。

ps:虽然hdw聚聚每次的题坑点都挺多的,但写完收获也很大,吹爆hdwdl.

I.摸鱼的tomjobs2

题意
给n个数,每一个连续区间的按位与,作为一个交叉值,求有多少个不同的交叉值。
思路

  1. 考虑以每一个数为左端点,考虑每一个二进制位,要想使得这个二进制位发生改变,就要找到右边第一个不为1的二进制位作为右端点,然后记录这一段的值。这个方法必须先用 st表预处理 区间值,至于为什么能用 st 表,聚聚告诉我这是个可覆盖的区间,豁然开朗。然后找右边第一个不为1的位置,虽然也可以预处理出来,但貌似不太会,,我用了 前缀和+二分。
  2. 考虑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)

思路:

  1. 一开始想的裸的 dfs(状压没入门,可怜),交了一发 t 了,仔细想想 15! 不T 就怪了。然后又想到数字只能是 0-9,只要记录 0-9数字的数量,再进行 dfs 会有一些优化,比如 11111,就只会搜索一次,而原来要搜索 5! 次相同状态。然而还是会 t .
  2. 还是别挣扎了,还是需要记录状态呀,不然会 t 傻的。
  3. 状压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 吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值