您的位置:首页 > 其它

codevs1376帕秋莉•诺蕾姬 东方幻想乡系列模拟赛Stage 2[三星]

2015-10-24 13:00 323 查看
题目很老但是很经典~

题目链接:—————————————->题目地址

题目描述 Description

在幻想乡,帕秋莉•诺蕾姬是以宅在图书馆闻名的魔法使。这一天帕秋莉又在考虑如何加强魔法咒语的威力。帕秋莉的魔法咒语是一个仅有大写字母组成的字符串,我们考虑从’A’到’Z’分别表示0到25的数字,于是这个魔法咒语就可以看作一个26进制数。帕秋莉通过研究发现,如果一个魔法咒语所代表的数能够整除10进制数M的话,就能够发挥最大的威力。若当前的魔法咒语并不能整除M,帕秋莉只会将其中两个字符的位置交换,尽量让它能够被M整除,当然由于某些咒语比较特殊,无论怎么改变都不能达到这个目的。请你计算出她能否只交换两个字符就让当前咒语被M整除。(首位的’A’为前导0) 第1行:1个字符串,长度不超过L。 第2行:1个正整数,M 第1行:用空格隔开的2个整数,输出时先输位置靠前的那个。 如果存在多种交换方法,输出字典序最小的,比如1 3和1 5都可以达到目的,就输出1 3;1 3和2 4都行时也输出1 3。注意字符串下标从左到右依次为1到L开始。如果初始魔法咒语已经能够整除M,输出”0 0”;若无论如何也不能到达目的输出”-1 -1”。 PATCHOULI 16 4 9 对于30%的数据:1 <= L <= 10, 1 <= M <= 100 对于50%的数据:除前面30%外,1 <= L <= 500, M = 5或25或26 对于100%的数据:1 <= L <= 2,000, 1 <= M <= 200,000

输入描述 Input Description

第1行:1个字符串,长度不超过L。 第2行:1个正整数,M

输出描述 Output Description

第1行:用空格隔开的2个整数,输出时先输位置靠前的那个。 如果存在多种交换方法,输出字典序最小的,比如1 3和1 5都可以达到目的,就输出1 3;1 3和2 4都行时也输出1 3。注意字符串下标从左到右依次为1到L开始。如果初始魔法咒语已经能够整除M,输出”00”;若无论如何也不能到达目的输出”-1-1”。

样例输入 Sample Input

PATCHOULI 16

样例输出 Sample Output

4 9

数据范围及提示 Data Size & Hint

对于30%的数据:1 <= L <= 10, 1 <= M <= 100 对于50%的数据:除前面30%外,1 <= L <= 500, M = 5或25或26 对于100%的数据:1 <= L <= 2,000, 1 <= M <= 200,000

这题的简要题目描述:

对于给定的字符串,转化为 26 进制数字,交换其中两个字母的位置,使得这个数字

能被 M 整除

.

.

.

.

.

题解

(接下来的’^’符号一定是次方运算不是异或)

这题看完题目很容易想出来n^2*l复杂度的算法,即,枚举两端点,每次暴力求sum,然后暴力验证取模,这样复杂度是n^2*l。这是30分算法。

然后对于剩下的50,m<= 26;乱搞一下~,不过不如直接打100分算法。

对于100分算法,我们这样想,26进制向10进制转换,也就是将第i位的数乘上26^(i-1),于是我们可以先处理出来一个num数组,保存的是每一位的数(如ABC,num[1] = 0,num[2] = 1,num[3] = 2)这样的,这里还要特别注意,这是一个类似高精的读入,也就是第一个读入的是最高位……

然后我们再处理一个v数组,表示v是第i位需要乘的数(可以不处理,但是打起来比较麻烦不如直接预处理一下,并且还不用打快速幂),递推关系式:

[code]    v[1] = 1%m;
    v[2] = pr%m;
    for(int i = 3;i <= 2300;i ++)
    {
        v[i] = (v[i-1]%m * pr%m)%m;
    }


并且这里要注意的是,这里处理的v是在%m意义下的,因为完整的v数组存不下来,又有取模的性质,所以可以直接在这里取模,而且,为了保险还是要每步都取模。

这时候,对初始序列处理sum,到这里都和暴力算法没什么区别,但是,下面的枚举过程暴力是对于每一次交换都暴力求和,这里可不可以优化一下呢?

既然每次只交换两个数,那么这两个数对答案造成影响,而其他数不会,那么我们可以理解成:先把这两位掏空,然后再填上两个新的数。

于是就有转移:sum-num[i] * v[j]-num[j] * v[j] + num[i] * v[j] + num[j] * v[i];即

[code]    for(int i = 1;i <= l;i ++)
    {
        for(int j = 1;j <= l;j ++)
        {
            if(i == j)  continue;
            sum = sum - num[i]*v[i] - num[j]*v[j] + num[i]*v[j] + num[j]*v[i];
        }
    }


这个过程就是掏空重填的过程。

然后每次交换后检查一下是否满足%m == 0,如果满足就加到新的数组里,这是因为要求字典序最小,当然你也可以从小到大枚举。

最后,输出最小值,不满足就-1 -1~

暴力还是很容易想的~,后面的思路也不是很难,但是有一定思维难度~多思考~思维的乐趣~

全部代码:

[code]#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int size = 2333;
char s[size];
ll m;
int num[size];
ll v[size];
ll sum;
int tot;
struct Poi{int l,r;}pre[size];
bool cmp(Poi a,Poi b)
{
    return a.l == b.l ? a.r < b.r : a.l < b.l;
}
int main()
{
    ll pr = (ll)(26);
    scanf("%s%lld",s+1,&m);
    int l = strlen(s+1);
    for(int i = l;i > 0;i --)
    {
        num[l-i+1] = s[i]-'A';
    }
    v[1] = 1%m;
    v[2] = pr%m;
    for(int i = 3;i <= 2300;i ++)
    {
        v[i] = (v[i-1]%m * pr%m)%m;
    }
    for(int i = 1;i <= l;i ++)
    {
        sum += (ll)(num[i])*v[i];
    }
    if(sum % m == 0)
    {
        puts("0 0");
        return 0;
    }
    ll tmp = sum;
    for(int i = 1;i <= l;i ++)
    {
        for(int j = 1;j <= l;j ++)
        {
            if(i == j)  continue;
            sum = sum - num[i]*v[i] - num[j]*v[j] + num[i]*v[j] + num[j]*v[i];
            if(sum % m == 0)
            {
                int p = i,q = j;
                if(p < q)   swap(p,q);
                p = l - p + 1;
                q = l - q + 1;
                pre[++tot].l = p;
                pre[tot].r = q;
            }
            sum = tmp;
        }
    }
    if(tot)
    {
        sort(pre+1,pre+1+tot,cmp);
        printf("%d %d",pre[1].l,pre[1].r);
        return 0;
    }
    puts("-1 -1");
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: