【Codeforces 176B】 Word Cut

题目链接

题目大意

给定一个字符串,给出一种操作为,选定一个字符(第一个除外),将从这个字符开始到字符串最后的这一部分子串放到字符串的最前面。
给出两个串,问经过k次操作有多少种方案能够从第一个串得到第二个串。

题解

这个操作其实就是把串看成一个环,每次选一个字符来当头。
设字符串长度为n,那么我们通过操作只可能得到n个子串。考虑除了原串外其他串经过若干次操作能够被得到的方案数都是一样的,定义 fi,0 为经过i次操作能够得到原串的方案数, fi,1 为经过i次操作能够得到其他串的方案数。
那么:

fi,0=fi1,1×(n1)
fi,1=fi1,0+fi1,1(n2)

再考虑第二个串能在多少位置和第一个串完全匹配(环状)答案就是多少倍的 fk,1 。特殊的,如果第二个串和第一个串完全相同,即在第一个位置就完全匹配,那么加上的是 fk,0

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

#define rep(i, l, r) for(int i = (l); i <= (r); i++)

typedef long long ll;
const int N = 1000 + 10, mod = 1000000000 + 7;
char s[N], t[N];
int k, n;
ll f[2][2], ans;

void init(){
    scanf("%s%s%d", s, t, &k);
    n = strlen(s);
}

void work(){
    f[0][0] = 1;
    rep(i, 1, k){
        f[i&1][0] = (f[i&1^1][1] * (n - 1)) % mod;
        f[i&1][1] = (f[i&1^1][0] + f[i&1^1][1] * (n - 2)) % mod;
    }
    int ok;
    rep(i, 0, (n - 1)){
        ok = 1;
        rep(j, 0, (n - 1))
            if(s[(j+i)%n] != t[j]){
                ok = 0;
                break;
            }
        if(ok)
            if(i == 0) ans += f[k&1][0];
            else ans += f[k&1][1];
        ans %= mod;
    }
    cout<<ans<<endl;
}

int main(){
    init();
    work();
    return 0;
}

upd:
将f的计算过程改为矩阵快速幂,两串的匹配过程改为hash,可通过字符串长度 106 k1012 的数据。

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

#define rep(i, l, r) for(register int i = (l); i <= (r); i++)
#define per(i, r, l) for(register int i = (r); i >= (l); i--)
#define ll long long
#define ull unsigned long long
const int N = 1000 + 10, mo = 1000000000 + 7;
char s[N], t[N];
int k, n;
ll ans;
ull pw[N], hs[N], ht[N];

struct M{
    ll dat[2][2];
    M(){memset(dat, 0, sizeof(dat));}
}f, e;

void mul(M &a, M &b){
    M ret;
    rep(i, 0, 1) rep(j, 0, 1) rep(k, 0, 1)
        ret.dat[i][j] = (ret.dat[i][j] + a.dat[i][k] * b.dat[k][j]) % mo;
    rep(i, 0, 1) rep(j, 0, 1) a.dat[i][j] = ret.dat[i][j];
}

void poww(M &a, int b){
    M ret;
    ret.dat[0][0] = ret.dat[1][1] = 1;
    while(b){
        if(b & 1) mul(ret, a);
        mul(a, a);
        b >>= 1;
    }
    rep(i, 0, 1) rep(j, 0, 1) a.dat[i][j] = ret.dat[i][j];
}

void cal(ll &a, ll &b){
    rep(i, 0, n - 1)
        if(hs[i] + (hs[0] - hs[i] * pw[i]) * pw[n-i] == ht[0])
            if(!i) a++;
            else b++;
}

void init(){
    scanf("%s%s%d", s, t, &k);
    n = strlen(s);
    pw[0] = 1;
    rep(i, 1, n) pw[i] = pw[i-1] * 27;
    hs[n-1] = s[n-1];
    ht[n-1] = t[n-1];
    per(i, n - 2, 0){
        hs[i] = hs[i+1] * 27 + s[i];
        ht[i] = ht[i+1] * 27 + t[i];
    }
    f.dat[0][0] = 1;
    e.dat[0][1] = n - 1;
    e.dat[1][0] = 1, e.dat[1][1] = n - 2;
}

void work(){
    poww(e, k);
    mul(e, f);
    ll a(0), b(0);
    cal(a, b);
    cout<<(a * e.dat[0][0] % mo + b * e.dat[1][0] % mo) % mo << endl;
}

int main(){
    freopen("176b.in", "r", stdin);
    freopen("176b.out", "w", stdout);
    init();
    work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值