当前位置: 首页 > news >正文

CSDN竞赛14期题解

总结

这次竞赛题目不难,但是蛮久没刷数位DP了,还是花了一些时间去推导公式才解决 T 4 T4 T4,浪费了不少时间,排名不是很理想。

题目列表

1.字符串全排列

题目描述

对K个不同字符的全排列组成的数组, 面试官从中随机拿走了一个, 剩下的数组作为输入, 请帮忙找出这个被拿走的字符串? 比如[“ABC”, “ACB”, “BAC”, “CAB”, “CBA”] 返回 “BCA”

分析

全排列数组一共 n = k ! n = k! n=k!个排列,这意味着,任意一个字符出现在第 i i i位的次数都是 n / k n / k n/k,所以可以使用hash表统计出现在各个位置上的字符的次数,如果第 i i i个位置上字符 x x x出现的次数不是 n / k n / k n/k,就说明被拿走的字符串的第 i i i个位置就是 x x x

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
using namespace std;
unordered_map<char, int> m;
std::string solution(int n, std::vector<string>& vec) {
	string res;
	if(!n) return res;
	int k = vec[0].size();
	string s = vec[0];
	for(int i = 0; i < k; i++) m[s[i]] = i;
	int t = (n + 1) / k;
	for(int i = 0; i < k; i++) {
		vector<int> cnt(k, t);
		for(int j = 0; j < n; j++) {
			cnt[m[vec[j][i]]]--;
		}
		for(int j = 0; j < k; j++) {
			if(cnt[j]) {
				res += s[j];
				break;
			}
		}
	}
	return res;
}
int main() {
	int n;
	vector<string> vec;
	cin>>n;
	string x;
	for(int i = 0; i < n; i++) {
		cin>>x;
		vec.push_back(x);
	}
	std::string result = solution(n,vec);
	std::cout<<result<<std::endl;
	return 0;
}

2.小Q新式棋盘

题目描述

已知棋盘大小为n*n。 每个位置都有自己的权值q。 该棋盘中有多少对行权值和小于列权值和。

输入描述:

第一行输入整数n。(1<=n<=100)表示棋盘的大小
以下n行每行输入n个整数表示棋子的权值。(1<=a<=1000)

输出描述:

输出小Q的分值。

输入样例:

3
1 2 3
1 2 3
1 2 3

输出样例:

3

分析

本题相当于求出n个行权值和数组 r o w row row和n个列权值和数组 c o l col col后,对 r o w row row中的每个元素,在 c o l col col中找出大于它的元素个数,累加起来就是答案。分别对这两个数组排序,然后暴力枚举即可。

代码

#include <iostream>
#include <algorithm>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
const int N = 1005;
int g[N][N];
int row[N], col[N];
int main() {
	int n;
	cin>>n;
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < n; j++) {
			cin>>g[i][j];
			row[i] += g[i][j];
			col[j] += g[i][j];
		}
	}
	sort(row, row + n);
	sort(col, col + n);
	int res = 0;
	for(int i = 0; i < n; i++) {
		int x = row[i];
		int j = 0;
		while(j < n && col[j] <= x) j++;
		res += n - j;
	}
	cout<<res<<endl;
	return 0;
}

3. 因数-数字游戏

题目描述

小Q的柠檬汁做完了。 掏出了自己的数字卡牌。 想要和别人做数字游戏。 可是她又不想要输掉游戏。 她制定好规则,每 次每个人只能把这个牌换成它的因子的某个牌。 但是这个因子不能是1或者整数本身。 现在给出整数n。 两个人开始做游 戏,谁无法再给出因子牌则该人胜利,如果该整数无因子牌直接视为先手胜利,请判断先手在最优策略状态下能否必胜,

分析

本题考察博弈论。其实就是简单的数论题目。

  • 如果初始的数字是1或者质数,则先手无法给出因子牌,先手获胜;
  • 如果初始的数字有两个因子(不包括1和自身),那么先手只能给出其中一个因子,后手拿到的就是质数,后手获胜;
  • 如果初始数字有两个以上的因子,先手拿掉大部分因子,只留下包含两个因子的数字给后手,如此一来,后手面临的就是第二种情况,先手获胜。

因此,本题只需要求出初始数字的因子个数,因子个数是2则后手获胜,否则先手获胜。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
typedef long long ll;
int main() {
	ll n;
	std::cin>>n;
	int cnt = 0;
	for(ll i = 2; i <= n / i; i++) {
		if(n % i == 0) {
			ll x = n;
			while(x % i == 0) {
				cnt++;
				x /= i;
			}
			ll j = n / i;
			if (j != i) {
				x = n;
				while(x % j == 0) {
					cnt++;
					x /= j;
				}
			}
		}
	}
	if(cnt == 2) cout<<2<<endl;
	else cout<<1<<endl;
	return 0;
}

