暑假作業 三

打地鼠
小明聽說打地鼠是一件很好玩的遊戲,於是他也開始打地鼠。地鼠只有一隻,而且一共有N個洞,編號爲1到N排成一排,兩邊是牆壁,小明當然不可能百分百打到,因爲在他打到地鼠之前他一直不知道地鼠在哪個洞。小明只能在白天打地鼠,而且每次打了都覺得好累,感覺再也不會打了,必須休息到第二天才能再次打地鼠,也就是說他每天只有一次打地鼠的機會。
機智的地鼠在每天晚上都會跑向相鄰的兩個洞中的一個,如果一邊是牆壁就只有往另一邊跑,而且它很固執,每天晚上肯定會跑,也就是說不會連續呆在同一個洞。
儘管小明很累,但是他明白要想拿到一等獎,就必須打到地鼠,所以他想知道怎樣才能在最壞的情況下最短的天數內保證肯定打到地鼠。
輸入
只有一行,該行有一個整數N,表示N個洞並排在一起。地鼠在隨機一個洞。
輸出
只有一行,該行有一個整數,表示小明肯定能打中至少需要的天數。
樣例
mouse.in
1
mouse.out
1

mouse.in
4
mouse.out
4

數據範圍
40% n<=10
100%n<=20.

分析:
這是一道找規律的題。
這道題首先是要考慮怎樣才能夠保證一定能夠打中。如果每天選擇洞的規律和地鼠移動的規律一樣,也就是每天都選前一天相鄰的一個洞,那麼可以發現,每次你選擇的洞口和地鼠所在洞口的距離要麼不變,要麼增加二或者減少二,按這樣從一邊牆壁檢查到另一邊牆壁,如果沒有發現地鼠,那就說明地鼠的初始位置和你的初始檢查距離爲奇數,這個時候再重複檢查一次你剛檢查過的洞口,那麼因爲地鼠必須要移動,你們的距離就修改成了偶數,這樣再從另一邊檢查回來,就保證一定能夠發現地鼠。這樣算下來的結果是2N。
但是可以再分析一下,第一遍掃描的N次的意義在與檢查你和地鼠的距離是否爲偶數,第二次的意義在於把奇數變爲偶數。那麼第一次完全可以只從N-1掃描到2(因爲地鼠如果在N號洞或者1號洞與你的距離都爲奇數,和第一次的任務沒有關係),第二次也從2掃描到N-1(因爲距離如果是偶數那麼相遇時肯定是地鼠與你相向運動,也就是地鼠從相遇點後面過來的,所以不可能會在1號和N號洞相遇)。最後再考慮只有1,和2兩個洞這兩種特殊情況。

#include <cstdio>

using namespace std;

int n;

int main() {
    scanf("%d", &n);

    if(n == 1) { printf("1\n"); return 0; }
    if(n == 2) { printf("2\n"); return 0; }
    printf("%d\n", (n - 2) * 2);

    return 0;
}

城牆攻防戰、
A國和B國積怨已久,戰爭不可避免的爆發了,在一次戰役中,A國的一支部隊包圍了B國的一座城池,就在A國以爲勝利在望的時候,卻殊不知B國正在計劃利用堅固的城牆進行反擊。
已知B國城池的城牆是由線性排列的N個石塊組成,排列由1到N,每個石塊都有它的防禦值ai,由許多石塊連成一段的城牆的防禦值等於這段城牆內所有石塊防禦值之和乘以這段城牆內防禦最低的那塊石頭的防禦值。
經過戰術商討,B國決定將敵人引入一段防禦最高的城牆將其全殲,但是尋找出這段防禦最高的城牆的問題需要他們快速解決。
輸出
兩行。
第一行一個正整數 N,表示城牆石塊的個數。
第二行N個整數,表示每個石塊的防禦值。

輸出
一行
最強防禦的城牆的防禦力。
樣例
wall.in
6
3 1 6 4 5 2

wall.out
60
數據範圍
20% 0<=n<=1000;
100% 0<=n<=100000,0<=ai<=1000000.

這道題可以枚舉以每個石塊爲最小值時,他所在的城牆段防禦最高是多少,基於貪心原則應該向左和向右儘可能延伸他所在城牆段的區域,這樣可以得出最優解。
枚舉尋找每個區間的左右界 n^2
改進的話可以使用RMQ(nlogn)或者線段樹(n(logn)^2)
還有一種更爲暴力的方法,時間效率直逼(O)n,還是用枚舉來確立左右邊界,如果發現枚舉的值小於標準值那麼結束枚舉,確定邊界。如果發現枚舉值大於等於標準值,那麼直接跳到該枚舉點的邊界繼續枚舉。時間複雜度分析,每個標準值只可能遇到一個比它小的枚舉值。
每個被枚舉的值只可能被一個標準值小於(因爲之後他直接被跳過),所以單向枚舉的時間複雜度是接近於2n的。

#include <cstdio>
#include <iostream>
using namespace std;

int n;
int a[100000+10];
int lf[100000+10],rf[100000+10];
long long sum[100000+10];//和

void work()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];  //!
        lf[i]=i;
        rf[i]=i;
    }
    for(int i=1;i<=n;i++)
    {
        int x;
        for(x=lf[i];a[i]<=a[x-1]&&x-1>0;x=lf[x-1]);
        lf[i]=x;//記錄lf[i] 
        for(x=rf[n-i+1];a[n-i+1]<=a[x+1]&&x+1<=n;x=rf[x+1]);
        rf[n-i+1]=x;//記錄rf[i] 
    }
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,(long long)a[i]*(sum[rf[i]]-sum[lf[i]-1])); 
    }
    printf("%I64d\n",ans);
}

int main()
{
    work();
    return 0;
}

