提高组模拟 5.2

提高组模拟 5.2

时间安排

T1

8:20开始,30分钟连读题带写完,测完样例觉得没啥问题就走了。

T2

刚读完题没想法,估到有30分暴力能拿,先跳了,10点半回来写完了暴力。

T4

8:50-10:30 读完题觉得可做,从dfs的时间到分解质因数一部分一部分开始试,整体都在写搜索,思路方面接近正解了。但直到发现样例情况我会多算2后直接破防,回头看T2。

10点半到结束就在T2,T4之间来回跳跃,破大防了。

最后发现大家都挂分了,分数上好像还没那么丢人。但T2不会做正解有点丢人。

反思

T2

考虑到了前缀和,但是没有想到不同字符之间做差分,按理说写一下就能看出来的性质没有想到。

T4

考虑到了搜索,但那个126位的状压确实是没想到,正经人谁写这么奇怪的算法啊

部分题解

T2 魔法

题目要求是求一个字符串中出现过的各个元素出现次数相同的字串个数。

我们假设这个字符串的字符集大小是\(s\)\(cnt_{i,j}\)是从\(1 \sim i\)\(j\)的出现次数,那么就有

\[\forall i \in [1,s],\ cnt_{i,r}-cnt_{i,l-1}=const \]

也就是说

\[\forall i \in [2,s],\ cnt_{i,r}-cnt_{i,l-1}=cnt_{1,r}-cnt_{1,l-1} \\cnt_{i,r}-cnt_{1,r}=cnt_{i,l-1}-cnt_{1,l-1} \]

这样的话,我们只需要把每一位看成一个\(s\)元组,每次更新的时候加上在这之前相同的\(s\)元组出现次数,\(s\)元组可以通过哈希映射到一个数上去。

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int mn=1e5+7,mod=1e9+7,val=300007,mod2=1e9+9;
int a[mn],n,vis[60],cnt,pre[mn][60];
LL po[60],hs1,hs2,ans;
map<pair<LL,LL>,int> to;
char s[mn];

void Hash(int pos)
{
    hs1=hs2=0;
    for(int i=2;i<=cnt;++i) {
        hs1=hs1*val%mod;
        hs1=(hs1+pre[pos][i]-pre[pos][1])%mod;
        hs2=hs2*val%mod2;
        hs2=(hs2+pre[pos][i]-pre[pos][1])%mod2;
    }
    ans+=to[make_pair(hs1,hs2)];
    to[make_pair(hs1,hs2)]++;
}

int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
//    po[0]=1;
//    for(int i=1;i<60;++i) po[i]=po[i-1]*val%mod;
    for(int i=1;i<=n;++i) {
        if(s[i]>='a'&&s[i]<='z') a[i]=s[i]-'a';
        else a[i]=s[i]-'A'+26;
    }
    for(int i=1;i<=n;++i) {
        if(!vis[a[i]]) vis[a[i]]=++cnt;
        a[i]=vis[a[i]];
    }
    if(cnt==1) {
        printf("%lld\n",(1ll*n*(n+1)/2)%mod);
        return 0;
    }
    to[make_pair(0,0)]++;
    for(int i=1;i<=n;++i) {
        for(int j=1;j<=cnt;++j) pre[i][j]=pre[i-1][j];
        pre[i][a[i]]++;
        Hash(i);
    }
    cout<<ans%mod;
}

T4 たのしいたのしいたのしい家庭菜園

题目要求是选取一个数n的约数构成集合,满足当前集合与新加入的数最多一个不互质。计算满足条件集合个数。

首先每个约数是什么和约数的唯一分解中的次数对这个题并没有影响,所以我们可以只考虑\(2^{siz}-1\)种数,\(siz\)是质因数个数。

对于每一种数,与它不互质的数最多出现两次,本身一次,其他数一次。我们可以通过一个三进制数表示不同的这些情况。一个技巧是用四进制,这样的话就可以用两位二进制去存一种情况,省去三进制的表示,便于代码书写。

所以我们就可以开始搜索了,考虑从当前位置向下搜,枚举\(1 \sim 2^{siz}-1\),通过二进制的操作看这一位当前的出现次数,小于2的话说明可以加入,然后更新所有相关情况,用更新完的新状态向下搜索。另外就是要用记忆化把答案记下来。

#include<bits/stdc++.h>
#define LL long long
#define cnt po[0]
using namespace std;

const int mod=1e9+7;
LL n,t;
LL ct[100],po[10];
map<__uint128_t,int> vis;

int dfs(__uint128_t x)
{
    if(vis.count(x)) return vis[x];
    int res=1;
    __uint128_t tmp=x;
    for(int i=1;i<(1<<cnt);++i) {
        int s=(x>>(i<<1))&3;
        if(s>1) continue;
        for(int j=1;j<(1<<cnt);++j) {
            if(!(i&j)) continue;
            s=(x>>(j<<1))&3;
            if(s<2) x+=(__uint128_t(1)<<(j<<1));
        }
        res=(res+dfs(x)*ct[i]%mod)%mod;
        x=tmp;
    }
    return vis[x]=res;
}

int main()
{
    scanf("%lld",&n);
    t=n;
    for(int i=2;1ll*i*i<=t;++i) {
        if(t%i==0) {
            cnt++;
            while(t%i==0) t/=i,po[cnt]++;
        }
    }
    if(t!=1) po[++cnt]=1;
    for(int i=1;i<(1<<cnt);++i) {
        ct[i]=1;
        for(int j=0;j<cnt;++j) if((i>>j)&1) ct[i]=ct[i]*po[j+1]%mod;
    }
    cout<<(dfs(0)+mod-1)%mod;
}
posted @ 2021-05-02 20:05  tianyy  阅读(65)  评论(0)    收藏  举报