NOIP 2015 普及組

T1 金幣

題目點擊→計蒜客 [NOIP2015] 金幣

題目描述
國王將金幣作爲工資,發放給忠誠的騎士。第一天,騎士收到一枚金幣;之後兩天(第二天和第三天),每天收到兩枚金幣;之後三天(第四、五、六天),每天收到三枚金幣;之後四天(第七、八、九、十天),每天收到四枚金幣……;這種工資發放模式會一直這樣延續下去:當連續 NN 天每天收到 NN 枚金幣後,騎士會在之後的連續 N+1N+1 天裏,每天收到 N+1N+1 枚金幣。

請計算在前 KK 天裏,騎士一共獲得了多少金幣。

輸入格式

輸入只有 11 行,包含一個正整數 KK ,表示發放金幣的天數。

輸出格式

輸出只有 11 行,包含一個正整數,即騎士收到的金幣數。

數據範圍

對於 100%100\% 的數據,1K10,0001 \le K \le 10,000

樣例說明

樣例1:

騎士第一天收到一枚金幣;第二天和第三天,每天收到兩枚金幣;第四、五、六天,每天收到三枚金幣。因此一共收到 1+2+2+3+3+3=141+2+2+3+3+3=14 枚金幣。

T1分析

送分題,按照題目要求一天一天模擬,兩層for循環就可以了,第一層枚舉個數,第二層循環枚舉所有的數字。

#include <iostream>
using namespace std;
int main(){
	int n;
	cin >> n;
	long long sum = 0;
	for(int i = 0; ;i++){
		for(int j = 0; j < i; j++){
			sum += i;
			n--;
			if(n == 0){
				cout << sum << endl;
				return 0;
			}
		}
	}
	return 0;
}

T2 掃雷遊戲

題目點擊→計蒜客 [NOIP2015] 掃雷遊戲

題目描述
掃雷遊戲是一款十分經典的單機小遊戲。在 nnmm 列的雷區中有一些格子含有地雷(稱之爲地雷格),其他格子不含地雷(稱之爲非地雷格)。玩家翻開一個非地雷格時,該格將會出現一個數字——提示周圍格子中有多少個是地雷格。遊戲的目標是在不翻出任何地雷格的條件下,找出所有的非地雷格。

現在給出 nnmm 列的雷區中的地雷分佈,要求計算出每個非地雷格周圍的地雷格數。

注:一個格子的周圍格子包括其上、下、左、右、左上、右上、左下、右下八個方向上與之直接相鄰的格子。

輸入格式

輸入文件第一行是用一個空格隔開的兩個整數 nnmm ,分別表示雷區的行數和列數。

接下來 nn 行,每行 mm 個字符,描述了雷區中的地雷分佈情況。字符’*’表示相應格子是地雷格,字符’?’表示相應格子是非地雷格。相鄰字符之間無分隔符。

輸出格式

輸出文件包含 nn 行,每行 mm 個字符,描述整個雷區。用’*’表示地雷格,用周圍的地雷個數表示非地雷格。相鄰字符之間無分隔符。

數據範圍

對於 100%100\% 的數據,1n100,1m1001 \le n \le 100, 1 \le m \le 100

T2分析

簡單模擬題,其實也是送分題,每次找到一個地雷之後,把他周圍的所有數字全部 +1+1 就可以了

#include <iostream>
using namespace std;
const int N = 1e2 + 9;
char a[N][N];
int f[N][N];
int dx[] = {1, -1, 0, 0, 1, -1, 1, -1};
int dy[] = {0, 0, -1, 1, 1, -1, -1, 1};
int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            if (a[i][j] == '*') {
                for (int k = 0; k < 8; k++) {
                    int x = i + dx[k];
                    int y = j + dy[k];
                    f[x][y]++;
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (a[i][j] == '*') {
                cout << '*';
            } else {
                cout << f[i][j];
            }
        }
        cout << endl;
    }
    return 0;
}

T3 求和

題目點擊→ 計蒜客 [NOIP2015] 求和

題目描述
一條狹長的紙帶被均勻劃分出了 nn 個格子,格子編號從 11nn。每個格子上都染了一種顏色 coloricolor_i[1,m][1,m] 當中的一個整數表示),並且寫了一個數字 numberinumber_i

定義一種特殊的三元組:(x,y,z)(x,y,z),其中 x,y,zx,y,z 都代表紙帶上格子的編號,這裏的三元組要求滿足以下兩個條件:

  1. x,y,zx,y,z 是整數,x<y<z,yx=zyx<y<z,y-x=z-y

  2. colorx=colorzcolor_x=color_z