4.题目名称:编码

题目描述

编码工作常被运用于密文或压缩传输。这里我们用一种最简单的编码方式进行编码:把一些有规律的单词编成数字。 字母 表中共有26个字母{a,b,…,z},这些特殊的单词长度不超过6且字母按升序排列。把所有这样的长度相同的单词放在 一起,按字典顺序排列(a…z,ab…az,bc…bz…)一个单词的编码就对应着它在整个序列中的位置。 你的任务就是对 于所给的单词,求出它的编码。

输入描述:

仅一行,被编码的单词。

输出描述:

仅一行,对应的编码。如果单词不在序列中,输出0。

输入样例:

ab

输出样例:

27

分析

如果将字母看成数字,本题就相当于定义了一种"升序数",比如“456”,第1个升序数是0,第11个是12。给定一个升序数,求它的编号。这就是经典的数位DP问题了,数位DP问题一段时间不做就生疏了,这里再借助本题回忆下求解过程。

首先,定义基本的状态数组。 f [ i ] [ j ] f[i][j] f[i][j]表示以 j j j为开头的 i i i位数的数量,自然要加上限制条件,比如本题就是表示以 j j j为开头的 i i i位数的编码数量。

状态转移方程: f [ i ] [ j ] = f [ i − 1 ] [ j + 1 ] + f [ i − 1 ] [ j + 2 ] + . . . f[i][j] = f[i-1][j+1] + f[i-1][j+2] + ... f[i][j]=f[i1][j+1]+f[i1][j+2]+...,也就是说,只要 i − 1 i-1 i1位数的首字符大于 j j j j j j追加上这个 i − 1 i-1 i1位数都可以转移为以 j j j开头的 i i i位数的状态。再来看状态 f [ i ] [ j + 1 ] f[i][j+1] f[i][j+1] f [ i ] [ j + 1 ] = f [ i − 1 ] [ j + 2 ] + f [ i − 1 ] [ j + 3 ] + . . . f[i][j+1] = f[i-1][j+2] + f[i-1][j+3] + ... f[i][j+1]=f[i1][j+2]+f[i1][j+3]+...,比较下两个状态的状态转移方程,可以得出

f [ i ] [ j ] = f [ i − 1 ] [ j + 1 ] + f [ i ] [ j + 1 ] f[i][j]=f[i-1][j+1]+f[i][j+1] f[i][j]=f[i1][j+1]+f[i][j+1]

状态边界是 f [ 1 ] [ j ] = 1 f[1][j]=1 f[1][j]=1,表示以 j j j为首字符的一位数编码个数是1。

上面的基本状态数组是数位DP中最容易的一部分,数位DP的难点在于按位遍历分类讨论。

以s= " b d f bdf bdf"为例,我们自右而左遍历:

  • 遍历到 f f f,由于s是三位数,所以所有的1位数都小于它,我们给最终的编号 r e s res res加上一位数的编码 f [ 1 ] [ j ] f[1][j] f[1][j],其中 0 < = j < 26 0<=j<26 0<=j<26。然后再加上以 b d bd bd开头的,小于 b d f bdf bdf的三位数 b d e bde bde
  • 遍历到 d d d,给 r e s res res加上所有的两位数的编码 f [ 2 ] [ j ] f[2][j] f[2][j],再加上以 b b b开头,第二位数小于 d d d的三位数,这就确定了第二位数是 c c c,相当于加上 f [ 2 ] [ c ] f[2][c] f[2][c]就可以了。
  • 遍历到 b b b,最高只有三位,所以这里不需要加上所有的三位数了,只需要给 r e s res res加上以小于 b b b的字符开头的三位数的个数,即 f [ 3 ] [ a ] f[3][a] f[3][a]即可。

此时的 r e s res res就是s前面编码的个数,那么s自身的编码就是 r e s + 1 res+1 res+1

观察上面的遍历过程可以发现,所有小于"bdf"的编码,一位和两位数编码分布在前面的两轮遍历,剩下的编码我们通过固定前面字符的方式来进行状态划分:

  • 遍历 f f f,固定前面的 b d bd bd,枚举第三位大于 d d d小于 f f f的状态;
  • 遍历 d d d,固定前面的 b b b,枚举第二位大于 b b b小于 d d d的状态。
  • 遍历 b b b,枚举第一位小于 b b b的三位数状态。

