考試(一)(標題未取好)
目錄
思路篇
猴子跳樹
題目
有Q只猴子要從第一棵樹到第n棵樹去,第i只猴子一次跳躍的最遠距離為Ki。如果它在第x棵樹,那它最遠可以跳到第x+Ki棵樹。如果第j棵樹的高度比第i棵樹高或相等,那麼它從第i棵樹直接跳到第j棵樹,它的勞累值會增加1。所有猴子一開始在第一棵樹,請問每隻猴子要跳到第n棵樹花費的勞累值最小。
輸入
第一行一個整數n,表示有n棵樹。(2<=n<=1000000)
接下來第二行給出n個正整數D1,D2,……,Dn(1<=Di<=10^9),其中Di表示第i棵樹的高度。
第三行給出了一個整數Q(1<=Q<=25),接下來Q行,給出了每隻猴子一次跳躍的最遠距離Ki(1<=Ki<=N-1)。
輸出
輸出Q行,每行一個整數,表示一隻猴子的最小的勞累值。
樣例輸入
9
4 6 3 6 3 7 2 6 5
2
2
5
樣例輸出
2
1
非正解思路——暴搜+剪枝
主要看怎麼暴搜,暴搜得好,就可以多得一點(畢竟還有什麼剪枝,記憶化等等)
我就說一種大家都能想到的暴搜!
就是我們從當前的樹開始,往下從x+1到x+Ki都嘗試一遍(都暴搜一遍),一直到第n棵樹。
這個時間複雜度我算了算,每個點都要嘗試Ki次,還是Q個猴子,就要暴搜n次,就是O(n*sigma(Ki)),
這個最大的時候就是1000000*25000000=25000000000000 大概要3天左右!
嗯,你會說已經算過的點標記一下,後面就可以剪枝了,話說,你也減不了多少啊,也要個一兩天吧!
所以,這個暴搜肯定是不行的。
可能性正解思路——貪心+單調佇列優化
我就是用貪心做的,但後來聽說有人用貪心做出來了(我自然相信,因為我也相信能過的)
我們的貪心思想就是,後面有比當前這棵樹小的,就跳到比自己小的樹上,萬一有多個比自己小的樹呢?
那我們就跳較大的,這樣的話就可以為後面增加繼續往下跳的可能性。
那沒有比自己小的怎麼辦,那就只有跳最高的那個,增加勞累值啦,為什麼跳最高的,原因自然和上面是一樣的。
那怎麼找後面x+1到x+Ki有沒有比自己小的,怎麼找比自己小的較大的,或者找比自己大的最大的?
第一種 迴圈暴力
大家發現了吧,你上面的暴搜不也是每個結點要嘗試這麼多次嗎?這時間複雜度好像沒有什麼變化啊!
但是還是要快一點的,因為上面的暴搜是每個樹都要嘗試,而我們這裡只嘗試了要跳的樹,所以還是減了不少。
能減多少?可能還是要個半天一天才出來的到吧!
那這個貪心豈不沒有什麼卵用?就多騙了一點點分,一點點分也是分啊!但我們還可以優化!
第二種 單調佇列優化
我們何必去用迴圈呢?直接用單調佇列不就可以了?
什麼是單調佇列,大家自己去看吧,我就不說明了,反正用了單調佇列的話,就只用O(1)就可以找到較大的,直接代替迴圈
這樣的話,我們的時間複雜度就是O(nQ)
就是1000000*25=25000000 可是這個時間複雜度看起來只是很懸罷了,按理說不會超過1秒吧
1秒約等於100000000 我們這裡只有7位數,1秒有8位,這樣算就是250ms
但是很遺憾,我只騙了80%!!!震驚,所以我想說的是,大家以後啊,對於這個估算的時間複雜度,不要太過於相信
因為這個只是一個估計嘛,還有一些小操作我們是忽略了的,我上面那個只要再乘以4就暴了,那些小操作也應該要大於4吧!
所以你的估計要絕對的保守,因為你忽略了小操作,真實的時間複雜度肯定比這個高(除非你自己算錯了)
其實我可能是因為用的STL單調佇列,所以慢了幾倍!用手打還是能過的(說不定比正解還要快)
所以以後要用手打了啊!!!
正解思路——DP+單調佇列優化
我們很容易想到DP
我們設f[i]表示猴子跳到第i棵樹時的最小勞累值(當然這個你要做Q次)
那麼 f[i] = min(f[i],f[j] + (a[j]<=a[i])) j的範圍自然就是i-Ki到i-1了
很明顯,我們還是用單調佇列來優化,由於後面的最多隻會加上1,所以是不會影響的,因為就算要加1的話,最多跟第2小相等
這個自然就是滑動視窗的模板了,只是多了一個判斷,特殊時加上一個1
我看了看,跟上面的貪心單調佇列優化過的時間複雜度一樣的,哎,要用手打的啊!!!
電話線路
題目
最近,約翰的奶牛們越來越不滿足於牛棚裡一塌糊塗的電話服務,於是,她們要求
約翰把那些老舊的電話線換成效能更好的新電話線。新的電話線架設在己有的n根
電話線杆上,第i根電話線的高度為hi, ( 1 <= hi<= 100)。電話線總是從一根電話
線杆的頂端被弓}到相鄰的那根的頂端,如果這兩根電話線杆的高度hi和hj不同,那
麼約翰就必須支付c * |hi - hj|的費用,當然,你不能行動電話線杆,只能按照原有
的順序在相鄰杆間架設電話線。
加高某些電話線杆能減少架設電話線的總費用,儘管這項工作也需要支付一定的
費用。更準確的說,如果他把一根電話線杆加高x米的話,他需要付出x^2費用。
請你幫約翰計算一下,如果合理的進行這兩項工作,他最少要在這個電話線改造
工程中花多少錢。
輸入
第一行輸入兩個數n和c, 含義如上
接下來n行,每行一個整數hi
輸出
輸出約翰完成電話線改造工程需要花費的最小費用
樣例輸入
5 2
2
3
5
1
4
樣例輸出
15
非正解思路——暴搜
怎麼又是這個,我也沒有辦法啊!
我們暴搜那些需要增加
再暴搜增加的這些電話線杆要增加多少
突然感覺這個暴搜很無語啊!時間複雜度為O(n!maxhi!)
哎,這個要算到猴年馬月啊!據不完全估計,太陽死亡時都算不出來!
正解思路篇
一.引入:正解之——DP
我們設f[i][j]表示只算前i個電線杆且第i個電線杆增加後的高度為j的最小費用
則我們可以知道f[i][j] = min(f[i][j],f[i - 1][k] +|j-k|*c+(j-h[i])^2)
我們自然要迴圈i,j,k
j的範圍為h[i]到maxhi,即到最高電線杆的長度,因為你超過了這個值只會更虧的,沒有意義,而你又只能增加,最小肯定是原來的高度。
k代表的是第i-1跟電線杆的長度,自然是從h[i-1]到maxhi了,而我們的第一根電線杆自然是要單獨處理的(即預處理)
時間複雜度就是(n*maxhi*maxhi) 最壞時是100000*100*100=1000000000 這個就是10秒左右!肯定超時,所以我們要優化
二.優化:正解之——變數充當單調佇列
我們之前之所以要用單調佇列,是因為它是有一個區間限制的,所以我們用一個變數的話就顯得很只顧眼前,所以會出錯。
這裡我們的範圍就只有前面一個,你用單調對列的話,永遠就只會對當前的狀態貢獻,因為後面只要前面一個的,所以儲存了又要立馬出去,用單調佇列沒有必要,就用一個變數記錄好前面的最小值就行了。
那你會說,我們每次j一改變,又要去更新這個變數,不就差不多嘛,還是很浪費時間啊!
我們先不慌,來看一看怎麼變形
j>k
f[i][j] = min(f[i][j],f[i - 1][k] + (j-k)*c + (j-h[i])^2)
f[i][j] = min(f[i][j],f[i - 1][k] + j*c - k*c + (j-h[i])^2)
我們發現,不管K怎麼變,與j相關的都不變,即j*c + (j-h[i])^2 這一堆是不變的,所以我們只需要考慮f[i - 1][k] - k*c 最小就行了
變數記錄的就是這個中最小的 (就把這個變數記作min1吧)我們就得到 f[i][j]= min(f[i][j],min1 + j*c + (j-h[i])^2)
j<=k
f[i][j] = min(f[i][j],f[i - 1][k] + (k-j)*c + (j-h[i])^2)
f[i][j] = min(f[i][j],f[i - 1][k] + k*c - j*c + (j-h[i])^2)
記這個變數為min2 min2 = min(min2,f[i - 1][k] + k*c)
f[i][j] = min(f[i][j],min2 - j*c + (j-h[i])^2)
三.核心:正解之——變數的優化與迴圈方向的重要
如何求這兩個變數 用迴圈,那你的時間複雜度減了沒有多少啊!
我們發現,j>k時,如果我們的j是從小到大的,每次j增加1,其實我們的變數min1在上一次的基礎上,迴圈k的範圍只是多了上一輪的j而已,我們既然上一輪中,已經求出k到上一輪的j-1的最小值,那麼這一次,何必再用迴圈求呢?
直接比較現在的min1和k==上一輪的j(即這一輪的j-1)不就可以了嗎?
對於j<=k時 我們j從大到小
j每減少一個,min2的範圍也是隻從上一輪的階段的範圍多了個j
所以我們再也不用一個迴圈了,時間複雜度就降為O(n*maxhi) 100000*100=10000000 約為100ms
這裡我們就看出了好的迴圈方向使得優化的機率更大,也更節省時間。
四.點綴:正解之——滾動陣列優化時間和空間+卡常數
有些人說滾動陣列只會優化空間,但大量資料表明,一定程度上,也會優化時間的
由於我們的DP只用到了i-1的階段,所以我們不用開n*maxhi怎麼大的陣列
就只用開2*maxhi就行了,不停的滾動,來回利用(不懂的自己先了解再說)
卡常數就是inline reg 讀優寫優啦,不懂的自己去了解吧!
程式碼篇
建議三思而後行,思考1小時,除錯2小時以上再來看!
猴子跳樹
貪心
請注意,這個是用的STL,想要過的請自行改成手動版(不會自學)
#include <cstdio>
#include <list>
#define reg register
using namespace std;
struct node{
int x, id;
};
int n, q, a[1000005], x, w, ans;
list<node> q1;
list<node> q2;
bool flag;
inline void read(int &x){
int f = 1;
x = 0;
char s = getchar();
while(s < '0' || s > '9'){
if(s == '-'){
f = -1;
}
s = getchar();
}
while(s >= '0' && s <= '9'){
x = (x << 3) + (x << 1) + (s - '0');
s = getchar();
}
x *= f;
}
int main(){
freopen("monkey.in","r",stdin);
freopen("monkey.out","w",stdout);
read(n);
for(reg int i = 1; i <= n; i++){
read(a[i]);
}
scanf("%d",&q);
for(reg int i = 1; i <= q; i++){
read(x);
w = 1;
ans = 0;
while(!q1.empty()){
q1.pop_back();
}
while(!q2.empty()){
q2.pop_back();
}
for(reg int j = 2; j <= n; j++){
if(j - w > x){
flag = 0;
while(!q1.empty()){
node t = q1.front();
if(t.id <= w){
q1.pop_front();
}
else {
break;
}
}
while(!q2.empty()){
node t = q2.front();
if(t.id <= w){
q2.pop_front();
}
else {
break;
}
}
if(!q2.empty()){
node t = q2.front();
w = t.id;
}
else {
node t = q1.front();
w = t.id;
ans ++;
flag = 1;
}
while(!q1.empty()){
node t = q1.front();
if(t.id <= w){
q1.pop_front();
}
else {
break;
}
}
while(!q2.empty()){
node t = q2.front();
if(t.id <= w){
q2.pop_front();
}
else {
break;
}
}
if(flag){
while(!q1.empty()){
node t = q1.front();
q2.push_back(t);
q1.pop_front();
}
}
}
node h;
h.x = a[j];
h.id = j;
if(a[j] >= a[w]){
while(!q1.empty()){
node t = q1.back();
if(t.x <= h.x){
q1.pop_back();
}
else {
break;
}
}
q1.push_back(h);
}
else {
while(!q2.empty()){
node t = q2.back();
if(t.x <= h.x){
q2.pop_back();
}
else {
break;
}
}
q2.push_back(h);
}
}
while(w != n){
flag = 0;
while(!q1.empty()){
node t = q1.front();
if(t.id <= w){
q1.pop_front();
}
else {
break;
}
}
while(!q2.empty()){
node t = q2.front();
if(t.id <= w){
q2.pop_front();
}
else {
break;
}
}
if(!q2.empty()){
node t = q2.front();
w = t.id;
}
else {
node t = q1.front();
w = t.id;
ans ++;
flag = 1;
}
while(!q1.empty()){
node t = q1.front();
if(t.id <= w){
q1.pop_front();
}
else {
break;
}
}
while(!q2.empty()){
node t = q2.front();
if(t.id <= w){
q2.pop_front();
}
else {
break;
}
}
if(flag){
while(!q1.empty()){
node t = q1.front();
q2.push_back(t);
q1.pop_front();
}
}
}
printf("%d\n",ans);
}
return 0;
}
DP+單調佇列
我發現用STL也會超時,這也說明了和貪心的時間複雜度是一樣的!
#include <cstdio>
#define reg register
inline void read(int &x){
int f = 1;
x = 0;
char s = getchar();
while(s < '0' || s > '9'){
if(s == '-'){
f = -1;
}
s = getchar();
}
while(s >= '0' && s <= '9'){
x = (x << 3) + (x << 1) + (s - '0');
s = getchar();
}
x *= f;
}
inline void write(int x){
if(x < 0){
putchar('-');
x *= -1;
}
if(x > 9){
write(x / 10);
}
putchar((x % 10) + '0');
}
struct node{
int x, id;
};
int n, a[1000005], w, x, ans, head, tail;
node q[1000005];
int main(){
read(n);
for(reg int i = 1; i <= n; i++){
read(a[i]);
}
read(w);
for(reg int i = 1; i <= w; i++){
read(x);
ans = 0;
head = tail = 1;
for(reg int j = 1; j <= n; j++){
node h;
while(head < tail){
if(j - q[head].id > x){
head ++;
}
else {
break;
}
}
h.x = 0;
h.id = j;
if(j == 1){
q[tail] = h;
tail ++;
continue;
}
h = q[head];
if(a[h.id] <= a[j]){
h.x ++;
}
ans = h.x;
h.id = j;
while(head < tail){
node t = q[tail - 1];
if(t.x > h.x || (t.x == h.x && a[t.id] <= a[h.id])){
tail --;
}
else {
break;
}
}
q[tail] = h;
tail ++;
}
write(ans);
putchar('\n');
}
return 0;
}
電話線路
#include <cstdio>
#include <iostream>
#include <cstring>
#define reg register
using namespace std;
inline void read(int &x){
int f = 1;
x = 0;
char s = getchar();
while(s < '0' || s > '9'){
if(s == '-'){
f = -1;
}
s = getchar();
}
while(s >= '0' && s <= '9'){
x = (x << 3) + (x << 1) + (s - '0');
s = getchar();
}
x *= f;
}
inline void write(int x){
if(x < 0){
putchar('-');
x *= -1;
}
if(x > 9){
write(x / 10);
}
putchar((x % 10) + '0');
}
int n, c, a[100005], dp[2][105], iop, noip;
int main(){
read(n);
read(c);
int maxhigh = 0;
for(reg int i = 1; i <= n; i ++){
read(a[i]);
maxhigh = max(maxhigh, a[i]);
}
memset(dp, 0x3f, sizeof(dp));
noip = 1;
for(reg int i = a[1]; i <= maxhigh; i ++){
dp[iop][i] = (i - a[1]) * (i - a[1]);
}
/*for(reg int i = 2; i <= n; i++){
for(reg int j = a[i]; j <= maxhigh; j++){
for(reg int k = a[i - 1]; k <= maxhigh; k++){
dp[i][j] = min(dp[i][j], dp[i - 1][k] + (jdz(j - k)) * c + (j - a[i]) * (j - a[i]));
}
}
}*/
for(reg int i = 2; i <= n; i ++){
int min1 = 2147483647, min2 = 2147483647;
for(reg int j = 0; j < a[i]; j ++){
min1 = min(min1, dp[iop][j] - c * j);
}
for(reg int j = a[i]; j <= maxhigh; j ++){
dp[noip][j] = min(dp[noip][j], min1 + c * j + (j - a[i]) * (j - a[i]));
min1 = min(min1, dp[iop][j] - c * j);
}
for(reg int j = maxhigh; j >= a[i]; j --){
min2 = min(min2, dp[iop][j] + c * j);
dp[noip][j] = min(dp[noip][j], min2 - c * j + (j - a[i]) * (j - a[i]));
}
for(reg int j = 0; j <= maxhigh; j++){
dp[iop][j] = 0x3f3f3f3f;
}
swap(iop, noip);
}
int ans = 2147483647;
for(reg int i = a[n]; i <= maxhigh; i++){
ans = min(ans, dp[iop][i]);
}
write(ans);
return 0;
}