西华师大周赛 Round 21
A - 英语课上的最短字母串
题目描述
在一个阳光明媚的上午,小\(C\)正在教室上英语课,他看着英语文章逐渐走神。。。,他想一整个英语文章可不可以看做是一个连续的字母串(字符串) \(S\)(忽略文章单词之间的空格),他想把整个文章的组成的字母串的子串(连续子串)找出来,一个 \(S\) 的子串 \(P\) 是合法的,当且仅当 \(T\) 中包含了所有的小写字母,小 \(C\) 想知道所有的合法的\(S\)的子串中,长度最短是多少?
输入格式
第一行一个整数 \(T\) \((1 \leq T \leq 100)\) ,表示输入数据的组数
接下来 \(T\) 行,每一行一个字符串。(只包含小写字母,\(S\) 的长度不超过 \(10^6\))
输出格式
\(T\) 行,每行一个数字,代表最短长度。数据保证存在一个合法的\(S\)的子串。
样例#1
输入样例#1
1
ykjygvedtysvyymzfizzwkjamefxjnrnphqwnfhrnbhwjhqcgqnplodeestu
输出样例#1
49
题解
知识点:模拟,尺取法(双指针)
#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
string s;
int sts[30];//统计字母个数
bool pd(int *sts){//判断统计字母是否满足26个
int i;
for(i=0;i<26;i++){
if(sts[i]==0) return false;
}
return true;
}
void Solve(){
cin >> s;
int i, j, minn = s.size() + 500, flag = 0, p = 0;//p为左指针
for(i = 0; i < s.size(); i++) { //右指针移动
int flag = 0;
sts[s[i]-'a']++;
while(pd(sts) && p < i-26){//当字符串合法
flag = 1;//标记合法,后面左指针可能要向后移动进行
minn = min(minn, i-p+1);//更新最短长度
if(minn == 26) {cout << minn << endl; return ;}//当长度为26时直接输出结束了
sts[s[p]-'a']--;//
p++;//左指针试着向右移动
}
if(flag){//左指针返回一位
p--;
sts[s[p]-'a']++;
}
}
cout << minn << endl;
return ;
}
int main(){
int T = 1;
cin >> T;
while(T--){
Solve();
}
return 0;
}
B - 数学课上的表达式计算
题目描述
刚刚睡醒的小\(C\)看了一眼课表,“我嘞个逗!五节数学课!!!”
小 \(C\) 昏昏欲睡的去了教室,在黑板上老师写满了各种各样的数学表达式,其中运算符只包括+,-,*,/,^,(加,减,乘,除,乘方),准备抽同学回答(只要求计算出最终值),现在小 \(C\) 在想能不能写一个程序实现这样的计算,你能帮帮他吗?
数据可能会出现有括号的情况,还有可能出现多余的括号,但不影响计算
数据保证不会出现大于 \(2^{31}\) 的答案
数据可能会出现负数的情况
输入格式
仅一行,即为表达式
输出格式
仅一行,即为表达式计算出的结果
样例#1
输入样例#1
(2+2)^(1+1)
输出样例#1
16
提示
表达式总长度 \(\leq 30\)
题解
知识点:递归思想
#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 5;
string s;
int nums(int l, int r) {
int cnt = 0;
for (int i = l; i <= r; i++)
cnt = cnt * 10 + (s[i] - '0');
return cnt;
}
int calc(int l, int r) {
if (l > r) return 0;
int pos1 = -1, pos2 = -1, pos3 = -1; // ops1 : +- ; pos2 : */ ; pos3 : ^
int cnt = 0; // 标记左右括号
for (int i = l; i <= r; i++) {
// 统计当前范围内的括号个数
if (s[i] == '(') cnt++;
if (s[i] == ')') cnt--;
// 定位这个范围内相应运算符号最后一次出现的地点(越早被定位,递归深度越浅,越晚被执行运算)
// 这里是从递归角度出发确定相同优先级的运算符的执行顺序
if ((s[i] == '+' || s[i] == '-') && cnt == 0) pos1 = i;
if ((s[i] == '*' || s[i] == '/') && cnt == 0) pos2 = i;
if (s[i] == '^' && cnt == 0) pos3 = i;
}
// 在当前范围内没有符号的情况下,进行条件判断
if (pos1 == -1 && pos2 == -1 && pos3 == -1) {
//// 多余括号情况
// 左括号多余
if (cnt > 0 && s[l] == '(') return calc(l+1, r);
// 右括号多余
else if (cnt < 0 && s[r] == ')') return calc(l, r-1);
//// cnt == 0, 说明要么是数字,要么有一对括号括住它
else if (cnt == 0 && s[l] == '(' && s[r] == ')') return calc(l+1, r-1);
else return nums(l, r);
}
// 按优先级递归
if (pos1 != -1) { // + - 优先级最低
if (s[pos1] == '+') return calc(l, pos1-1) + calc(pos1+1, r);
if (s[pos1] == '-') return calc(l, pos1-1) - calc(pos1+1, r);
}
else if (pos2 != -1) { // * / 优先级中等
if (s[pos2] == '*') return calc(l, pos2-1) * calc(pos2+1, r);
if (s[pos2] == '/') return calc(l, pos2-1) / calc(pos2+1, r);
}
else if (pos3 != -1) { // ^ 优先级最高
return (int)pow(calc(l, pos3-1), calc(pos3+1, r));
}
return 0;
}
int main () {
cin >> s;
cout << calc(0, s.length()-1);
return 0;
}
C - 体育课上的“硬币游戏”
题目描述
在今天的体育课上,老师和同学们做了一个有趣的游戏,老师拿出了一堆硬币,选出一个 “挑战者”出列,很不幸,今天小\(C\)成了其中一个“挑战者”, 然后老师让其他的同学排 \(n\) 行 \(m\) 列,然后现在给第 \(i\) 行第 \(j\) 列的同学发 \(a_{(i,j)}\) 个硬币,然后让“挑战者”进行 \(k\) 个回合的游戏,每个回合,“挑战者”可以选择一行或一列的同学拿走他们的硬币,在\(k\)个回合拿走的硬币总数就是“挑战者”整个游戏的得分,现在每一个“挑战者”都想要在 \(k\) 个回合结束后使得自己的分数 \(score\) 最大(每个”挑战者“的队伍会进行变换,硬币也会重新分配),假如你是”挑战者“,怎样才能使得自己的分数最大呢?
输入格式
第一行三个整数 \(n,m,k\),表示每次游戏队伍的行,列和游戏的回合数
接下来的 \(n\) 行每行 \(m\) 个整数,表示队伍中每个同学手上的硬币数 \(a_{(i,j)}\)
输出格式
输出 \(1\) 行,每行表示该挑战者能获得的最大分数 \(score\)
样例#1
输入样例#1
3 3 2
101 1 102
1 202 1
100 8 100
输出样例#1
414
提示
数据与模的规定
\(1 \leq n,m \leq 12\) ,
\(1 \leq k \leq n\times m\),
\(1 \leq a_{(i,j)} \leq 10^5\)
题解
知识点:枚举,贪心
对于每个挑战者每一个回合肯定是用贪心的思路选择硬币数总和最大的一行或一列,但是按照这个思路我们会发现一个问题,每次选择之后,那一行或一列的同学就出列没在队伍里,相应的位置也就没有硬币了,然后选择后每一行或者每一列的总数就会发生改变,然后最大值的位置就会改变,所以说我们可能需要重新找最大值,可是这样会不会太麻烦了?我们观察一下数据范围,\(1 \leq n,m \leq 12\) 所以说我们可以先枚举列数(只选\(1,3\)列,只选\(2,4\)列... 只选 ...... 列)先对列进行求和,然后对列贪心,然后选完列之后然后再对行求和,然后对行贪心,然后每次记录最大值,与下一次的最大值比较,最终求出答案(当然反过来先选行再选列也是可以的)
然后对于每次选择,我们怎么知道之前选过哪几列,哪几种情况呢,这个我们直接进行搜索就行了,这个就不多说,然后平常像这种的枚举我们可以用 \(01\) 串枚举
所谓\(01\)串枚举,就是我们在每个个体都面对两种选择的时候,可以用一个\(01\)串表示,比如说对于本题,每一行有选和不选两种可能,假设有\(5\)行的话,我们就可以用一个长度为\(5\)的\(01\)串来表示,\(0\)表示不选\(1\)表示选,就像一个\(bool\)数组一样,如:\(01001\) 表明第\(1,3,4\)行不选,第\(2,5\)行要选。
只是用一个数字来表示比用数字表示方便,为什么方便呢?因为所有长度小于\(n\)的\(01\)串,转成十进制之后就是所有小于\(2^n\)的数字,这样从\(0\)到\(2^n−1\)枚举数字就是从\(00…00\)枚举到\(11…11\)。这样比写个搜索枚举所有选和不选的情况快乐吧!
#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 20;
int n, m, k;
int a[N][N];
ll sh[N], sl[N];//sh[i]是第i行的和,sl[i]是第i列的和(在行选完之后用)
bool b[N];//b[i]是01串转换过来的bool数组
ll ans = 0; //存答案,注意总和会超过int的最大值要用long long
int deal(int x){ //把x这个数字转换成bool数组
memset(b, 0, sizeof(b)); //不清数组会爆炸!!!
int cnt = 0, i = 1; //cnt存01串里面1的个数
while (x){
if (x&1) { //如果x的最后一位是1
cnt++;
b[i] =1;
}
x >>= 1; //x右移一位,等价于除以二取整
i++;
}
return cnt;
}
void Solve(){
//————————————读入——————————————
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++){
scanf("%d", &a[i][j]);
sh[i] += a[i][j];
}
//——如果k>n或者k>M,那么肯定就把矩阵选完了————
//这里我这么处理主要是因为我后面的有个判断把这种情况给直接continue掉了,
//k=n或者m就已经能把矩阵选完,所以结果肯定是一样的
if (k > n) k = n;
if (k > m) k = m;
//————————————————————————————————————————
//—————枚举选行的所有可能性,贪心处理列——————
int tmp = (1<<n) -1; //1<<n就等于2^n
for (int T =0; T <= tmp; T++){ //T用来枚举行的状态
int numh = deal(T);//将状态T转为bool数组,并返回有多少个1,既要选多少行
int numl = k - numh; //numl是要选多少列
if (numl > m || numl < 0) continue;
//numl>m或者numl<0,说明行的方案不对(其实也有可能是k太大,这里就是上文说的k比较大的时候直接continue没了的地方)。
ll sum=0; //sum是本次枚举的方案的和
for (int i = 1; i <= n; i++) //把选中的行都加上
if (b[i]) sum += sh[i];
memset(sl, 0, sizeof(sl));//行选得不同矩阵就不同,所以sl数组每次都要清干净,不然又得爆炸
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (!b[i]) sl[j] += a[i][j]; //第i行没有选,j列的和需要加上a[i][j](第i行选了a[i][j]就清零了不能加)
sort(sl+1, sl+1+m);
for (int i=1,j=m; i<=numl; i++,j--) sum += sl[j]; //把最大的numl列的和加到方案里
ans = max(ans, sum);//维护答案
}
//————————————枚举+贪心结束——————————————
printf("%lld\n", ans);
return ;
}
int main(){
int T = 1;
while(T--){
Solve();
}
return 0;
}
D - 语文课上的“硬币游戏”
题目描述
上语文课的时候,小\(C\)的老师看着大家都无精打采,死气沉沉的,为了活跃课堂的气氛,老师决定让大家一起玩个游戏,老师听说体育课的时候大家一起玩了一个“硬币游戏”,于是也决定玩这个,可是老师又不想和体育课的那个游戏一模一样,不然就太没意思了,于是修改了游戏规则。
现在教室里同学的座位是一个 \(n\times m\)的矩阵,给每个同学发 \(a_{i,j}\)个硬币,”挑战者“进行选人拿硬币(”挑战者“离开座位的时候,老师会到“挑战者”的座位,不会存在空位),现在拿硬币的规则如下:
1.每次拿硬币时须从每行拿走一个硬币,共\(n\)个。\(m\)次后拿走所有同学的硬币;
2.每次拿走的硬币只能是行首或者行尾的硬币(每一行有硬币的第一个同学或者有硬币的最后一个同学);
3.每次拿硬币都有一个得分值,为每行拿硬币的得分之和,**每行取数的得分 = 被拿走的硬币个数 \(\times\) \(2^i\) ** ,其中\(i\)表示第\(i\)次拿硬币(从\(i\)开始编号);
4.游戏结束总分为\(m\)次拿硬币得分之和。
小\(C\)想让你帮忙写一个程序,对于任意的矩阵,可以求出拿硬币后的最大得分。
输入格式
第\(1\)行为两个用空格隔开的整数\(n\)和\(m\)。\((1\leq n,m \leq 80)\)
接下来的 \(n\) 行每行 \(m\) 个整数,表示队伍中每个同学手上的硬币数 \(a_{(i,j)}\)。\((0 \leq a_{i,j} \leq 1000)\)
输出格式
输出一个整数,即拿硬币之后的最大得分
样例#1
输入样例#1
2 3
1 2 3
3 4 2
输出样例#1
82
样例#2
输入样例#2
1 4
4 5 0 5
输出样例#2
122
样例#3
输入样例#3
2 10
96 56 54 46 86 12 23 88 80 43
16 95 18 29 30 53 88 83 64 67
输出样例#3
316994
提示说明
对于第一组数据:
第\(1\)次:第\(1\)行取行首元素,第\(2\)行取行尾元素,本次得分为\(1 \times 21 + 2 \times 21 = 6\),
第\(2\)次:两行均取行首元素,本次得分为\(2 * 22 + 3 * 22 = 20\),
第\(3\)次:得分为\(3 \times 23 + 4 \times 23 = 56\)。
总得分为\(6 + 20 + 56 = 82\)
题解
知识点:动态规划
这是一个区间\(dp\)问题 从题目描述我们可以知道行与行之间没有联系
于是我们可以设\(dp[i][j]\)为某一行\(i-j\)范围内所得值最大的取值方法 于是转移状态就出来了
取数变成了向这个区间加数 加的数为下次第一个取的
则有转移方程 $if $ $len==1 $ \(dp[l][r]=a[l] * 2\)
\(else\) \(dp[l][r]=max((dp[l+1][r]+a[l])*2\),\((dp[l][r-1]+a[r])*2)\);
#include <bits/stdc++.h>
#define ll __int128
#define endl '\n'
using namespace std;
ll n, m, a[110], ans, dp[110][110];
inline ll read() {
ll x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar();}
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(ll x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
void Solve(){
n = read(); m = read();
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
a[j] = read();
}
for(int len = 1; len <= m; ++len){
for(int l = 1; l+len-1 <= m; ++l){
int r = l+len-1;
if (len == 1) dp[l][r] = a[l]*2;
else dp[l][r] = max((dp[l+1][r]+a[l])*2, (dp[l][r-1]+a[r])*2);
///向区间两边加的数为第一个取的
}
}
ans += dp[1][m];
}
print(ans);
return ;
}
int main(){
int T = 1;
// cin >> T;
while(T--){
Solve();
}
return 0;
}

浙公网安备 33010602011771号