用数字表示更为直观,比如247,寻找小于它的升序数的个数。遍历7,枚举了所有的1位数(0到9),以及小于247的前两位是24的三位升序数(245,246);遍历4,枚举了所有的两位升序数(12,13,…,23,…),以及小于247的首位是2的三位升序数(234,…,239,245,246);遍历2,枚举所有首位小于2的三位升序数(123,123,…)。

数位DP之所以难,就是划分的状态多,求小于n位编码x的个数,需要划分状态为:1位数编码,2位数编码,…,n-1位数编码,第一位小于x的首位的n位数编码,第一位等于x的首位数且第二位小于x的第二位的n位数编码,…,前n-1位等于x的前n-1位且最后一位小于x的最后一位的n位数编码。

总结下数位DP的求解过程。

  • 求解基本的状态数组f
  • 遍历时加上小于n位数的状态
  • 遍历时加上前面位数固定的状态

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
typedef long long ll;
ll f[7][30];
bool check(string &s) {
	int n = s.size();
	for(int i = 1; i < n; i++) {
		if(s[i] <= s[i-1]) return true;
	}
	return false;
}
int main() {
	string s;
	cin>>s;
	ll res = 0;
	int n = s.size();
	if(n > 6 || check(s)) cout<<0<<endl;
	else {
		for(int i = 0; i < 26; i++) f[1][i] = 1;
		for(int i = 2; i <= n; i++) {
			for(int j = 25; j >= 0; j--) {
				f[i][j] = f[i][j+1] + f[i-1][j+1];
			}
		}
		for(int i = n - 1; i >= 0; i--) {
			int x = s[i] - 'a';
			int last = 0;
			if(i) last = s[i - 1] - 'a' + 1;
			int k = n - i;
			for(int j = last; j < x; j++) res += f[k][j];
			if(i) {
                for(int j = 0;j < 26;j++) res += f[k][j];
			}
		}
		cout<<res + 1<<endl;
	}
	return 0;
}

相关文章:

  • Qt创建线程的几种方式_创建一个新线程的方法
  • Android自定义ViewGroup布局进阶,完整的九宫格实现
  • 完全开源的代码生成器之code-generator
  • C++:实现量化将期限结构与一组债券相匹配 使用四种不同的拟合方法测试实例
  • 毫米波雷达的那些知识点——增益、阈值、功耗、窗口、RF 发射功率调整、VCO、LNA
  • 1568_AURIX_TC275_电源管理_唤醒配置与状态
  • MySQL表的增删查改(上)
  • 世界杯---人生就是一届又一届世界杯
  • LeetCode 1832. 判断句子是否为全字母句
  • 多数据源解决分布式事务
  • 跳槽一次能涨多少?今天见识到跳槽天花板。
  • 【HTML期末学生大作业】 制作一个简单HTML宠物网页(HTML+CSS)
  • 分享一些冷门但却很实用的css样式
  • 写代码时记录的小技巧
  • Springboot 那年我双手插兜,手写一个excel导出
  • Google 是如何开发 Web 框架的
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • Cumulo 的 ClojureScript 模块已经成型
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • Python socket服务器端、客户端传送信息
  • Swoft 源码剖析 - 代码自动更新机制
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • 关于List、List?、ListObject的区别
  • 码农张的Bug人生 - 见面之礼
  • 写给高年级小学生看的《Bash 指南》
  • 延迟脚本的方式
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • #1015 : KMP算法
  • #if 1...#endif
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (蓝桥杯每日一题)love
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • .net Application的目录
  • .NET CF命令行调试器MDbg入门(一)
  • .NET CLR基本术语
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .net core控制台应用程序初识
  • .net的socket示例
  • .NET开发人员必知的八个网站
  • [ IO.File ] FileSystemWatcher
  • [ Linux 长征路第五篇 ] make/Makefile Linux项目自动化创建工具
  • [2010-8-30]
  • [2016.7 day.5] T2
  • [3D基础]理解计算机3D图形学中的坐标系变换
  • [ASP.NET 控件实作 Day7] 设定工具箱的控件图标
  • [AutoSAR 存储] 汽车智能座舱的存储需求
  • [C/C++]_[初级]_[关于编译时出现有符号-无符号不匹配的警告-sizeof使用注意事项]
  • [LeetCode] 148. Sort List 链表排序
  • [LeetCode]: 145: Binary Tree Postorder Traversal