1. 程式人生 > >『NOIP2018PJ題解』

『NOIP2018PJ題解』

<更新提示>

<第一次更新>


<正文>

標題統計

題目描述

凱凱剛寫了一篇美妙的作文,請問這篇作文的標題中有多少個字元? 注意:標題中可能包含大、小寫英文字母、數字字元、空格和換行符。統計標題字 符數時,空格和換行符不計算在內。

輸入格式

輸入檔案只有一行,一個字串 s。

輸出格式

輸出檔案只有一行,包含一個整數,即作文標題的字元數(不含空格和換行符)。

樣例資料

input1

234

output1

3

input2

Ca 45

output2

4

樣例說明

樣例 1 :標題中共有 3 個字元,這 3 個字元都是數字字元。

樣例 2 :標題中共有 5 個字元,包括 1 個大寫英文字母, 1 個小寫英文字母和 2 個數字字元, 還有 1 個空格。由於空格不計入結果中,故標題的有效字元數為 4 個。

資料規模與約定

規定∣s∣ 表示字串 s 的長度(即字串中的字元和空格數)。

對於 %40 的資料,1 ≤ |s| ≤ 5,保證輸入為數字字元及行末換行符。

對於 %80 的資料,1 ≤ |s| ≤ 5,輸入只可能包含大、小寫英文字母、數字字元及行末換行符。

對於 %100 的資料,1 ≤ |s| ≤ 5,輸入可能包含大、小寫英文字母、數字字元、空格和行末換行符。

時間限制:1s

空間限制:256MB

解析

T1和往年一樣,還是簽到題。不過這一次好像更注重考察語言了,不少不熟悉語言的小夥伴可能就沒有分了啦。
大概是考察如何處理輸入吧,會用\(getline()\)的基本都用了\(getline()\)