滿足上述條件的三元組的分數規定爲 (x+z)×(numberx+numberz)(x+z) \times (number_x+number_z)。整個紙帶的分數規定爲所有滿足條件的三元組的分數的和。這個分數可能會很大,你只要輸出整個紙帶的分數除以 10,00710,007 所得的餘數即可。

輸入格式

第一行是用一個空格隔開的兩個正整數 nnm,nm,n 表紙帶上格子的個數, mm 表紙帶上顏色的種類數。

第二行有 nn 用空格隔開的正整數,第 ii 數字 numbernumber 表紙帶上編號爲 ii 格子上面寫的數字。

第三行有 nn 用空格隔開的正整數,第 ii 數字 colorcolor 表紙帶上編號爲 ii 格子染的顏色。

輸出格式

共一行,一個整數,表示所求的紙帶分數除以 10,00710,007 所得的餘數。

數據範圍

對於第 11 組至第 22 組數據,1n1001 \le n \le 1001m51 \le m \le 5

對於第 33 組至第 44 組數據,1n30001 \le n \le 30001m1001 \le m \le 100

對於第 55 組至第 66 組數據,1n1000001 \le n \le 1000001m1000001 \le m \le 100000,且不存在出現次數超過 2020 的顏色;

對於全部 1010 組數據,1n1000001 \le n \le 1000001m1000001 \le m \le 1000001colorim1 \le color_i \le m1numberi1000001 \le number_i \le 100000

樣例說明

樣例1:

紙帶如題目描述中的圖所示。

所有滿足條件的三元組爲:(1,3,5)(1,3,5)(4,5,6)(4,5,6)

所以紙帶的分數爲 (1+5)×(5+2)+(4+6)×(2+2)=42+40=82(1+5) \times (5+2)+(4+6) \times (2+2)=42+40=82

T3分析

觀察數據範圍,首先前兩組 n100n \leq 100 所以直接 O(n3)O(n^3) 暴力即可獲得 20%20\%

繼續觀察題目要求,可以看到首先我們要滿足第一個條件 yx=zyy - x = z - y,左右移動後可以得到式子 x+z=2yx + z = 2 *y 從這裏我們可以知道,2y2 * y 必然是一個偶數,而 x+z=2yx + z = 2 * y 那就意味着 x,zx,z 要麼同是奇數要麼同是偶數,當然如果不理解這個其實自己舉個例子也就能發現了

繼續往後看可以發現,我們所需要計算的貢獻是跟 yy 無關的,所以第一個式子可以得到一個結論,x,zx,z 是同奇同偶的,在這個條件下我們可以發現,任意兩個奇偶性相同的 x,zx,z 必然存在一個 yy 使得 yx=zyy - x = z - y ,所以也就意味着只要奇偶性相同的兩個數字 x,zx,z 就一定可以滿足 第一個條件

在有了這個條件後,其實 40%40\% 的分數就可以獲得了,只要暴力枚舉 x,zx,z 即可獲得 40%40\% 的分數

接下來看第二個條件,也就是 colorx=colorzcolor_x = color_z,也就是說只要 x,zx,z 奇偶性相同,並且顏色相同,那這組 x,zx,z 就可以作爲一組三元組的解,將貢獻加到答案中

其實 60%60\% 的數據也在提示這一點,接下來的考慮方向應該是顏色!

所以我們可以先將紙帶進行奇偶的區分,將奇數部分和偶數部分單獨取出,對於兩部分分別取出顏色相同的所有元素,這部分所有元素就是可以任意兩兩組合成合法三元組的 x,zx,z ,那我們現在要做的就是計算這組元素的貢獻和

假設我們取出一組元素,比如取出一組同一個顏色的奇數元素,將這組元素的下標看做 a1,a2...a_1,a_2... 這組元素的 numbernumber 看做 n1,n2...n_1,n_2... ,任意兩兩組合計算貢獻,可以得到如下式子

化簡後的結果就是快速計算這組貢獻和的方法,所以這裏只需要在分別討論奇偶的情況下,記錄下同一個顏色的前綴下標和,前綴數字和,前綴數字乘下標的和,即可 O(n)O(n) 計算出結果

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
using namespace std;
long long n,m,num[100005],cont[2][100005],cl;
long long sum1[3][100005],sum2[3][100005];
long long ans;
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i = 1; i <= n; i++){
        scanf("%lld",&num[i]);
    }
    for(int i = 1; i <= n; i++){
        scanf("%lld",&cl);
        if(i % 2 == 1){
            ans = (ans + sum1[0][cl] % 10007 + i * sum1[1][cl] % 10007 + num[i] * sum1[2][cl] % 10007 + cont[0][cl] * i * num[i] % 10007) % 10007;
            sum1[0][cl] = (sum1[0][cl] + num[i]*i) % 10007;
            sum1[1][cl] = (sum1[1][cl] + num[i]) % 10007;
            sum1[2][cl] = (sum1[2][cl] + i) % 10007;
            cont[0][cl]++;
        } else {
            ans = (ans + sum2[0][cl] % 10007 + i * sum2[1][cl] % 10007 + num[i] * sum2[2][cl] % 10007 + cont[1][cl] * i * num[i] % 10007) % 10007;
            sum2[0][cl] = (sum2[0][cl] + num[i] * i) % 10007;
            sum2[1][cl] = (sum2[1][cl] + num[i]) % 10007;
            sum2[2][cl] = (sum2[2][cl] + i) % 10007;
            cont[1][cl]++;
        }
    }
    printf("%lld",ans % 10007);
}

