字节--魔法权值

字节–魔法权值

一、题目描述

给出 n 个字符串,对于每个 n 个排列 p,按排列给出的顺序(p[0] , p[1] … p[n-1])依次连接这 n 个字符串都能得到一个长度为这些字符串长度之和的字符串。所以按照这个方法一共可以生成 n! 个字符串。

一个字符串的权值等于把这个字符串循环左移 i 次后得到的字符串仍和原字符串全等的数量,i 的取值为 [1 , 字符串长度]。求这些字符串最后生成的 n! 个字符串中权值为 K 的有多少个。

注:定义把一个串循环左移 1 次等价于把这个串的第一个字符移动到最后一个字符的后面。

  • 输入描述:
每组测试用例仅包含一组数据,每组数据第一行为两个正整数 n, K , n 的大小不超过 8 ,
 K 不超过 200。接下来有 n 行,每行一个长度不超过 20 且仅包含大写字母的字符串。
  • 输出描述:
输出一个整数代表权值为 K 的字符串数量。


输入例子1:
3 2
AB
RAAB
RA

输出例子1:
3

二、分析

如果你发现题目读的比较别扭,难懂,请不要怀疑题错了,原题就是这样!

  • 我们先理解一下题意:
  • 首先理解一下对于每个 n 个排列 p这句话什么意思,其实就是n个字符串的所有排列组合情况而已,只不过其中不是一个字符,而是一个字符串而已
  • 其次一个字符串的权值等于把这个字符串循环左移 i 次后得到的字符串仍和原字符串相等的数量
  • 现在题目当中给出了n个字符串,和一个权值K,求满足权值K的字符串的数量,相当于在字符串所有的全排列的情况中找权值为K的字符串的数量
  • 先整个例子理解一下:

第一行输入 3 2,即 n = 3, K = 2
紧接着是 n 行输入:
AB
RAAB
RA
设 一个字符串循环左移 i 次后 得到的字符串仍和原字符串全等 情况设为 is_eq_after_move(i) , 其中 i 的取值为 [1, 字串长度]
K = count (is_eq_after_move(i) ), 即这些情况的总数。
因为 i 最大值可以取到 字符长度, 所以 K 的最小值是 1。
K 的含义基本清楚了,那字符串有如何生成?
根据题意 字符串会有 n! 个情况,能将 n 个字符串组装成 n! 个 新的 字符串,想到的只有 全排列 了 。可以根据 输入输出例子 验证这个猜测。
验证如下:
n = 3, K = 2
全排列:
AB RAAB RA (偏移后和原串相等的偏移量 i = 4, 8) ,则 K = count(i) = 2
AB RA RAAB (偏移量 i = 8), 则 K = count(i) = 1
RAAB AB RA ( 偏移量 i = 8), 则 K = count(i) = 1
RAAB RA AB ( 偏移量 i = 4, 8), 则 K = count(i) = 2
RA AB RAAB ( 偏移量 i = 4, 8), 则 K = count(i) = 2
RA RAAB AB ( 偏移量 i = 8), 则 K = count(i) = 1
输出 K == 2 的数量: count(K == 2) = 3
正是 输出例子给出的数字 3。

  • 至此理解题意了。 接下来只需搞定, 生成全排列 的 算法,和 验证 偏移 offset 个字符 后和原串相等 的算法 就可以了。

三、代码

#include <iostream>
#include <vector>
#include <assert.h>
#include <cstring> // for strncmp
#include <algorithm> // for std::next_permutation
using namespace std;
 
bool is_eq_after_move(const string& str, const size_t offset) 
{
    const size_t len = str.size();
    if (offset == len) 
    	return true;
     
    assert(offset >= 1 && offset < len);
    // 左移 offset 位数后,与原串相等的情况之一:
    //      每 offset 个数据块 都要相等
    //      所以 这种情况 的字符串,长度首先必须是 offset 的 整数倍。
    //
    // 情况之二,串长不是 offset 的 整数倍,像这种: ABABAB , offset = 4
    // 这种情况可以用递归来化解:
    if (len % offset != 0) 
    {  
        if (len % offset > len / 2) 
        	return false; // 加上此句可减少复杂度
        return is_eq_after_move(str, len % offset);
    }
     
    // 程序能走到这的都是 之前提到的 情况一:
    char* s = (char*)&str[0]; // 先指向首地址
    for (size_t loop = 0, max_loop = len / offset - 1;loop < max_loop; ++loop) 
    {
        if ( 0 != strncmp(s, s + offset, offset) ) 
        {
            return false;
        }
        s += offset;
    }
    return true;
}
 