了吧,當然,不會用的還有其他的辦法,主要是\(while(cin>>str)\)\(scanf()\)讀到換行符為止。這樣的話,只要暴力統計一下就可以了啦。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
string s;int ans=0;
int main(void)
{
    freopen("title.in","r",stdin);
    freopen("title.out","w",stdout);
    getline(cin,s);
    for(int i=0;i<s.size();i++)
    {
        if(s[i]!=' ')ans++;
    }
    printf("%d\n",ans);
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
char s;
int ans=0;
int main()
{
    freopen("title.in","r",stdin);
    freopen("title.out","w",stdout);
    while (cin>>s)
    {
        if (s>='0'&&s<='9'||s>='a'&&s<='z'||s>='A'&&s<='Z') ans++;
    }
    cout<<ans<<endl;
    return 0;
}

龍虎鬥

題目描述

軒軒和凱凱正在玩一款叫《龍虎鬥》的遊戲,遊戲的棋盤是一條線段,線段上有 n個兵營(自左至右編號 1 ∼n),相鄰編號的兵營之間相隔 1 釐米,即棋盤為長度為 n-1 釐米的線段。i 號兵營裡有 ci位工兵。

下面圖 1 為 n=6的示例:

軒軒在左側,代表“龍”;凱凱在右側,代表“虎”。 他們以 m 號兵營作為分界, 靠左的工兵屬於龍勢力,靠右的工兵屬於虎勢力,而第 m號兵營中的工兵很糾結,他們不屬於任何一方。

一個兵營的氣勢為:該兵營中的工兵數 × 該兵營到 m 號兵營的距離;參與遊戲 一方的勢力定義為:屬於這一方所有兵營的氣勢之和。

下面圖 2 為 n = 6,m = 4 的示例,其中紅色為龍方,黃色為虎方:

遊戲過程中,某一刻天降神兵,共有 s1位工兵突然出現在了 p1號兵營。作為軒軒和凱凱的朋友,你知道如果龍虎雙方氣勢差距太懸殊,軒軒和凱凱就不願意繼續玩下去了。為了讓遊戲繼續,你需要選擇一個兵營 p2,並將你手裡的 s2位工兵全部派往 兵營 p2,使得雙方氣勢差距儘可能小。

注意:你手中的工兵落在哪個兵營,就和該兵營中其他工兵有相同的勢力歸屬(如果落在 m 號兵營,則不屬於任何勢力)。

輸入格式

輸入檔案的第一行包含一個正整數n,代表兵營的數量。

接下來的一行包含 n 個正整數,相鄰兩數之間以一個空格分隔,第 i個正整數代 表編號為 i 的兵營中起始時的工兵數量 ci。

接下來的一行包含四個正整數,相鄰兩數間以一個空格分隔,分別代表 m,p1,s1,s2。

輸出格式

輸出檔案有一行,包含一個正整數,即 p2,表示你選擇的兵營編號。如果存在多個編號同時滿足最優,取最小的編號。

樣例資料

input1

6 
2 3 2 3 2 3 
4 6 5 2

output1

2

input2

6 
1 1 1 1 1 16 
5 4 1 1

output2

1

樣例說明

樣例1:見問題描述中的圖 2。

雙方以 m=4號兵營分界,有 s1=5位工兵突然出現在p1=6號兵營。 龍方的氣勢為:

2×(4−1)+3×(4−2)+2×(4−3)=14

虎方的氣勢為:

2×(5−4)+(3+5)×(6−4)=18

當你將手中的 s2=2s2=2位工兵派往 p2=2號兵營時,龍方的氣勢變為:

14+2×(4−2)=18

此時雙方氣勢相等。

樣例2:

雙方以 m = 5號兵營分界,有 s1=1位工兵突然出現在 p1=4號兵營。

龍方的氣勢為:

1×(5−1)+1×(5−2)+1×(5−3)+(1+1)×(5−4)=11

虎方的氣勢為:

16×(6−5)=16

當你將手中的 s2=1位工兵派往 p2=1號兵營時,龍方的氣勢變為:

11+1×(5−1)=15

此時可以使雙方氣勢的差距最小。

資料規模與約定

1<m<n,1≤p1≤n。

對於%20 的資料,\(n=3,m=2,ci=1,s1,s2≤100。\)

另有%20 的資料,\(n≤10,p1=m,ci=1,s1,s2≤100。\)

對於%60 的資料,\(n≤100,ci=1,s1,s2≤100。\)

對於%80 的資料,\(n≤100,ci,s1,s2≤100。\)

對於%100 的資料,\(n≤10^5,ci,s1,s2≤10^9。\)

時間限制:1s

空間限制:256MB

解析

今年的第二題沒有往年怎麼簡單啦。這回T2雖然說也沒有考演算法,但是細節問題就很多了,於是就有了不少坑點:

1.爆int
2.min的初值需要賦到MAX_LONGLONG大小
3.需要注意把工兵放在m號兵營的情況

那麼大體思路是這樣的,我們暴力列舉把\(s2\)個工兵放在每一個兵營的情況,看看放在哪裡呢得到最優解,直接輸出即可。那麼,這裡就有一個顯而易見的優化了:那一邊初始的氣勢值總和最小,那就在那一邊列舉。但是,這就如3.,無論是那一邊,我們都要判斷是否把\(s2\)個工兵放在\(m\)號兵營會取得更優的答案,因為存在如下一種情況:

一開始雙方勢力值總和的差較小,我們在勢力值總和較小的一邊列舉,但是\(s2\)的值巨大,無論放在哪裡,新勢力值的差比原勢力值的差還大,此時,不如把工兵放在\(m\)號兵營處,讓勢力值總和不變化

如果這些都注意到了,那麼這題就解決了。

#include<bits/stdc++.h>
using namespace std;
inline void read(long long &k)
{
    long long x=0,w=0;char ch;
    while(!isdigit(ch))w|=ch=='-',ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    k=(w?-x:x);return;
}
const int Maxn=100000+80;
long long n,c[Maxn],m,p1,s1,s2,vleft=0,vright=0,Mindif=0x3f3f3f3f,ans;
inline void input(void)
{
    read(n);
    for(int i=1;i<=n;i++)read(c[i]);
    read(m),read(p1);
    read(s1),read(s2);
    c[p1]+=s1;
}
inline void init(void)
{
    for(int i=1;i<m;i++)vleft+=(c[i]*(m-i));
    for(int i=m+1;i<=n;i++)vright+=(c[i]*(i-m));
}
inline void work(void)
{
    if(vleft<vright)
    {
        for(int i=1;i<=m;i++)
        {
            long long l=vleft+s2*(m-i);
            long long dif=abs(vright-l);
            if(dif<Mindif)
            {
                Mindif=dif;
                ans=i;
            }
        }
    }
    else if(vright<vleft)
    {
        for(int i=m;i<=n;i++)
        {
            long long r=vright+(i-m)*s2;
            long long dif=abs(r-vleft);
            if(dif<Mindif){
                
                Mindif=dif;
                ans=i;
            }
        }
    }
    else
    {
        ans=m;
    }
}
int main(void)
{
    freopen("fight.in","r",stdin);
    freopen("fight.out","w",stdout);
    input();
    init();
    work();
    printf("%lld\n",ans);
    return 0;
}

擺渡車

題目描述

有 n 名同學要乘坐擺渡車從人大附中前往人民大學,第 i 位同學在第 ti分鐘去 等車。只有一輛擺渡車在工作,但擺渡車容量可以視為無限大。擺渡車從人大附中出發、 把車上的同學送到人民大學、再回到人大附中(去接其他同學),這樣往返一趟總共花費m分鐘(同學上下車時間忽略不計)。擺渡車要將所有同學都送到人民大學。

凱凱很好奇,如果他能任意安排擺渡車出發的時間,那麼這些同學的等車時間之和最小為多少呢?

注意:擺渡車回到人大附中後可以即刻出發。

輸入格式

第一行包含兩個正整數 n,m,以一個空格分開,分別代表等車人數和擺渡車往返 一趟的時間。

第二行包含 n 個正整數,相鄰兩數之間以一個空格分隔,第 i 個非負整數 ti代 表第 i 個同學到達車站的時刻。

輸出格式

輸出一行,一個整數,表示所有同學等車時間之和的最小值(單位:分鐘)。

樣例資料

input1

5 1 
3 4 4 3 5

output1

0

input2

5 5 
11 13 1 5 5

output2

4

樣例說明

樣例1:

同學 1 和同學 4 在第 3 分鐘開始等車,等待 0 分鐘,在第 3 分鐘乘坐擺渡車出發。擺渡車在第 4分鐘回到人大附中。

同學 2 和同學 3 在第 4 分鐘開始等車,等待 0 分鐘,在第 4 分鐘乘坐擺渡車 出發。擺渡車在第 5 分鐘回到人大附中。

同學 5 在第 5 分鐘開始等車,等待 0 分鐘,在第 5 分鐘乘坐擺渡車出發。自此 所有同學都被送到人民大學。總等待時間為 0。

樣例2:

同學 3 在第 1 分鐘開始等車,等待 0 分鐘,在第 1 分鐘乘坐擺渡車出發。擺渡 車在第 6分鐘回到人大附中。

同學 4 和同學 5 在第 5 分鐘開始等車,等待 1 分鐘,在第 6 分鐘乘坐擺渡車 出發。擺渡車在第 11 分鐘回到人大附中。

同學 1 在第 11 分鐘開始等車,等待 2 分鐘;同學 2 在第 13 分鐘開始等車, 等待 0 分鐘。他/她們在第 13 分鐘乘坐擺渡車出發。自此所有同學都被送到人民大學。 總等待時間為 4。

可以證明,沒有總等待時間小於 4 的方案。

資料規模與約定

對於%10 的資料,\(n≤10,m=1,0≤ti≤100。\)

對於%30 的資料,\(n≤20,m≤2,0≤ti≤100。\)

對於%50 的資料,\(n≤500,m≤100,0≤ti≤10^4。\)

另有%20 的資料,\(n≤500,m≤10,0≤ti≤4×10^6。\)

對於%100 的資料,\(n≤500,m≤100,0≤ti≤4×10^6。\)

時間限制:2s

空間限制:256MB

題解

這就是今年的毒瘤T3了,可以說是近幾年來最難的一道題,比今年T4還難。
可是這道題的題面竟然異常簡潔,讓人一看就懂。有沒有人像我一樣一看就覺得簡單的(`・ω・´),然後就死在這了。
首先按時間排序,這是一定的。最容易讓人想到的就是\(O(n^3)\)\(dp\)了,設\(f[i]\)代表前\(i\)個同學到達的最小等待時間總和,那麼暴力轉移是不會超時的。但是考場上怎麼都不會想到的是,這樣的dp在轉移時的花費是會計算錯誤的,而且小資料基本調不出來,主要原因是:我們不知道在轉移第\(i\)位同學時車是什麼時候到的,也就是不知道轉移的出發時間。所以在大資料上,一維的\(dp\)就基本全錯了。
但是這個錯誤演算法也給我們一個啟示,我們就可以藉此來改進dp了。設\(f[i][j]\)代表前i個同學,第\(i\)個同學在第\(t[i]+j\)分鐘到達的最小等待時間總和,這樣的狀態就不會出錯。其實,這樣設定後我們可以很簡單地劃分階段,\(f[i][j]\)不需要從\(f[k][l]\)轉移,只需要從\(f[i-1][k]\)轉移即可,因為\(i\)個同學必然屬於以下兩類中的一類:

1.第\(i\)個同學和第\(i-1\)個同學坐同一輛車
2.第\(i\)個同學和第\(i-1\)個同學不坐同一輛車

那麼我們來討論轉移的細節。
先看一個最基本的問題,兩位到達時間差超過\(m\)且到達時間相鄰的同學必然不坐同一輛車,因為他們的時間間隔足以讓車往返一趟。
那麼狀態第二維的上限只需要開到\(2m\)大小即可,達到上限的情況就是和他同時來的一個同學直接坐車走了,花費m分鐘,車回來後再送他,又花費m分鐘,在第\(t[i]+2m\)分鐘時,前i位同學全部到達。
此時,我們利用階段轉移:
\[ 1.\ if(t[i-1]+k==t[i]+j)\ f[i][j]=min(f[i][j],f[i-1][k]+j) \\2.\ if(t[i-1]+k+m<=t[i]+j)\ f[i][j]=min(f[i][j],f[i-1][k]+j) \]
那麼三重迴圈就解決了呀。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N=580,M=580,T=4*1e6+80,INF=0x3f3f3f3f;
int n,m,t[N]={},f[N][2*M]={},ans=INF;
inline void input(void)
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&t[i]);
} 
inline void init(void)
{
    sort(t+1,t+n+1);
    for(int i=1;i<=m*2;i++)f[1][i]=i;
}
inline void solve(void)
{
    for(int i=2;i<=n;i++)
    {
        for(int j=0;j<2*m;j++)
        {
            f[i][j]=INF;
            for(int k=0;k<2*m;k++)
            {
                if(t[i-1]+k==t[i]+j||t[i-1]+k+m<=t[i]+j)f[i][j]=min(f[i][j],f[i-1][k]+j);
            }
            if(i==n)ans=min(ans,f[i][j]);
        }
    }
}
int main(void)
{
    freopen("bus.in","r",stdin);
    freopen("bus.out","w",stdout);
    input();
    init();
    solve();
    printf("%d\n",ans);
    return 0;
}


<後記>