本文搬运自本人初中博客园博客,若图片加载不出来,可到原文查看:https://www.cnblogs.com/zhangtingxi/p/15990701.html
Part A 单调队列
何为单调队列?
单调队列 (Monotone queue )即单调递减或单调递增的队列。
例:滑动窗口
T1
题目
对于一个长为 N N N 的序列,求所有从左到右长为 K K K 的区间最大值 和最小值 。
N , K ⩽ 10 6 N,K\leqslant 10^6 N , K ⩽ 1 0 6
思路
以最大值为例,维护一个从大到小的队列,从队首到队尾单调不升。
这个队列维护的是 [ i , i + k − 1 ] [i,i+k-1] [ i , i + k − 1 ] 的值,则此时最大值为队首。
然后考虑 [ i + 1 , i + k ] [i+1, i+k] [ i + 1 , i + k ] 区间的最大值。
上一个区间的最大值能不能用呢?
我们只需要判断当前队首的编号是否大于 i i i 即可。
如果不大于 i i i 就pop掉。
这里主要是运用一种思想,删掉多余的元素。
考虑两个值 a i , a j a_i,a_j a i , a j ,若 i < j i<j i < j 且 a i < a j a_i<a_j a i < a j ,则 a i a_i a i 是完全没有意义的。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> using namespace std;int n, m, i, j, k;int dui[1000010 ],front,rear;int a[1000010 ];int main () { scanf ("%d%d" , &n, &k); for (i=1 ; i<=n; ++i) scanf ("%d" , &a[i]); for (i=1 ; i<=k; ++i) { while (rear && a[i]<a[dui[rear]]) --rear; dui[++rear]=i; } printf ("%d " , a[dui[1 ]]); for (i=k+1 ; i<=n; ++i) { while (rear>front && a[dui[rear]]>a[i]) --rear; dui[++rear]=i; while (dui[front]<i-k+1 && front<rear) ++front; printf ("%d " , a[dui[front--]]); } printf ("\n" ); memset (dui, front=rear=0 , sizeof (dui)); for (i=1 ; i<=k; ++i) { while (rear && a[i]>a[dui[rear]]) --rear; dui[++rear]=i; } printf ("%d " ,a[dui[1 ]]); for (i=k+1 ; i<=n; ++i) { while (rear>front && a[i]>a[dui[rear]]) --rear; dui[++rear]=i; while (dui[front]<i-k+1 &&front<rear) ++front; printf ("%d " ,a[dui[front--]]); } return 0 ; }
T2
题目
给出一个长为 N N N 的序列,求长度不超过 K K K 的连续子序列的最大和。
N , K ⩽ 2 × 10 5 N, K\leqslant 2\times 10^5 N , K ⩽ 2 × 1 0 5
思路
方法一:双指针
方法二:前缀和+堆优化
我们先对整个序列求一遍前缀和
我们考虑当前以位置 r r r 结尾,则位置 l l l 要满足什么?
满足区间长度小于等于 k k k ,即:r − k + 1 ⩽ l ⩽ r r-k+1\leqslant l\leqslant r r − k + 1 ⩽ l ⩽ r
满足区间 [ l , r ] [l,r] [ l , r ] 之和最大,就是 S r − S l − 1 S_r-S_{l-1} S r − S l − 1 最大。
先考虑如何满足条件2?
我们可以拿个小根堆来维护,那么就可以保证 S l S_l S l 最小。
如何满足条件1?
每次对于小根堆的堆顶,使得其满足条件1即可。
方法三:前缀和+单调队列
让我们考虑刚刚那种方法,我们能不能把优先队列改为单调队列。
要使能用单调队列,就必须满足两个条件。
由于单调队列满足单调性,所以条件2显然可以满足。
而我们刚刚是每次对小根堆的堆顶进行条件1判断,而此时我们同样可以对单调队列的队首进行类似维护。
时间复杂度 O ( n ) O(n) O ( n ) 。
Part B 单调队列优化dp的引入
T3
题目
求一个长为 N N N 的序列选出任意多个数的最大和。需满足不存在连续 M M M 个数。
N , M ⩽ 10 5 N,M\leqslant 10^5 N , M ⩽ 1 0 5
思路
思路一
考虑暴力做法,设 d p i dp_i d p i 表示前 i i i 个数选且第 i i i 个数不选的最大和。
d p i = max j = i − M − 1 i − 1 ( d p j + S i − 1 − S j ) ( 1 ⩽ i ⩽ N + 1 ) dp_i=\max_{j=i-M-1}^{i-1}(dp_j+S_{i-1}-S_j)\,\,\,(1\leqslant i\leqslant N+1)
d p i = j = i − M − 1 max i − 1 ( d p j + S i − 1 − S j ) ( 1 ⩽ i ⩽ N + 1 )
答案即为:
a n s = max i = 1 n + 1 d p j ans=\max_{i=1}^{n+1} dp_j
an s = i = 1 max n + 1 d p j
Code1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> using namespace std;#define int long long #define N 100010 int n, m, i, j, k, p; int a[N], dp[N], sum[N];signed main () { scanf ("%lld%lld" , &n, &k); for (i=1 ; i<=n; ++i) { scanf ("%lld" , &a[i]); sum[i]=sum[i-1 ]+a[i]; } for (i=1 ; i<=n+1 ; ++i) for (j=max ((long long )(0 ), i-k-1 ); j<=i-1 ; ++j) dp[i]=max (dp[i], dp[j]+sum[i-1 ]-sum[j]); printf ("%lld" ,dp[n+1 ]); return 0 ; }
思路二
我们看回刚刚的式子:
d p i = max j = i − M − 1 i − 1 ( d p j + S i − 1 − S j ) ( 1 ⩽ i ⩽ N + 1 ) dp_i=\max_{j=i-M-1}^{i-1}(dp_j+S_{i-1}-S_j)\,\,\,(1\leqslant i\leqslant N+1)
d p i = j = i − M − 1 max i − 1 ( d p j + S i − 1 − S j ) ( 1 ⩽ i ⩽ N + 1 )
可以变换一下:
d p i = max j = i − M − 1 i − 1 ( d p j − S j ) + S i − 1 ( 1 ⩽ i ⩽ N + 1 ) dp_i=\max_{j=i-M-1}^{i-1}(dp_j-S_j)+S_{i-1}\,\,\,(1\leqslant i\leqslant N+1)
d p i = j = i − M − 1 max i − 1 ( d p j − S j ) + S i − 1 ( 1 ⩽ i ⩽ N + 1 )
观察可以得到 d p j − S j dp_j-S_j d p j − S j 这一段只有 j j j 这一个变量,所以可以用单调队列来维护。
Code2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std;#define int long long #define N 100010 struct node { int x, id; }dui[N]; int n, m, i, j, k, p; int a[N], dp[N], sum[N];int front, rear;signed main () { scanf ("%lld%lld" , &n, &k); for (i=1 ; i<=n; ++i) { scanf ("%lld" , &a[i]); sum[i]=sum[i-1 ]+a[i]; } dui[++rear].x=0 ; dui[rear].id=0 ; for (i=1 ; i<=n+1 ; ++i) { while (front<rear && dui[front+1 ].id<i-k-1 ) ++front; dp[i]=max (dp[i], dp[dui[front+1 ].id]+sum[i-1 ]-sum[dui[front+1 ].id]); while (front<rear && dui[rear].x+sum[i]-sum[dui[rear].id]<dp[i]) --rear; dui[++rear].x=dp[i]; dui[rear].id=i; } printf ("%lld" ,dp[n+1 ]); return 0 ; }
T4
题目
给定一个长为 N N N 的序列,对其中一些数进行操作,满足所有长度为 M M M 的连续子序列中都至少存在一个数进行过操作。
N , M ⩽ 2 × 10 5 N,M\leqslant 2\times 10^5 N , M ⩽ 2 × 1 0 5
思路
先把暴力dp弄出来。
设 d p i dp_i d p i 表示前 i i i 个数中满足对 i i i 个数进行操作后的最少操作次数。
d p i = min j = i − M i − 1 d p j + a i dp_i=\min_{j=i-M}^{i-1}dp_j+a_i
d p i = j = i − M min i − 1 d p j + a i
显然,对 d p j dp_j d p j 进行单调队列维护即可。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> using namespace std;#define N 1000010 int n, m, i, j, k;int dui[N], a[N], f[N];int front=1 , rear=1 ; int main () { scanf ("%d%d" , &n, &m); for (i=1 ; i<=n; ++i) scanf ("%d" , &a[i]); dui[1 ]=0 ; for (i=1 ; i<=n+1 ; ++i) { while (rear>=front&&dui[front]<i-m) ++front; f[i]=a[i]+f[dui[front]]; while (rear>=front&&f[i]<f[dui[rear]]) --rear; dui[++rear]=i; } printf ("%d" , f[n+1 ]); return 0 ; }
Part C 单调队列优化dp的综合应用
T5
题目
给出一个有 N N N 个车站的环形公路,每个车站有一定的汽油可供行驶一段距离,相邻两个车站之间也有一定距离,问从每个车站出发是否能环游一周
N ⩽ 10 6 N\leqslant 10^6 N ⩽ 1 0 6
思路
环形问题难搞,考虑破环成链。
破成一个长度为 2 n 2n 2 n 的车站序列,让我们先考虑顺时针的情况。
让我们对汽车油量和距离分别做前缀和 s 1 , s 2 s1, s2 s 1 , s 2 ,如果从第 i i i 个点出发可以回到起点,就意味着在 [ i , i + n − 1 ] [i, i+n-1] [ i , i + n − 1 ] 的区间内,满足所有油量与距离之差的前缀和都大于等于0,即满足 ( s 1 j − s 1 i − 1 ) − ( s 2 j − s 2 i − 1 ) ⩾ 0 ( j ∈ [ i , i + n − 1 ] ) (s1_j-s1_{i-1})-(s2_j-s2_{i-1})\geqslant 0\,\,\,(j\in [i, i+n-1]) ( s 1 j − s 1 i − 1 ) − ( s 2 j − s 2 i − 1 ) ⩾ 0 ( j ∈ [ i , i + n − 1 ]) 。
可是这样子不好维护,于是我们可以尝试对汽车油量与距离之差做前缀和,记为 s s s 。
有了 s s s 数组,我们就可以求区间最小值减去 s i − 1 s_{i-1} s i − 1 与0作比较,相当于做一个长度为 n n n 的滑动窗口。
逆时针同理。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;#define int long long #define N 1000010 int n, m, i, j, k;int o[N], d[N], s[N<<1 ], ans[N];pair<int , int >p; deque<pair<int , int > >q; signed main () { scanf ("%d" , &n); for (i=1 ; i<=n; ++i) scanf ("%d%d" , &o[i], &d[i]); for (i=1 ; i<=2 *n; ++i) s[i]=s[i-1 ]+o[(i-1 )%n+1 ]-d[(i-1 )%n+1 ]; for (i=1 ; i<=n; ++i) { while (!q.empty () && q.back ().first>s[i]) q.pop_back (); p.first=s[i]; p.second=i; q.push_back (p); } for (i=n+1 ; i<=2 *n; ++i) { ans[i-n]=q.front ().first-s[i-n-1 ]; while (!q.empty () && i-q.front ().second+1 >n) q.pop_front (); while (!q.empty () && q.back ().first>s[i]) q.pop_back (); p.first=s[i]; p.second=i; q.push_back (p); } q.clear (); for (i=1 , j=2 *n; i<=2 *n; ++i, --j) s[i]=s[i-1 ]+o[(j-1 )%n+1 ]-d[((j-1 >0 ? j-1 : n)-1 )%n+1 ]; for (i=1 ; i<=n; ++i) { while (!q.empty () && q.back ().first>s[i]) q.pop_back (); p.first=s[i]; p.second=i; q.push_back (p); } for (i=n+1 ; i<=2 *n; ++i) { ans[2 *n-i+1 ]=max (ans[2 *n-i+1 ], q.front ().first-s[i-n-1 ]); while (!q.empty () && i-q.front ().second+1 >n) q.pop_front (); while (!q.empty () && q.back ().first>s[i]) q.pop_back (); p.first=s[i]; p.second=i; q.push_back (p); } for (i=1 ; i<=n; ++i) printf (ans[i]>=0 ? "TAK\n" : "NIE\n" ); return 0 ; }
总结
这道题的思路挺巧妙的。
首先对于这类题,可以先考虑破环成链,这是这类题目的常见思考方向。
然后,我们对于题目的本质进行分析,然后可以想出差值前缀和。
最后,我们发现求最小值的过程中可以使用数据结构,即单调队列进行优化,然后就能做出此题。
T6
题目
有 N N N 道题,每道题需要 a i a_i a i 分钟来做。现在有 t t t 分钟来做题,问最短空题段为多少。
n ⩽ 2000 , t ⩽ 10 8 n\leqslant 2000 ,t\leqslant 10^8 n ⩽ 2000 , t ⩽ 1 0 8
思路
由于最长长度和最短时间都不确定。我们可以假设其中一项确定来思考。
考虑二分答案,二分最长空题段。
假设二分当前最长空题段为 k k k ,我们就可以尝试dp处理。
设 d p i dp_i d p i 表示第 i i i 题做,且前 i i i 题的最长空题段小于等于 k k k 时的最短时间,我们可以枚举上一条做了的题,即:
d p i = min max ( j = i − k − 1 , 0 ) i − 1 d p j + a i dp_i=\min_{\max(j=i-k-1, 0)}^{i-1}dp_j+a_i
d p i = m a x ( j = i − k − 1 , 0 ) min i − 1 d p j + a i
答案我们可以在 [ n − k − 1 , n ] [n-k-1, n] [ n − k − 1 , n ] 里枚举最后一道题的位置,时间复杂度 O ( n 2 log n ) O(n^2\log n) O ( n 2 log n ) 。
显然,min max ( j = i − k − 1 , 0 ) i − 1 d p j \min_{\max(j=i-k-1, 0)}^{i-1}dp_j min m a x ( j = i − k − 1 , 0 ) i − 1 d p j 可以用单调队列优化,时间复杂度 O ( n log n ) O(n\log n) O ( n log n ) 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std;#define int long long #define N 500010 int n, m, i, j, k;int l, r, mid, ans;int dp[N], a[N], t;pair<int , int >p; deque<pair<int , int > >q; int check (int k) { q.clear (); p.first=0 ; p.second=0 ; q.push_back (p); for (i=1 ; i<=n; ++i) { dp[i]=q.front ().first+a[i]; while (!q.empty () && i-q.front ().second>k) q.pop_front (); while (!q.empty () && dp[i]<=q.back ().first) q.pop_back (); p.first=dp[i]; p.second=i; q.push_back (p); } ans=0x7fffffffffffffff ; for (i=n-k-1 ; i<=n; ++i) ans=min (ans, dp[i]); return ans<=t; } signed main () { scanf ("%lld%lld" , &n, &t); for (i=1 ; i<=n; ++i) scanf ("%lld" , &a[i]); l=1 , r=n; while (l<r) { mid=(l+r)>>1 ; if (check (mid)) r=mid; else l=mid+1 ; } printf ("%lld" , l); return 0 ; }
T7
题目
在一个 a × b a×b a × b 的矩阵中找出一个 n × n n×n n × n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
2 ≤ a , b ≤ 1000 , n ≤ a , n ≤ b , n ≤ 100 2\le a,b\le 1000,n\le a,n\le b,n\le 100 2 ≤ a , b ≤ 1000 , n ≤ a , n ≤ b , n ≤ 100
思路
显然我们可以枚举上界,则此时上下界可以确定。
那么在这种情况下,这个正方形可以为如下图中的红色区域。
那哪个红色区域是最优的呢?
这里我们可以把它看作一个二维的滑动窗口,最大最小值预处理一下即可。
枚举上下界是 O ( n ) O(n) O ( n ) 的,横着扫一遍是 O ( n ) O(n) O ( n ) 的,总复杂度为 O ( n 2 ) O(n^2) O ( n 2 ) 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std;#define N 1010 int n, m, i, j, k;int a, b, c, ans=0x7fffffff ;int l, r, mid;int mn[N][N][11 ], mx[N][N][11 ];int mxx, mnn;signed main () { scanf ("%d%d%d" , &a, &b, &n); for (i=1 ; i<=a; ++i) for (j=1 ; j<=b; ++j) scanf ("%d" , &mx[i][j][0 ]), mn[i][j][0 ]=mx[i][j][0 ]; for (i=1 ; i<=a; ++i) for (k=1 ; k<=10 ; ++k) for (l=1 , r=l+(1 <<k-1 ); l+(1 <<k)-1 <=b; ++l, ++r) { mn[i][l][k]=min (mn[i][l][k-1 ], mn[i][r][k-1 ]), mx[i][l][k]=max (mx[i][l][k-1 ], mx[i][r][k-1 ]); } c=log2 (n); for (i=1 ; i+n-1 <=a; ++i) for (j=1 ; j+n-1 <=b; ++j) { mxx=0xffffffff ; mnn=0x7fffffff ; for (k=i; k<=i+n-1 ; ++k) mxx=max (mxx, max (mx[k][j][c], mx[k][j+n-1 -(1 <<c)+1 ][c])), mnn=min (mnn, min (mn[k][j][c], mn[k][j+n-1 -(1 <<c)+1 ][c])); ans=min (ans, mxx-mnn); } printf ("%d" , ans); return 0 ; }
T8
题目
现在已知 T T T 天内每天股票的入价 A P i AP_i A P i ,出价 B P i BP_i B P i ,每天最多买 A S i AS_i A S i 股,最多卖 B S i BS_i B S i 股,每次交易之间至少隔 W W W 天,任意时候手上持股数量不得超过 M a x P MaxP M a x P ,问 T T T 天后最多赚多少钱。
0 ≤ W < T ≤ 2000 , 1 ≤ MaxP ≤ 2000 0\leq W<T\leq 2000,1\leq\text{MaxP}\leq2000 0 ≤ W < T ≤ 2000 , 1 ≤ MaxP ≤ 2000
思路
明显的dp。
设 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 表示第 i i i 天持股为 j j j 的最大钱数,首先可以从上一天或者第0天推导过来。
然后考虑买入的情况,由于我们每次由上一天推导过来,具有传递性,所以我们可以由第 i − w − 1 i-w-1 i − w − 1 天推导过来。
假设第 i − w − 1 i-w-1 i − w − 1 天持股数量为 k k k ,那么需满足 j − a s i ⩽ k ⩽ j − 1 j-as_i\leqslant k\leqslant j-1 j − a s i ⩽ k ⩽ j − 1 ,转移为:
d p [ i ] [ j ] = d p [ i − w − 1 ] [ k ] − ( j − k ) × a p i dp[i][j]=dp[i-w-1][k]-(j-k)\times ap_i
d p [ i ] [ j ] = d p [ i − w − 1 ] [ k ] − ( j − k ) × a p i
可是这样的时间复杂度达到三次方级别,于是我们对上面的式子变换一下:
d p [ i ] [ j ] = ( d p [ i − w − 1 ] [ k ] + k × a p i ) − j × a p i dp[i][j]=(dp[i-w-1][k]+k\times ap_i)-j\times ap_i
d p [ i ] [ j ] = ( d p [ i − w − 1 ] [ k ] + k × a p i ) − j × a p i
明显,前面那一部分可以用单调队列优化。
卖出情况同理。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;#define int long long #define N 2010 int n, m, i, j, k;int ap[N], bp[N], as[N], bs[N];int dp[N][N], ans, w, mx;pair<int , int >p; deque<pair<int , int > >q1, q2; signed main () { memset (dp, 0x80 , sizeof (dp)); scanf ("%lld%lld%lld" , &n, &m, &w); for (i=1 ; i<=n; ++i) scanf ("%lld%lld%lld%lld" , &ap[i], &bp[i], &as[i], &bs[i]); for (i=1 ; i<=n; ++i) { for (j=0 ; j<=m; ++j) { dp[i][j]=dp[i-1 ][j]; if (j<=as[i]) dp[i][j]=max (dp[i][j], -j*ap[i]); } if (i<=w) continue ; for (j=0 ; j<=m; ++j) { while (!q1. empty () && q1.f ront().second<j-as[i]) q1. pop_front (); while (!q1. empty () && q1. back ().first<=dp[i-w-1 ][j-1 ]+(j-1 )*ap[i]) q1. pop_back (); p.first=dp[i-w-1 ][j-1 ]+(j-1 )*ap[i]; p.second=j-1 ; q1. push_back (p); dp[i][j]=max (dp[i][j], q1.f ront().first-j*ap[i]); } for (j=m; j>=0 ; --j) { while (!q2. empty () && q2.f ront().second>j+bs[i]) q2. pop_front (); while (!q2. empty () && q2. back ().first<=dp[i-w-1 ][j+1 ]+(j+1 )*bp[i]) q2. pop_back (); p.first=dp[i-w-1 ][j+1 ]+(j+1 )*bp[i]; p.second=j+1 ; q2. push_back (p); dp[i][j]=max (dp[i][j], q2.f ront().first-j*bp[i]); } q1. clear (); q2. clear (); } for (i=0 ; i<=m; ++i) ans=max (ans, dp[n][i]); printf ("%lld" , ans); return 0 ; }
总结
这是一道非常经典的dp加单调队列优化。
dp方面,传递性的思想很巧妙。
有些时候,dp式子需要进行一定变换才能使用单调队列优化。
T9
题目
有 n n n 种面值的硬币,分别为 b 1 , b 2 , ⋯ , b n b_1, b_2,\cdots , b_n b 1 , b 2 , ⋯ , b n 。要凑出面值 k k k ,最少要用多少个硬币。
n ⩽ 2000 , k ⩽ 2 × 10 4 n\leqslant 2000,k\leqslant 2\times 10^4 n ⩽ 2000 , k ⩽ 2 × 1 0 4
思路
单调队列做法略。主要是我没想懂 ,这里介绍一种二进制做法。
单调队列做法留给读者自己探究。
这题我本来不想写的,但是由于在老师布置的题单上,就写了另一种做法。
首先不考虑时间复杂度,这个问题应该是可以用多重背包求解的。
同时,我们也可以把每种面值的货币拆成 c i c_i c i 个,用01背包求解。时间复杂度为 O ( n m 2 ) O(nm^2) O ( n m 2 ) 。
这时我们可以考虑每种面值。设 t = l o g 2 c i t=log_2c_i t = l o g 2 c i ,对于 c i c_i c i 以内的任何一个数,我们都可以用 2 0 , 2 1 , ⋯ , 2 t 2^0, 2^1,\cdots,2^t 2 0 , 2 1 , ⋯ , 2 t 凑出,所以我们并不需要把每种面值的硬币拆成 c i c_i c i 份,拆成 l o g 2 c i log_2c_i l o g 2 c i 份即可。
时间复杂度 O ( n m log m ) O(nm\log m) O ( nm log m ) 。
这是一道经典的多重背包优化题目。
对于多重背包来说,数量的个数限制往往可以用二进制的方法组合表示。
如果一道多重背包的题能拆成01背包,那么它必然可以用二进制的方法进行优化,这是一种常见题型。
Part D 结语
最后,我想以今日英才的一段激励语来结束这篇文章。
人最宝贵的是生命,人的生命只有一次。人的一生应当这样度过:当他回首往事时,他不会因为虚度年华而悔恨;也不会因为生活的庸俗而羞愧;临死的时候,他能够说,我把我的整个生命和全部精力,都献给了全中国最辉煌的事业——OI事业,为中国在21世纪成为世界第一OI强国而奋斗和努力!
让我们用这段光彩夺目的话来鞭策和激励自己!
让自己成为一个无愧于时代的高尚的人!
谢谢您们!
没错,这段是我抄的