【背包 组合】P7552 [COCI 2020/2021 #6] Anagramistica|普及+

本文涉及知识点

组合数学汇总
C++背包问题

P7552 [COCI 2020/2021 #6] Anagramistica

题目描述

Biljana 喜欢出字谜游戏。

如果一个单词可以由另一个单词交换字母顺序得到,则称它们是「相似」的。

现在,她有 n n n 个单词。她希望选出一些单词,使得其中恰好有 k k k 对单词是「相似」的。请你帮她计算可行的方案数,对 10 9 + 7 10^9 + 7 109+7 取模。

输入格式

第一行两个整数 n n n, k k k。

接下来 n n n 行,每行一个字符串,表示一个单词。

输出格式

一行一个整数,表示可行的方案数,对 10 9 + 7 10^9 + 7 109+7 取模。

输入输出样例 #1

输入 #1

复制代码
3 1
ovo
ono
voo

输出 #1

复制代码
2

输入输出样例 #2

输入 #2

复制代码
5 2
trava
vatra
vrata
leo
ole

输出 #2

复制代码
3

输入输出样例 #3

输入 #3

复制代码
6 3
mali
lima
imal
je
sve
ej

输出 #3

复制代码
6

说明/提示

样例 1 解释

恰含有一对「相似」的单词的方案为 ovo, ono, vooovo, voo


数据规模与约定

本题采用捆绑测试

Subtask 分值 数据规模与约定
1 1 1 10 10 10 1 ≤ n ≤ 15 1 \le n \le 15 1≤n≤15
2 2 2 30 30 30 0 ≤ k ≤ 3 0 \le k \le 3 0≤k≤3
3 3 3 70 70 70 无附加约定

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 2 × 10 3 1 \le n \le 2 \times 10^3 1≤n≤2×103, 0 ≤ k ≤ 2 × 10 3 0 \le k \le 2 \times 10^3 0≤k≤2×103,单词的长度不超过 10 10 10 且仅含小写字母。


说明

本题分值按 COCI 原题设置,满分 110 110 110

题目译自 COCI2020-2021 CONTEST #6 T3 Anagramistica

背包 组合

对各字符串的字符按升序排序。枚举各字符串s。哈希映射mCnt[s]++。

动态规划的状态表示

dp[i][k], 处理完mCnt的前i个元素,相似对数是k的方案数。 i ∈ [ 0 , N 1 ] , k ∈ [ 0 , k ] i\in[0,N1],k\in[0,k] i∈[0,N1],k∈[0,k]。空间复杂度:O(NK) ,N1= mCnt.size()

动态规划的填报顺序

i从到大,k从小到大。

动态规划的转移方程

dp[i+1][k+k1] += dp[i][k] × C n 2 i 1 \times C_{n2}^{i1} ×Cn2i1 j1\\in\[1,n2\] , n 2 是 m C n t 第 i 个元素的数量。 ,n2是mCnt第i个元素的数量。 ,n2是mCnt第i个元素的数量。k1 = i1 \\times (i1-1)/2

dp[?][k]到dp[][k+k1]的时间复杂度是n2之后,即N。故总时间复杂度是:O(NK)

动态规划的初始值

pre=dp[i] cur=dp[i+1] pre[0]=1,其它全为0。

动态规划的返回值

dp.back().back()

代码

核心代码

cpp 复制代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t);
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	cin >> n;
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
	vector<T> ret;
	T tmp;
	while (cin >> tmp) {
		ret.emplace_back(tmp);
		if ('\n' == cin.get()) { break; }
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<long long MOD = 1000000007, class T1 = int, class T2 = long long>
class C1097Int
{
public:
	C1097Int(T1 iData = 0) :m_iData(iData% MOD)
	{

	}
	C1097Int(T2 llData) :m_iData(llData% MOD) {

	}
	C1097Int  operator+(const C1097Int& o)const
	{
		return C1097Int(((T2)m_iData + o.m_iData) % MOD);
	}
	C1097Int& operator+=(const C1097Int& o)
	{
		m_iData = ((T2)m_iData + o.m_iData) % MOD;
		return *this;
	}
	C1097Int& operator-=(const C1097Int& o)
	{
		m_iData = ((T2)MOD + m_iData - o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator-(const C1097Int& o)
	{
		return C1097Int(((T2)MOD + m_iData - o.m_iData) % MOD);
	}
	C1097Int  operator*(const C1097Int& o)const
	{
		return((T2)m_iData * o.m_iData) % MOD;
	}
	C1097Int& operator*=(const C1097Int& o)
	{
		m_iData = ((T2)m_iData * o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator/(const C1097Int& o)const
	{
		return *this * o.PowNegative1();
	}
	C1097Int& operator/=(const C1097Int& o)
	{
		*this /= o.PowNegative1();
		return *this;
	}
	bool operator==(const C1097Int& o)const
	{
		return m_iData == o.m_iData;
	}
	bool operator<(const C1097Int& o)const
	{
		return m_iData < o.m_iData;
	}
	C1097Int pow(T2 n)const
	{
		C1097Int iRet = (T1)1, iCur = *this;
		while (n)
		{
			if (n & 1)
			{
				iRet *= iCur;
			}
			iCur *= iCur;
			n >>= 1;
		}
		return iRet;
	}
	C1097Int PowNegative1()const
	{
		return pow(MOD - 2);
	}
	T1 ToInt()const
	{
		return ((T2)m_iData + MOD) % MOD;
	}
private:
	T1 m_iData = 0;;
};

template<class T >
class CFactorial
{
public:
	CFactorial(int n) :m_res(n + 1) {
		m_res[0] = 1;
		for (int i = 1; i <= n; i++) {
			m_res[i] = m_res[i - 1] * i;
		}
	}
	T Com(int iSel, int iCanSel)const {
		return m_res[iCanSel] / m_res[iSel] / m_res[iCanSel - iSel];
	}
	T Com(const vector<int>& cnt)const {
		T biRet = 1;
		int iCanSel = std::accumulate(cnt.begin(), cnt.end(), 0);
		for (int j = 0; j < cnt.size(); j++) {
			biRet *= Com(cnt[j], iCanSel);
			iCanSel -= cnt[j];
		}
		return biRet;
	}
	vector<T> m_res;
};
typedef C1097Int<> BI;
class Solution {
public:
	int Ans(const int N, const int K, vector<string>& strs) {
		for (auto& s : strs) { sort(s.begin(), s.end()); }
		unordered_map<string, int> mCnt;
		for (const auto& s : strs) { mCnt[s]++; }
		const int N1 = mCnt.size();
		CFactorial<BI> fac(N);
		vector<BI> pre(K + 1); pre[0] = 1;
		for (const auto& [tmp, c] : mCnt) {
			auto cur = pre;
			for (int k = 0; k <= K; k++) {
				for (int i = 1; i <= c; i++) {
					const int k1 = i * (i - 1) / 2;
					if (k + k1 > K) { break; }
					cur[k + k1] += pre[k] * fac.Com(i, c);
				}
			}
			pre.swap(cur);
		}
		return pre.back().ToInt();
	}
};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	int N,K;	
	cin >> N >> K;
	auto strs = Read<string>(N);
#ifdef _DEBUG		
	printf("N=%d,K=%d,",N,K);
	Out(strs, "strs=");
	//Out(strs2, ",strs2=");
	//Out(que, ",que=");
	/*Out(que, "que=");*/
#endif // DEBUG		
	auto res = Solution().Ans(N,K,strs);	
	cout << res << "\n";
	return 0;
}

单元测试

cpp 复制代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t);
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	cin >> n;
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
	vector<T> ret;
	T tmp;
	while (cin >> tmp) {
		ret.emplace_back(tmp);
		if ('\n' == cin.get()) { break; }
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<long long MOD = 1000000007, class T1 = int, class T2 = long long>
class C1097Int
{
public:
	C1097Int(T1 iData = 0) :m_iData(iData% MOD)
	{

	}
	C1097Int(T2 llData) :m_iData(llData% MOD) {

	}
	C1097Int  operator+(const C1097Int& o)const
	{
		return C1097Int(((T2)m_iData + o.m_iData) % MOD);
	}
	C1097Int& operator+=(const C1097Int& o)
	{
		m_iData = ((T2)m_iData + o.m_iData) % MOD;
		return *this;
	}
	C1097Int& operator-=(const C1097Int& o)
	{
		m_iData = ((T2)MOD + m_iData - o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator-(const C1097Int& o)
	{
		return C1097Int(((T2)MOD + m_iData - o.m_iData) % MOD);
	}
	C1097Int  operator*(const C1097Int& o)const
	{
		return((T2)m_iData * o.m_iData) % MOD;
	}
	C1097Int& operator*=(const C1097Int& o)
	{
		m_iData = ((T2)m_iData * o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator/(const C1097Int& o)const
	{
		return *this * o.PowNegative1();
	}
	C1097Int& operator/=(const C1097Int& o)
	{
		*this /= o.PowNegative1();
		return *this;
	}
	bool operator==(const C1097Int& o)const
	{
		return m_iData == o.m_iData;
	}
	bool operator<(const C1097Int& o)const
	{
		return m_iData < o.m_iData;
	}
	C1097Int pow(T2 n)const
	{
		C1097Int iRet = (T1)1, iCur = *this;
		while (n)
		{
			if (n & 1)
			{
				iRet *= iCur;
			}
			iCur *= iCur;
			n >>= 1;
		}
		return iRet;
	}
	C1097Int PowNegative1()const
	{
		return pow(MOD - 2);
	}
	T1 ToInt()const
	{
		return ((T2)m_iData + MOD) % MOD;
	}
private:
	T1 m_iData = 0;;
};

template<class T >
class CFactorial
{
public:
	CFactorial(int n) :m_res(n + 1) {
		m_res[0] = 1;
		for (int i = 1; i <= n; i++) {
			m_res[i] = m_res[i - 1] * i;
		}
	}
	T Com(int iSel, int iCanSel)const {
		return m_res[iCanSel] / m_res[iSel] / m_res[iCanSel - iSel];
	}
	T Com(const vector<int>& cnt)const {
		T biRet = 1;
		int iCanSel = std::accumulate(cnt.begin(), cnt.end(), 0);
		for (int j = 0; j < cnt.size(); j++) {
			biRet *= Com(cnt[j], iCanSel);
			iCanSel -= cnt[j];
		}
		return biRet;
	}
	vector<T> m_res;
};
typedef C1097Int<> BI;
class Solution {
public:
	int Ans(const int N, const int K, vector<string>& strs) {
		for (auto& s : strs) { sort(s.begin(), s.end()); }
		unordered_map<string, int> mCnt;
		for (const auto& s : strs) { mCnt[s]++; }
		const int N1 = mCnt.size();
		CFactorial<BI> fac(N);
		vector<BI> pre(K + 1); pre[0] = 1;
		for (const auto& [tmp, c] : mCnt) {
			auto cur = pre;
			for (int k = 0; k <= K; k++) {
				for (int i = 1; i <= c; i++) {
					const int k1 = i * (i - 1) / 2;
					if (k + k1 > K) { break; }
					cur[k + k1] += pre[k] * fac.Com(i, c);
				}
			}
			pre.swap(cur);
		}
		return pre.back().ToInt();
	}
};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	int N,K;	
	cin >> N >> K;
	auto strs = Read<string>(N);
#ifdef _DEBUG		
	printf("N=%d,K=%d,",N,K);
	Out(strs, "strs=");
	//Out(strs2, ",strs2=");
	//Out(que, ",que=");
	/*Out(que, "que=");*/
#endif // DEBUG		
	auto res = Solution().Ans(N,K,strs);	
	cout << res << "\n";
	return 0;
}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关推荐
xiaoye-duck2 小时前
【C++:C++11】C++11新特性深度解析:从类新功能、Lambda表达式到包装器实战
开发语言·c++·c++11
一个行走的民2 小时前
C++ Lambda 表达式语法详解
c++
小小码农Come on2 小时前
C++访问QML控件-----QML访问C++对象属性和方法
java·开发语言·c++
小章UPUP2 小时前
2026年第十六届MathorCup数学应用挑战赛D题国奖思路
算法
Yungoal2 小时前
项目层级结构
c++
hssfscv2 小时前
软件设计师下午试题四——C语言(N皇后问题、分治、动态规划)
c语言·算法·动态规划
lolo大魔王3 小时前
Go语言的反射机制
开发语言·后端·算法·golang
白羊by3 小时前
Softmax 激活函数详解:从数学原理到应用场景
网络·人工智能·深度学习·算法·损失函数
程序员-King.3 小时前
【基础分析】—— 条件变量wait(lock, 谓词)
c++·c·多线程·条件变量