字节–魔法权值
一、题目描述
给出 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;
}