题目链接 题目大意: 小明有a个1元硬币,b个2元硬币; 小明想要购买一个商品,并且不想找零; 现在小明想知道自己无法给到最低价格是多少;
比如说1个1元硬币,1个2元硬币,最低价格就是4元; 比如说0个1元硬币,1个2元硬币,最低价格就是1元;(不能找零)
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4) 每个样例一行,整数 𝑎𝑖 and 𝑏𝑖 (0≤𝑎𝑖,𝑏𝑖≤1e8)
输出: 每个样例一行,输出最低价格;
Examples input 5 1 1 4 0 0 2 0 0 2314 2374
output 4 5 1 1 7063
题目解析: 如果有1元硬币,那么必然可以给到a+2*b价格内的所有整数; 如果没有1元硬币,那么1元就无法给到;
class Solution {
static const int N = 201010;
int a[N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int x, y;
cin >> x >> y;
cout << (x > 0 ? x+1+2*y : 1) << endl;
}
}
}
ac;
题目链接 题目大意: 小明有n种糖果,每种糖果分别有a[i]颗; 现在小明开始吃糖果,并且每次只吃剩下糖果数量最多的那种,如果有多种则可以任意选择一种数量最多的糖果;
小明想知道最终,能不能吃完所有糖果,并且满足没有连续2天吃到一样的糖果;
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4) 每个样例两行,第一行整数 𝑛 (1≤𝑛≤2⋅1e5) 第二行是n个整数𝑎𝑖 (1≤𝑎𝑖≤1e9)
输出: 每个样例一行,如果可以输出YES,如果不行则输出NO;
Examples input 4 aabbcc aaab aaa abba
output 0 2 1 2
题目解析: 因为题目限定了每次只吃数量最多的糖果,那么可以将数组排序,这样方便后续抉择; 我们唯一能选的,就是当出现两种糖果一样多情况,我们要如何吃; 容易知道,假如有两个糖果一样多,只要交替吃就行了; 那么问题就变成,最多和次最多的两个糖果,能不能顺利达到数量一致的情况; 容易知道相差超过1就无法达到,因为需要连续两天吃一样的最多数量的糖果。
class Solution {
static const int N = 201010;
int a[N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
sort(a, a + n);
bool ans = 0;
if (n == 1) {
ans = a[0] <= 1;
}
else {
ans = (a[n - 1] - a[n - 2]) <= 1;
}
cout << (ans ? "YES" : "NO") << endl;
}
}
}
ac;
题目链接 题目大意: 字符串s可以成为偶字符串,如果字符串满足: 1、字符串的长度为偶数; 2、所有奇数位的字符𝑎[𝑖],满足𝑎[𝑖]=𝑎[𝑖+1];
比如说“aa”、“aabb”、“aabbcc”就是偶字符串,但是“aaa”、“aaab”、“abba”就不是偶字符串; 现在想知道,最少移除掉字符串s中多少个字符,可以使得字符串s变成偶字符串;
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4) 每个样例一行,字符串 𝑠 (1≤|𝑠|≤2⋅105),
输出: 每个样例一行,输出最少移除的字符串;
Examples input 4 aabbcc aaab aaa abba
output 0 2 1 2
题目解析:
dp[i] 表示前i个字符,能找到的最长even字符串; 那么对于第i个字符,我们有两个选择: 取第i个字符,那么找到最近一个和第i个字符相近的位置k,我们有dp[i]=max(dp[i], dp[k-1] + 2); 不取第i个字符,那么有dp[i]=max(dp[i], dp[i - 1]);
class Solution {
static const int N = 201010;
char str[N];
int dp[N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
cin >> (str + 1);
int n = strlen(str + 1);
int pos[26] = {0};
for (int i = 1; i <= n; ++i) {
dp[i] = dp[i - 1];
int index = str[i] - 'a';
if (pos[index]) { //
dp[i] = max(dp[i], dp[pos[index] - 1] + 2);
}
pos[index] = i;
}
cout << n - dp[n] << endl;
}
}
}
ac;
题目链接 题目大意: 给出n个整数的数组a,其中数组的元素绝对值满足 abs(a[i]) <= 2; 现在可以移除数组前面x个元素和后面y的个元素,求剩下的元素乘积尽可能的大;
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4) 每个样例两行,第一行是整数𝑛 (1≤𝑛≤2⋅1e5) 接下来是n个整数𝑎1,𝑎2,…,𝑎𝑛 (|𝑎𝑖|≤2);
输出: 每个样例一行,包括整数x和y,x和y分别表示:移除数组前面x个元素和后面y的个元素; 允许移除所有的元素,这样乘积为1; 如果有多个答案,输出任意一个;
Examples input 5 4 1 2 -1 2 3 1 1 -2 5 2 0 -2 2 -1 3 -2 -1 -1 3 -1 -2 -2
output
0 2 3 0 2 0 0 1 1 0
题目解析: 题目的要求是乘积尽可能大,那么数字0首先被排除,因为0乘以任意数字都0,而移除所有元素的乘积结果都是1; 那么按照0,将数组切分成若干段,题目变成了在某一个子区间[left, right]中,寻找乘积最大的子区间; 假如区间[left, right]没有负数,或者有偶数个负数,那么这个区间所有数字的乘积就是最大的; 假如区间[left, right]有奇数个负数,那么肯定是去掉最左边的负数(假如位置是posLeft),或者去掉最右边的负数(假如位置是posRight); 这样就可以得到区间[left, right]的最大乘积;
由于乘积会比较大,这里只需要统计2和-2的数量即可,这个结果越大,则乘积越大。
class Solution {
static const int N = 201010;
int a[N];
int ansTotal, ansLeft, ansRight;
int pos[N], k;
void find(int left, int right) {
int total = 0, posLeft = 0, posRight = right;
int cntTotal = 0, cntLeft = 0, cntRight = 0;
for (int i = left; i <= right; ++i) {
if (a[i] < 0) {
++total;
}
if (abs(a[i]) == 2) {
++cntTotal;
}
if (a[i] < 0 && !posLeft) {
posLeft = i;
}
if (a[i] < 0) {
posRight = i;
}
}
for (int i = left; i <= posLeft; ++i) {
if (abs(a[i]) == 2) {
++cntLeft;
}
}
for (int i = posRight; i <= right; ++i) {
if (abs(a[i]) == 2) {
++cntRight;
}
}
if (total % 2 == 0) {
if (cntTotal > ansTotal) {
ansTotal = cntTotal;
ansLeft = left;
ansRight = right;
}
}
else {
if (cntLeft < cntRight) {
cntTotal -= cntLeft;
if (cntTotal > ansTotal) {
ansTotal = cntTotal;
ansLeft = posLeft + 1;
ansRight = right;
}
}
else {
cntTotal -= cntRight;
if (cntTotal > ansTotal) {
ansTotal = cntTotal;
ansLeft = left;
ansRight = posRight - 1;
}
}
}
}
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
k = 0;
pos[k++]= 0;
for (int i = 1; i <= n; ++i) {
if (a[i] == 0) { //
pos[k++] = i;
}
}
pos[k++] = n + 1;
ansTotal = 0;
ansLeft = 1;
ansRight = 0;
for (int i = 1; i < k; ++i) {
int l = pos[i - 1];
int r = pos[i]; // (l, r)
find(l + 1, r - 1);
}
cout << ansLeft - 1 << " " << (n - ansRight) << endl;
}
}
}
ac;
题目链接 题目大意: 给出一个n x n的矩阵,矩阵由数字0和1组成; 现在可以对矩阵进行下列操作: 1、将数组的每一行向上移动; 2、将数组的每一行向下移动; 2、将数组的每一列向左移动; 2、将数组的每一列向右移动; 这个操作是没有代价的,可以进行任意次; 然后还可以对矩阵中任何一个数字进行异或(XOR)操作,这个操作的代价是1;
现在想要把整个矩阵变成单元矩阵,问最小的代价是多少; (单元矩阵是一个对角线为非零元素,其它元素为零的方形矩阵)
输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4) 每个样例两行,第一行是整数𝑛 (1≤𝑛≤2000) 接下来是n x n的01矩阵;
输出: 每个样例一行,输出最小的代价。
Examples input 4
3 010 011 100
5 00010 00001 10000 01000 00100
2 10 10
4 1111 1011 1111 1111 output 1 0 2 11
题目解析: 题目的要求是构造单元矩阵的代价最小,那么需要尽可能利用无代价的4个位移操作; 以简单的3阶矩阵来分析,对于矩阵 010 001 010 我们可以拼接4个矩阵,得到 010 010 001 001 010 010 ---------- 010 010 001 001 010 010
以左移一列为例,结果就是图中左上角的2-4列,如下: 0 100 0 010 0 100
按照这个思路,其实就是在4个n x n矩阵拼出来的大矩阵中,找到一个n x n子矩阵,并且斜对角线的1尽可能多; 那么就直接从每一行的第一列开始向右下角遍历,保持长度为n的斜对角线,存在尽可能多的1; 但是直接拼接4个矩阵去模拟,整体实现复杂度比较高;对于第i行第j列的元素a[i][j],右下角其实就是a[i+1][j+1],其中: j是从1到n,因为每行的遍历都是从最左开始; i可能会存在大于n的情况,此时可以对n取模,保证数组不越界;
假设对角线上最多的1数量为maxSize,矩阵中所有1的数量是totalSize; 那么答案就是ans=(total - maxSize) + (n - maxSize) 。
class Solution {
static const int N = 3010;
char a[N][N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> (a[i] + 1);
}
int total = 0, maxSize = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
total += ('1' == a[i][j]);
}
// 从a[i][1]开始,向右下角遍历
int tmpSize = 0;
for (int k = 0; k < n; ++k) {
int x = (i - 1 + k) % n + 1;
int y = 1 + k;
// cout << x << " " << y << endl;
if (a[x][y] == '1') {
++tmpSize;
}
}
maxSize = max(maxSize, tmpSize);
}
cout << (total - maxSize) + (n - maxSize) << endl;
}
}
}
ac;
题目链接 题目大意: 给出由+和-组成的字符串,我们称字符串为平衡的字符串,假如+和-的字符是相同的; 现在可以对字符串执行操作,每次将两个相邻的-号合并为+号; 假如若干次操作之后,字符串变成了平衡的字符串,那么这个字符串可以称之为特殊的字符串;
现在给出长度为n的字符串,问字符串中有多少子串是特殊的;
输入: 第一行,整数𝑡 表示t个样例 (1≤𝑡≤500) 每个样例两行,第一行是整数 𝑛 (1≤𝑛≤3000) 第二行是字符串;
输出: 输出满足要求的子串数量;
Examples input 5 3 +-+ 5 -+--- 4 ---- 7 --+---+ 6 +++--- output 2 4 2 7 4
题目解析:
这是easy难度,题目给出来的范围比较小;
那么可以使用最暴力的办法,O(N*N)的复杂度,枚举所有字符串的子串;
再分别计算这个子串是否符合要求;
判断一个字符串是否是特殊的,可以遍历整个字符串中+和-的数量(假如总数是x和y);
如果x==y,或者x<y并且(y-x)%3==0 && (y-x)%3 < z
,则子串满足要求。
统计x或者y是一个比较快的过程,用countx[i][j]表示区间[i, j]内的+数量,那么countx[i][j]可以由countx[i][j-1]来快速计算;y的计算同理;
class Solution {
static const int N = 3010;
char str[N];
int a[N];
int dp[N]; // dp[i] 前i个字符,以i结尾,并且x==y的子串数量
int countx[N][N], county[N][N];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
cin >> (str + 1);
// 如果x==y,或者x<y并且`(y-x)%3==0
int ans = 0;
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
if (str[j] == '+') {
countx[i][j] = countx[i][j - 1] + 1;
county[i][j] = county[i][j - 1];
}
else {
countx[i][j] = countx[i][j - 1];
county[i][j] = county[i][j - 1] + 1;
}
if (countx[i][j] == county[i][j] || ((countx[i][j] < county[i][j] && (county[i][j] - countx[i][j]) % 3 == 0))) {
++ans;
}
}
}
cout << ans << endl;
}
}
}
ac;
题目链接 题目大意: 给出由+和-组成的字符串,我们称字符串为平衡的字符串,假如+和-的字符是相同的; 现在可以对字符串执行操作,每次将两个相邻的-号合并为+号;假如若干次操作之后,字符串变成了平衡的字符串,那么这个字符串可以称之为特殊的字符串;
现在给出长度为n的字符串,问字符串中有多少子串是特殊的;
输入: 第一行,整数𝑡 表示t个样例 每个样例两行,第一行是整数𝑛 第二行是字符串;
输出: 输出满足要求的子串数量;
Examples input 5 3 +-+ 5 -+--- 4 ---- 7 --+---+ 6 +++--- output 2 4 2 7 4
题目解析: 这是hard难度,题目给出来的范围比较大,O(N*N)的复杂度枚举所有字符串的子串的方式已经不适用; 首先需要对我们已经采用的方法进行简化,countx和county其实可以简化为countx-county的方式,并且由二维简化为一维: 我们用sum[i]表示前i个字符相加的结果,字符+表示-1,字符-表示+1; 那么子串[i, j]可以用sum[j] - sum[i - 1]的方式快速得到结果,(sum[j] - sum[i - 1])%3==0 && sum[j] >= sum[i - 1] 表示有解;
从左到右遍历字符串,对于第j个字符,首先得到sum[j],然后遍历i∈[1, j-1]判断 (sum[j] - sum[i - 1])%3==0 && sum[j] >= sum[i - 1] 的数量; 这个遍历过程存在较多重复计算,我们只在乎i∈[1, j-1]这个区间中sum[i]的值,而对i的位置并不关心,并且sum[i]的值最多也只有2n+1种可能(取值范围是[-n, n]); 那么如果我们把sum[i]的结果直接存到[-n, n] 这个区间上,我们就可以直接获取遍历这个区间的值即可。 以n=4为例,sum[i]的结果可能有[-4,-3,-2,-1,0,1,2,3,4],如果sum[j]=3,那么我们只需要取sum[i]=3/0/-3的值;如果sum[j]=2,那么我们只需要取sum[i]=-1/-4的值; 由于我们只在乎%3的结果,我们可以用cnt[0,1,2]来表示sum[i]%3的值和,这样从数组中取值就不需要遍历,可以很快取到某个余数的值;
但是这里还有一个条件是sum[j] >= sum[i - 1],sum[j]=0的时候,sum[i]=3这种情况是不允许的,所以cnt的值是需要维护的,维护方式就是: 当我们sum[j]变小的时候,cnt作为累加和,要退掉之前累加的值; 比如说sum[j]=2的时候,cnt累加了[-4, 2]的区间;sum[j+1]=1的时候,cnt累加了[-4, 1]的区间;也就是cnt需要退掉sum[j]=2的值;
由于sum[i]的取值范围是[-n, n] 我们可以将sum[i]+n,这样取值范围变成[0, 2n],好处是可以用数组来表示,并且不影响我们对结果的结算,因为 (sum[j] - sum[i - 1])等于 (sum[j] + n)-(sum[i-1] + n),sum[j] >= sum[i-1] 等于 sum[j] + n >= sum[i-1] + n;
class Solution {
static const int N = 200010;
char str[N];
int sum[N * 2];
int cnt[3];
public:
void solve() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
cin >> (str + 1);
lld ans = 0;
int preCount = n;
memset(cnt, 0, sizeof(cnt));
memset(sum, 0, sizeof(sum));
sum[preCount] = 1;
cnt[preCount % 3] = 1;
for (int i = 1; i <= n; ++i) {
int curCount = preCount + (str[i] == '-' ? 1 : -1);
int index = curCount % 3;
if (curCount < preCount) {
cnt[preCount % 3] -= sum[preCount];
}
else {
cnt[curCount % 3] += sum[curCount];
}
ans += cnt[index % 3];
cnt[curCount % 3]++;
sum[curCount]++;
preCount = curCount;
}
cout << ans << endl;
}
}
}
ac;
趁着假日闲暇时光以及工作间隙,把1660CF的7个题目都AC了。 有一丝成就感,毕竟已经是退役接近十年的选手;也有一丝挫败感,在毕业多年之后,水平已经变成只能在DIV3达成AK成就。 在最应该学习的时候,没有努力学习,那么未来即使有机会也很难超越自己。当年不擅长做的数论题,现在依旧不会做。 这让我逐渐明白一个道理:面对困难和挑战,人在当下的选择,往往会影响后续一辈子。 不是说生活一定要迎难而上,一定要吃得苦中苦方为人上人,而是思考和确定自己的目标、选择、动力,然后再努力前行。