void get_ret(size_t& ret, const int* pos, const size_t size,
             const vector<string>& input, const size_t K) 
{
    size_t count = 1; // 自身整体移动算一个
    string newstring;
    for (size_t i = 0; i < size; ++i) 
    {
        newstring += input[pos[i]];
    }
 
    const int len = newstring.size();
    for (int offset = 1; offset < len; ++offset) 
    {
        // 只判断 和第一个相等的字符即可
        if (newstring[offset] == newstring[0]) 
        {
            if (is_eq_after_move(newstring, offset)) 
            {
                count++;
            }
        }
    }
 
    if (count == K) 
    {
        ret++;
    }
}
 
int main() 
{
    size_t n, K;
    cin >> n >> K;
    vector<string> input(n);
    size_t newstrlen = 0;
    for (size_t i = 0; i < n; ++i) 
    {
        cin >> input[i];
        newstrlen += input[i].size();
    }
     
    // 生成 0 ~ n-1 的全排列
    int pos[n];
    for (int i = 0; i < n; ++i) 
    	pos[i] = i;
 
    size_t ret = 0;
    do 
    {
        get_ret(ret, pos, n, input, K);
    } while (std::next_permutation(pos, pos + n));
 
    cout << ret << endl;
    return 0;
}
  • std::next_permutation解释
  • C++ STL中提供了std::next_permutation与std::prev_permutation可以获取数字或者是字符的全排列,其中std::next_permutation提供升序、std::prev_permutation提供降序
  • std::next_permutation函数原型

template <class BidirectionalIterator>

bool next_permutation (BidirectionalIterator first, 
										BidirectionalIterator last );

template <class BidirectionalIterator, class Compare>

bool next_permutation (BidirectionalIterator first,
							BidirectionalIterator last, Compare comp);
  • 说明:next_permutation,重新排列范围内的元素[第一,最后一个)返回按照字典序排列的下一个值较大的组合。
  • 返回值:如果有一个更高的排列,它重新排列元素,并返回true;如果这是不可能的(因为它已经在最大可能的排列),它按升序排列重新元素,并返回false。
  • 如果不想要STL提供的,自己也可以写一个全排列
#include <iostream>
#include <string>
#include <vector>
 
using namespace std;
vector<string> strs;//存储输入的字符串
vector<int> flag;//存储全排列标记信息
int n, k;
int count;//计数
 
//全排列 + 解决问题
//pos代表当前是第pos位
//sequence代表全排列的临时数组
void backTracking(int pos, vector<int> &sequence)
{
	//如果相等代表已经找到一种全排列的方式,就需要判断是否权值的要求
    if(pos == n)
    {
        string s;
        
        //链接在一起构成新的字符串
        for(int i = 0; i < n; i++)
            s += strs[sequence[i]];
 
        string tmps = s;

		//权值计数器
        int lcount = 1;
        //判断左移[1,length - 1]的权值情况
        for(int i = 0; i < s.length() / 2; i++)
        {
            tmps = tmps.substr(1) + tmps[0];
            if(tmps == s)
            {
                lcount = s.length() / (i + 1);
                break;
            }
        }

		//如果权值计数器和题目要求的权值K相等代表改种字符串排列算作一种情况,++即可
        if(lcount == k)
            count++;
        return;
    }
    //全排列代码,就是回溯
    for(int i = 0; i < n; i++)
    {
        if(flag[i])
        {
        	//做选择
            sequence.push_back(i);
            flag[i] = false;
            backTracking(pos + 1, sequence);
            flag[i] = true;
            //撤销选择
            sequence.resize(sequence.size() - 1);
        }
    }
}
 
int main()
{
    count = 0;
    cin>>n>>k;
    strs.resize(n);
    flag.resize(n);
    vector<int> seq;
     
    for(int i = 0; i < n; i++)
    {
        cin>>strs[i];
        flag[i] = true;
    }
    backTracking(0, seq);
    cout<<count<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/106739147