P1449 后缀表达式
题目链接: P1449
参考思路:
如果遇到的字符是数字,则将连续的数字转成整数,并把操作数后面的"."过滤掉;如果遇到是字符是运算符,则取出操作数栈栈顶的两个元素进行计算,并把结果压回去;如果是“@”,程序结束,输出栈顶(栈底)元素。
C++参考代码
#include <bits/stdc++.h>
using namespace std;
int n, m,i,j,k, a[1010];
string s;
int getint(){//从字符串中获取一个操作数int n=0;while(s[i]>='0'&& s[i]<='9'){n = n * 10 + s[i++]- 48;}i++; //s[i]= ·过滤操作数后面的“.”return n;return n;
}
void cal(){//取出两个操作数根据运算符进行计算if(s[i] == '+')a[n-1] += a[n];else if(s[i] == '-')a[n-1] -= a[n];else if(s[i] == '*')a[n-1] *= a[n];else if(s[i] == '/')a[n-1] /= a[n];n--, i++;//操作数少一个,字符串后移一位
}
int main(){cin>>s;while(s[i] != '@'){if(s[i]< 48)cal();//遇到运算符则计算else a[++n]= getint();//读取操作数}cout<<a[1];return 0;
}
java参考代码
import java.util.Scanner;public class Main {static int n = 0, i = 0; // 定义静态变量n和istatic int[] a = new int[1010]; // 定义一个静态整数数组a,长度为1010static String s; // 定义静态字符串spublic static void main(String[] args) {Scanner scanner = new Scanner(System.in);s = scanner.nextLine(); // 从标准输入读取一行字符串while (i < s.length() && s.charAt(i) != '@') { // 循环直到字符串结束或遇到'@'if (s.charAt(i) < '0') { // 如果字符不是数字,则是运算符calculate(); // 调用calculate方法进行计算} else { // 如果字符是数字a[++n] = getInt(); // 调用getInt方法获取整数并存储在数组a中}}System.out.println(a[1]); // 输出数组a的第一个元素scanner.close(); // 关闭Scanner对象}static int getInt() { // 从字符串中获取一个整数的方法int num = 0; // 定义并初始化变量numwhile (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9') { // 循环直到非数字字符num = num * 10 + (s.charAt(i++) - '0'); // 将字符转换为数字并累加到num}if (i < s.length() && s.charAt(i) == '.') { // 如果数字后面跟着一个点'.'i++; // 跳过这个点}return num; // 返回获取到的整数}static void calculate() { // 根据运算符进行计算的方法switch (s.charAt(i)) { // 根据当前字符选择运算符case '+':a[n - 1] += a[n]; // 加法运算break;case '-':a[n - 1] -= a[n]; // 减法运算break;case '*':a[n - 1] *= a[n]; // 乘法运算break;case '/':a[n - 1] /= a[n]; // 除法运算break;}n--; // 用掉一个操作数i++; // 字符串指针后移一位}
}
python参考代码
def getint(s, i):n = 0while i < len(s) and '0' <= s[i] <= '9':n = n * 10 + int(s[i])i += 1i += 1 # 跳过数字后面的句点return n, idef cal(stack, operator):b = stack.pop()a = stack.pop()if operator == '+':stack.append(a + b)elif operator == '-':stack.append(a - b)elif operator == '*':stack.append(a * b)elif operator == '/':stack.append(a // b) # 使用整数除法def main():s = input()i = 0stack = []while i < len(s) and s[i] != '@':if '0' <= s[i] <= '9':number, i = getint(s, i)stack.append(number)else:cal(stack, s[i])i += 1print(stack[0])if __name__ == '__main__':main()
P1540机器翻译
题目链接: P1540
参考思路:
由于本题数据范围较小,我们可以采用指针的方法来解决。
首先,我们创建两个数组。一个数组 a
用于存储标记。在读入单词 x
时,若当前数组 a
中的第 x
位的标记为 1
,则表示该单词在内存中;若标记为 0
,则表示该单词不在内存中。这样做可以一步判断读入的单词当前是否在内存中,而不必从头到尾搜索。此外,在存入单词时,只需将数组 a
中的第 x
位的标记从 0
改为 1
;在删除内存中的单词 x
时,只需将数组 a
的第 x
位的标记从 1
改为 0
。这样一来,我们可以在一步到位,大大降低时间复杂度,提高程序效率。这是本题的关键之一,需要仔细体会和理解。
接着,我们创建另一个数组 b
,用于存储内存中的单词,并按读入顺序存入。例如,b[1]
中存储的单词 x
是在时间1存入的。需要注意的是,如果当前读入的单词 x
已经在内存中(即 a[x]==1
),则不必存入数组 b
中;只有遇到内存中没有的新单词才需要存入。
然后,我们需要引入两个指针。一个是 l
,指向当前内存中最先存入的单词,例如 b[l]
是当前内存中第一个存入的单词。另一个是 r
,指向当前内存中最后一个存入的单词,例如 b[r]
是当前内存中最后一个存入的单词。因此,数组 b
的第 l
位到第 r
位存储的就是当前内存中的单词。
当遇到新单词时(即 a[x]==0
),情况有两种:
- 如果内存未被用完(即
r<=m
),则指针r
向右移一位,在b[r]
中存入新单词,并将数组a
中单词x
的标记改为1
。 - 如果内存已满(即
r>m
),则先删除当前内存中最先存入的单词b[l]
。删除操作不需要太复杂,只需将指针l
向右移一位,然后再修改数组a
中b[l]
位置的标记即可。最后别忘了在b[r]
中存入新单词x
。
c++参考代码
#include <bits/stdc++.h>
using namespace std;
int n,m,x,ans,l,r,a[1005],b[1005];
int main(){cin>>m>>n;l=0;r=0;//初始化两个指针for (int i=1;i<=n;i++){cin>>x;//边读入边做if (a[x]==0) {ans++;r++;b[r]=x;a[x]=1;//因为每次遇到新单词都要做这些操作,不如搬到判断语句外做,这样程序更简洁if (r>m) {l++;a[b[l]]=0;}}}cout<<ans;return 0;
}
java参考代码
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int m = scanner.nextInt(); // 窗口或缓存的最大大小int n = scanner.nextInt(); // 总输入数int[] a = new int[1005]; // 用于检查项目是否在缓存中int[] b = new int[1005]; // 用于存储缓存中的项目(类似队列)int l = 0, r = 0, ans = 0; // l 和 r 是缓存窗口的指针,ans 是唯一项目的计数for (int i = 1; i <= n; i++) {int x = scanner.nextInt(); // 输入项目if (a[x] == 0) { // 如果项目尚未在缓存中ans++; // 增加唯一计数r++;b[r] = x; // 将项目添加到缓存中a[x] = 1; // 将项目标记为在缓存中if (r > m) { // 如果缓存超过其大小l++;a[b[l]] = 0; // 从缓存中移除最旧的项目}}}System.out.println(ans); // 输出唯一项目的计数scanner.close();}
}
python参考代码
m, n = map(int, input().split()) # 同时读取 m 和 n
inputs = list(map(int, input().split())) # 读取整行输入并转换为整数列表a = [0] * 1005 # 初始化数组 a
b = [0] * 1005 # 初始化数组 b
ans = 0
l = 0
r = 0for x in inputs: # 直接迭代处理输入的整数列表if a[x] == 0:ans += 1r += 1b[r] = xa[x] = 1if r > m:l += 1a[b[l]] = 0print(ans)
P9290 Decryption
题目链接: p9290
参考思路:
考虑到"最小"和"最大"对于序列的限制,我们可以采用单调栈来优化数组的维护。对于这道题目,我们需要使用两个栈进行维护:一个是严格单调递减的栈(记作 stk1[n]
),另一个是单调递增的栈(记作 stk2[n]
)(关于为何使用这两种单调栈的问题建议配合后面的 AC 代码食用)。
其次,这道题还需要利用动态规划(DP)来求解。我们定义一个一维数组 dp[n]
,对于任意一个数组下标 i
,dp[i]
表示到 i
坐标处,分割出来的序列全为合法序列的最小分割次数。
然后我们来构思状态转移方程,试想,如果当前坐标 i
后(注意是 i
坐标后,否则状态转移会出问题)需要进行分割以构成合法序列,上一次开始分割的坐标 j
需要尽可能地小才能保证转移出的结果是最小的。
有了这样的想法,这时候就应该与前面提到的单调栈来配合了。由于这道题对数组内的顺序有严格要求,因此在使用单调栈时,栈中存储的不是数组变量的值,而是数组变量的下标。
c++参考代码
#include<bits/stdc++.h>
using namespace std;
int n,a[300005],dp[300005],top1,top2,stk1[300005],stk2[300005];
//stk1:严格单调递减栈 stk2:单调递增栈
int main()
{cin>>n;for(int i = 1; i <= n; i++) scanf("%d",&a[i]);for(int i = 1; i <= n; i++){while(top1 && a[stk1[top1]] <= a[i]) top1--;stk1[++top1] = i;while(top2 && a[stk2[top2]] > a[i]) top2--;stk2[++top2] = i;//维护两个单调栈 dp[i] = dp[*lower_bound(stk2 + 1,stk2 + top2 + 1,stk1[top1 - 1]) - 1] + 1;}cout<<dp[n];return 0;
}
java参考代码
//注意!java版本500ms左右过不去
import java.util.*;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int[] a = new int[n + 1];int[] mn = new int[n + 1];int[] mx = new int[n + 1];Deque<Integer> mns = new ArrayDeque<>();Deque<Integer> mxs = new ArrayDeque<>();mns.addLast(0); // Initialize with 0 to handle base casemxs.addLast(0);for (int i = 1; i <= n; i++) {a[i] = scanner.nextInt();while (!mns.isEmpty() && a[i] < a[mns.peekLast()]) {mns.removeLast();}while (!mxs.isEmpty() && a[i] >= a[mxs.peekLast()]) {mxs.removeLast();}mn[i] = mns.isEmpty() ? 0 : mns.peekLast(); // Check if empty before accessingmx[i] = mxs.isEmpty() ? 0 : mxs.peekLast(); // Check if empty before accessingmns.addLast(i);mxs.addLast(i);}int res = 0;for (int i = n; i > 0; ) {int j = i;// Ensure that mn[j] is a valid indexwhile (mx[i] < mn[j] && mn[j] > 0) {j = mn[j];}i = j - 1;res++;}System.out.println(res);scanner.close();}
}
python参考代码
此题 Python不合适 正式比赛不会出现这种情况。
P1725 琪露诺
题目链接: P1725
参考思路:
假设f[i]表示跳到第 i 个格子的最优值。
初始状态为f[0]=0,表示在原点的价值为0。
状态转移方程为:
f [ i ] = max j ∈ [ i − r , i − l ] { f [ j ] + a [ i ] } f[i] =\max_{j\in[i-r,i-l]}\{f[j]+a[i]\} f[i]=j∈[i−r,i−l]max{f[j]+a[i]}
最终状态为 max(f[n+1],…,f[n+l]),因为琪露诺最终跳到的区间范围是 [n+1,n+l]。
为了优化这一过程,我们观察到 f[i] 的取值只能由决策区间[i−r,i−l] 转移而来,而 a[i] 是定值,因此我们只需考虑决策区间 [i−r,i−l] 对 f[i] 的影响。
根据动态规划的转移方程,我们需要求决策的最大值。因此,我们可以采用一种数据结构来维护决策的最优值。
考虑到每个f[i] 对应一个决策区间,而区间移动的长度是固定的,这启发我们联想到滑动窗口这个概念。我们可以通过动态维护区间的最大值,从而省去一维枚举决策的时间。
维护区间最值可以使用优先队列或者单调队列两种方式。
首先,考虑优先队列的做法。对于堆中的元素,我们需要使用一个 vis[i] 数组来表示是否在队列中,并检查维护的区间是否超出了决策区间,直到删除堆顶元素使其符合条件为止。
每次移动的操作时间复杂度为 O(logn),因此总时间复杂度为 O(nlogn)。
c++参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+50;
int n,l,r,a[N],f[N],vis[N],ans=-1e9,use[N];//use[i]标记f[i]是否合法
priority_queue< pair<int,int> > q;
int main(){cin>>n>>l>>r;for(int i = 1; i <= 2 * n; i++) f[i]=-1e9;for(int i = 0; i <= n; i++) cin>>a[i];for(int i = l; i <= r; i++) use[i]=1;for(int i = 0; i <= n; i++){if(i<l) {q.push(make_pair(-1e9,0));f[i+l]=a[i+l];continue;}int y=q.top().second; if(i-r+l-1>=0) vis[i-r+l-1]=1;while(vis[y]||!use[y]) { q.pop(); if(q.empty()) break; y=q.top().second;} //删除堆顶不合法的决策q.push(make_pair(f[i],i));f[i+l]=q.top().first+a[i+l];if(use[i]) use[i+l]=1;}for(int i = n + 1; i <= n + l; i++) if(use[i]) ans=max(ans,f[i]);cout<<ans;return 0;
}
java参考代码
import java.util.*;public class Main {static final int N = 300050; static int[] a = new int[N];static int[] f = new int[N];static boolean[] vis = new boolean[N];static boolean[] use = new boolean[N];static int ans = Integer.MIN_VALUE; // 相当于 C++ 中的 -1e9public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int l = sc.nextInt();int r = sc.nextInt();Arrays.fill(f, Integer.MIN_VALUE); // 使用 -1e9 初始化 f[]for (int i = 0; i <= n; i++) {a[i] = sc.nextInt();}for (int i = l; i <= r; i++) {use[i] = true;}PriorityQueue<Pair> q = new PriorityQueue<>((x, y) -> y.first - x.first); // 基于第一个元素的最大堆for (int i = 0; i <= n; i++) {if (i < l) {q.offer(new Pair(Integer.MIN_VALUE, 0)); // 入队 -1e9,0f[i + l] = a[i + l];continue;}int y = q.peek().second;if (i - r + l - 1 >= 0) {vis[i - r + l - 1] = true;}while (vis[y] || !use[y]) {q.poll();if (q.isEmpty()) break;y = q.peek().second;}q.offer(new Pair(f[i], i));f[i + l] = q.peek().first + a[i + l];if (use[i]) use[i + l] = true;}for (int i = n + 1; i <= n + l; i++) {if (use[i]) ans = Math.max(ans, f[i]);}System.out.println(ans);sc.close();}static class Pair {int first;int second;public Pair(int first, int second) {this.first = first;this.second = second;}}
}
python参考代码
此题 Python不合适 正式比赛不会出现这种情况。