T4 推銷員

點擊查看→ 計蒜客 [NOIP2015] 推銷員

題目描述
阿明是一名推銷員,他奉命到螺絲街推銷他們公司的產品。螺絲街是一條死衚衕,出口與入口是同一個,街道的一側是圍牆,另一側是住戶。螺絲街一共有 NN 家住戶,第i家住戶到入口的距離爲 SiS_i 米。由於同一棟房子裏可以有多家住戶,所以可能有多家住戶與入口的距離相等。阿明會從入口進入,依次向螺絲街的 XX 家住戶推銷產品,然後再原路走出去。

阿明每走 11 米就會積累 11 點疲勞值,向第 ii 家住戶推銷產品會積累 AiA_i 點疲勞值。阿明是工作狂,他想知道,對於不同的 XX ,在不走多餘的路的前提下,他最多可以積累多少點疲勞值。

輸入格式

第一行有一個正整數 NN ,表示螺絲街住戶的數量。

接下來的一行有 NN 個正整數,其中第 ii 個整數 SiS_i 表示第 ii 家住戶到入口的距離。數據保證 S1S2Sn<108S_1 \le S_2 \le … \le S_n<10^8

接下來的一行有 NN 個正整數,其中第 ii 個整數 AiA_i 表示向第i戶住戶推銷產品會積累的疲勞值。數據保證 Ai<103A_i<10^3

輸出格式

輸出 NN行,每行一個正整數,第i行整數表示當 X=iX=i 時,阿明最多積累的疲勞值。

數據範圍

對於 20%20\% 的數據,1N201 \le N \le 20

對於 40%40\% 的數據,1N1001 \le N \le 100

對於 60%60\% 的數據,1N10001 \le N \le 1000

對於 100%100\% 的數據,1N1000001 \le N \le 100000

樣例說明

樣例1:

X=1X=1: 向住戶 55 推銷,往返走路的疲勞值爲 5+55+5,推銷的疲勞值爲 55 ,總疲勞值爲 1515

X=2X=2 : 向住戶 4455 推銷,往返走路的疲勞值爲 5+55+5 ,推銷的疲勞值爲 4+54+5 ,總疲勞值爲 5+5+4+5=195+5+4+5=19

X=3X=3: 向住戶 334455 推銷,往返走路的疲勞值爲 5+55+5 ,推銷的疲勞值 3+4+53+4+5,總疲勞值爲 5+5+3+4+5=225+5+3+4+5=22

X=4X=4 : 向住戶 22334455 推銷,往返走路的疲勞值爲 5+55+5 ,推銷的疲勞值 2+3+4+52+3+4+5,總疲勞值 5+5+2+3+4+5=245+5+2+3+4+5=24

X=5X=5 : 向住戶 1122334455 推銷,往返走路的疲勞值爲 5+55+5,推銷的疲勞值 1+2+3+4+51+2+3+4+5,總疲勞值 5+5+1+2+3+4+5=255+5+1+2+3+4+5=25

樣例2:

X=1X=1 :向住戶 44 推銷,往返走路的疲勞值爲 4+44+4 ,推銷的疲勞值爲 44 ,總疲勞值 4+4+4=124+4+4=12

X=2X=2 :向住戶 1144 推銷,往返走路的疲勞值爲 4+44+4 ,推銷的疲勞值爲 5+45+4 ,總疲勞值 4+4+5+4=174+4+5+4=17

X=3X=3 :向住戶 112244 推銷,往返走路的疲勞值爲 4+44+4 ,推銷的疲勞值爲 5+4+45+4+4,總疲勞值 4+4+5+4+4=214+4+5+4+4=21

X=4X=4 :向住戶 11223344 推銷,往返走路的疲勞值爲 4+44+4,推銷的疲勞值爲 5+4+3+45+4+3+4,總疲勞值 4+4+5+4+3+4=244+4+5+4+3+4=24。或者向住戶 11224455 推銷,往返走路的疲勞值爲 5+55+5 ,推銷的疲勞值爲 5+4+4+15+4+4+1 ,總疲勞值 5+5+5+4+4+1=245+5+5+4+4+1=24