屠夫的鉤子
在DotA遊戲中,屠夫的鉤子是英雄們非常懼怕的武器之一,鉤子是由N段連續的金屬棒構成,一開始鉤子的每段金屬棒材質均爲銅,現在屠夫想增強他武器的威力,決定對他的鉤子進行改造,由於智商問題,他一次只能將連續的一段金屬棒的的材質變爲成金、銀、銅的一種,每種材質的一段金屬棒的攻擊力分別爲3,2,1,屠夫想知道按照一系列升級後,他的武器總攻擊力爲多少,即每個金屬棒攻擊力之和。
輸入
第一行一個數T,表示由T組測試數據。
對於每組數據,第一行一個數N,表示屠夫的鉤子的共有N段金屬棒,第二行一個數Q,表示屠夫對他的鉤子進行了Q次改造,接下來Q行,每行3個數x,y,z,表示屠夫將[x,y]段金屬的材質變爲z。z=1,表示銅,z=2表示銀,z=3表示金。
輸出
對於每組測試數據輸出改造後的鉤子的攻擊力,注意使用樣例中的格式,各佔一行。
樣例
hook.in
1
10
2
1 5 2
5 9 3
hook.out
Case 1: The total value of the hook is 24.

分析:
又是一道線段樹 。
不多說了。


文件排版
寫電子郵件是有趣的,但不幸的是經常寫不好看,主要是因爲所有的行不一樣長,你的上司想要發排版精美的電子郵件,你的任務是爲他編寫一個電子郵件排版程序。
完成這個任務最簡單的辦法是在太短的行中的單詞之間插入空格,但這並不是最好的方法,考慮如下例子:
This is the example you are
actually considering.
假設我們想將第二行變得和第一行一樣長,靠簡單地插入空格則我們將得到如下結果:
This is the example you are
actually considering.
但這太難看了,因爲在第二行中有一個非常大的空白,如果將第一行的單詞“are”移到下一行我們將得到較好的結果:
This is the example you
are actually considering.
當然,這必須對難看程度進行量化。因此我們必須給出單詞之間的空格的難看程度,一個包含N個空格符的空白段,其難看程度值爲(n-1)2,程序的目的是使難看程度的總和最小化。例如,第一個例子的難看程度是1+7*7=50,而第二個例子的難看程度僅爲1+1+1+4+1+4=12。
輸出時,每一行的開頭和結尾處都必須是一個單詞,即每行開頭和結尾處不能有空白。唯一例外的是該行僅有一個單詞組成的情況,此時如果該單詞比該行應有的長度短則我們指定它的難看程度爲500。
輸入
輸入文件第一行是一個整數N,表示排版後文章每行要求達到的寬度,1<=N<=80。
後面是給出的一段文章,該段文章由一個或多個單詞組成,單詞由ASCII碼值爲33到126(包含33和126)的字符組成,單詞與單詞之間用空格隔開(可能超過一個)。單詞長度不會超過段落要求達到的寬度。一段文字所有單詞的總長度不會超過10000個字符,任何一行都不會超過100個字符,任何一個單詞都在同一行內。
輸出
找出使其難看程度最小的排版形式並輸出句子:“Minimal badness is B.”,B是指按可能的最好排版形式會發生的難看程度值。注意排版後文本行數任意,多餘的空格也可刪除。
樣例
format.in
28
This is the example you are
actually considering.
format.out
Minimal badness is 12.

分析:
這道題是一個動態規劃,先預處理一下,字母符號什麼的都可以扔了,只留下每個單詞的長度,然後用f[i]表示當前已經排好了i個單詞並且第i號單詞在上一行結尾處文章的最小難看程度,也就是現在是從新一行開始放單詞,枚舉放多少個單詞轉移到下一行。每一行的空白都應該平均分配到兩個單詞之間,餘下的空白挨個分一個到單詞之間,這樣算出的代價是該行最優,最後再考慮一個單詞的特殊情況。

#include <cstdio> 
#include <cstring> 
#include <iostream>  
using namespace std; 
const int inf=0x3f3f3f; 

int n,cnt=0;
int a[10000];
int f[10000];//f[i]表示已放好i個單詞,並且i在上一行末尾處文章的最小難看度 

void readdata()
{
     char s[110];
     scanf("%d\n",&n);
     while(scanf("%s",s)!=EOF)
     {
         a[++cnt]=strlen(s);//將每個單詞的長度放到a數組 
     }
}

void work()
{ 
     memset(f,inf,sizeof(f));
     for(int i=0;i<cnt;i++)//前i個單詞已放好,從下一行開始放單詞
     {
         f[0]=0;
         int len=0; //表示該行的單詞長度 
         for(int j=1;;j++)//j爲該行單詞數 
         {
             int bad=0;//該行的難看程度 
             if(i+j>cnt)break;//當單詞數超過cnt時,退出 
             len+=a[i+j];//累加該行長度 
             if(len+j-1>n)break;//當該行最優長度(單詞長度+(j-1)個空格長度大於n時,退出 
             if(j==1&&len<n)bad=500;//當該行只有一個單詞且長度小於n時 
             else if(j==1&&len==n)bad=0;//當該行只有一個單詞且長度等於n時
             else
             {
                 int k,kk;//平均每個空白的空格數,平均分配後還多出的空格數
                 k=(n-len)/(j-1);//空格數除以空白數
                 kk=(n-len)-k*(j-1);//空格數-平均空格數*空白數
                 bad=(k-1)*(k-1)*(j-kk-1)+kk*k*k;//計算該行的難看度。 
             }
             if(f[i+j]>f[i]+bad)f[i+j]=f[i]+bad;//更新最小難看度 
         }
     }
     printf("Minimal badness is %d.\n",f[cnt]);
}


int main()
{
    readdata();
    work();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章