X=5X=5 :向住戶 1122334455 推銷,往返走路的疲勞值爲 5+55+5 ,推銷的疲勞值爲 5+4+3+4+15+4+3+4+1 ,總疲勞值 5+5+5+4+3+4+1=275+5+5+4+3+4+1=27

T4分析

40%40\% 分的數據有各種奇怪的做法…都可以

注意這裏有句話 不走多餘的路,所以簡單分析一下題目就可以得到題意:NNXX 的貢獻其實就是對於我們所選的 XX 家,總的疲勞值是2max(s[i])+a[i]2 * max(s[i]) +\sum a[i]

那麼正過來想,對於 X=1X = 1 的時候必然是選最大的 2s[i]+a[i]2 * s[i] + a[i]
那麼接着往下,X=2X = 2 其實無非就是在 X=1X = 1 的前提下,再選一家推銷,而這裏可以發現,如果上一次選的是 nownow,這次選 ii 則可以發現,如果選在上一次選的左邊,即 i<nowi < now 那貢獻只有 a[i]a[i] 而如果選在右邊即 i>nowi > now,貢獻則是 a[i]+2(s[i]s[now])a[i] + 2 * (s[i] - s[now])
X=2X = 2 的最大值必然存在於這兩種情況裏,所以由此可以推導出,每一步都可以這樣來選

當然也可以倒過來驗證這個思想,對於 X=NX = N 時,必然是所有人都選,在這個前提下考慮 X=N1X = N - 1
我們當然是去掉貢獻最小的,就會得到 X=N1X = N - 1 時的最大貢獻值,繼續往下每次減去最小影響的那戶人即可

所以這個貪心思路無疑是沒有問題的,那麼接下來就是如何實現的問題了,對於現在處於第 nownow 家時,我們可以發現,對於 i<nowi < now 的所有住戶只要求出最大的 a[i]a[i] 即可這一步我們完全可以用一個優先隊列或者堆來維護

而右邊部分我們要維護的則是 2(s[i]s[now])+a[i]2 * (s[i] - s[now]) + a[i],每次在兩種情況中選取最大值即可

當然這裏存在一種特殊情況,那就是如果左邊和右邊兩邊對當前這一步的貢獻值一樣,該選哪一邊?

其實取取誰都一樣,這個很容易證明,我們當前處於 nownow,現在有 k1<nowk1 < nowk2>nowk2 > now 同時滿足當前貢獻最大的情況,那麼我們這一步如果選 k1k1,那必然會使得 k2k2 的影響變得更大,因爲距離更遠了,所以我們下一步必然會選 k2k2,而如果這一步選的是 k2k2k1k1 本就是左邊 a[i]a[i] 中最大的,雖然選取 k2k2 後會使得處於左邊的數變多,但是原本在 nownow 右邊的點在額外附加了 2(s[i]now)2*(s[i] - now) 之後的 a[i]a[i] 都沒有 a[k1]a[k1] 大,那就算點變多了,左邊最大的依舊是 k1k1,所以下一步必然會選擇 k1k1

這裏如果右邊的點不使用優先隊列維護的話,時間複雜度最好情況下是 O(nlogn)O(nlogn),最壞情況下是 O(n2)O(n^2) 看數據,有可能因爲常數部分會被卡超時,如果數據較爲平均則能卡過去

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int n, s[maxn], a[maxn], curr, maxL, ans;
struct Node{
    int i, v;
    bool operator < (const Node& a)const{
        return v < a.v;
    }
}node,maxR;
priority_queue<Node>qR;
priority_queue<int>qL; 
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &s[i]);
    }
    for(int i = 1; i <= n; i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1; i <= n; i++){
        node.i = i;
        node.v = 2 * s[i] + a[i];
        qR.push(node);
    }
    for(int x = 1; x <= n; x++){
        maxL = maxR.v = 0;
        if(!qL.empty()){
            maxL = qL.top();
        }
        while(!qR.empty() && qR.top().i <= curr){
            qR.pop();
        }
        if(!qR.empty()){
            maxR=qR.top();
        }
        if(maxL < maxR.v - 2 * s[curr]){//當兩者相等時取哪邊都一樣 
            ans += maxR.v - 2 * s[curr];
            for(int k = curr + 1; k < maxR.i; k++){
                qL.push(a[k]);
            }
            curr = maxR.i;
            qR.pop();
        } else {
            ans += maxL;
            qL.pop();
        }
        printf("%d\n",ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章