1. 程式人生 > >[NHZXOI2017]2016NOIP普及組複賽題解

[NHZXOI2017]2016NOIP普及組複賽題解

  用了兩天的中午時間做了一套今年的普及組複賽試題結果測出來分數只有兩百!(第三題洛谷全過,lemon卻顯示編譯錯誤,奇了怪了微笑

第一題  買鉛筆

考查知識點:數學

解題思路:把每個產品買夠n支筆時的錢算出來,選取最小的。

程式碼:

#include <iostream>
#include <cstdio>
using namespace std;
char ch;
int getnum()
{
	ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar());
	int ret = 0;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) ret = ret * 10 + ch - 48;
	return ret;
}
int i,k,a,b,minnum;
int main()
{
	k = getnum(); 
	minnum = 1 << 30;
	for (i = 1; i <= 3; i++)
	{
		a = getnum(); b = getnum();
		if (a < k) 
		  if (k % a != 0) b = b * (k / a + 1); else b = b * (k / a);
		if (b < minnum) minnum = b;
	}
	printf("%d",minnum);
	return 0;
}

第二題:迴文日期

考察知識點:日期模擬,判斷迴文數

題解

①:設定三個變數,分別模擬年、月、日。

    注意當月有多少天?當年有多少天?

②:判斷迴文數,把原來的數翻轉然後跟原來的數判斷是否相同即可

程式碼:

#include <iostream>
#include <cstdio>
using namespace std;
char ch;
int date[4],arrived_date,num,ans;
int getnum()
{
	ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar());
	int ret = 0;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) ret = ret * 10 + ch - 48;
	return ret;
}
void iread()
{
	int temp,i;
	temp = getnum();
	date[1] = temp / 10000; temp %= 10000;
	date[2] = temp / 100; temp %= 100;
	date[3] = temp;
	arrived_date = getnum();
}
bool check()
{
	int i,x = 0,y = 0,temp;
	x = date[1] * 10000 + date[2] * 100 + date[3];
	temp = x;
	for (i = 8; i >= 1; i--)
	{
		y = y * 10 + temp % 10; temp /= 10;
	}  
	if (x == y) return true; else return false;
}
bool check_arrived()
{
	int i,x = 0,y = 0;
	x = date[1] * 10000 + date[2] * 100 + date[3];
	if (x <= arrived_date) return false; else return true;
}
bool check_year(int year)
{
	if (year % 100 != 0 && year % 4 == 0) return true;
	if (year % 400 == 0) return true;
	return false;
}
void solve()
{
	while (!check_arrived())
	{
		if (check()) ans++;
		date[3]++;
		if (date[3] > 31 && (date[2] == 1 || date[2] == 3 || date[2] == 5 || date[2] == 7 || date[2] == 8 || date[2] == 10 || date[2] == 12))
		{
			date[2]++; date[3] = 1;
		}
		if (date[3] > 30 && (date[2] == 4 || date[2] == 6 || date[2] == 9 || date[2] == 11))
		{
			date[2]++; date[3] = 1;
		}
		if (date[2] == 2 && ((date[3] > 28 && !check_year(date[1])) || (date[3] > 29 && check_year(date[1]))))
		{
			date[2]++; date[3] = 1;
		}
		if (date[2] > 12){
			date[1]++; date[2] = 1;
		}
	}
}
int main()
{
	
	iread();
	ans = 0;
	solve();
	cout << ans;
	return 0;
}

第三題:海港

考查知識點:模擬、指標、對時間、空間複雜度的計算

題解

  先來說一下怎麼做吧。一個是判斷日期相距是否在一天內,另一個是判斷有多少個乘客。

判斷時差:我們可以把船按時間先後順序排列好(題目給出就已經是這樣的)假如現在的船到達的時間是time[i],那麼可以選擇從第一個和i時差一天的船的位置。但由於時間是單調不減的,因此,在算與i時差一天的船位置時,可以在前一艘船的基礎上再往後找,就可以避免重複比較次數。

判斷當前有多少乘客:乘客是按數字來區分國家的,因此可以用一列陣列記錄當前i國家的人數有多少個。每加入一艘船的時候,就逐個記錄好(+1)。相對的,如果這艘船計算的時候不符合時差一天的條件,就逐個減去——同時

不要忘記了若減了後出現“0”,就代表少了一個國家的遊客。

注意:

 ,, 

設定陣列的時候如果單純的設定二維100000 * 3 * 100000,就中了出題者的陷阱。——空間溢位!所以建議在儲存遊客資訊的時候用指標記錄,能節省大量空間。而c++黨就可以用STL中的vector(懶人必備!)

程式碼:

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

char ch;
const int MAX_N = 100000 + 4;
const int MAX_X = 3 * 100000 + 4;
int n,l,visitor,i,j,k,time[MAX_N],num[MAX_X];
vector <int> x[MAX_N];

int getnum()
{
	ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar());
	int ret = 0;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) ret = ret * 10 + ch - 48;
	return ret;
}

int main()
{
	int temp;
	n = getnum(); l = 1; visitor = 0;
	for (i = 1; i <= n; i++){
		time[i] = getnum(); k = getnum();
		for (j = 1; j <= k; j++) 
		{
			temp = getnum();
			x[i].push_back(temp);
			num[temp]++; 
			if (num[temp] == 1) visitor++;
		}
		while (time[i] - time[l] >= 86400)
		{
			for (j = 0; j < x[l].size(); j++){
				num[x[l][j]]--;
				if (num[x[l][j]] == 0) visitor--;
			} 
			l++;
		}
		printf("%d\n",visitor);
	}
	return 0;
}
第四題:魔法陣

考查知識點:列舉、字首和、數學分析或者列舉+剪枝

題解

80分做法:列舉+剪枝

    按照題目意思,列舉每個物品當A、B、C、D的情況。這樣看起來是很簡單,但是這樣的列舉可能有兩三十分的差距。目測80為列舉最高分。

      所以就要看大家剪枝的功夫了。分析式子,用a代表A位置的物品魔法值,發現a<b<c<d——①  b-a=2(d-c)——②  4 * b-3 * a<c——③

          分析①和③,發現可以在列舉的時候做限制條件。②就提示我們,A、B、C都確定的時候,D就可以直接算出來

          還有物品的魔法值可能相同,所以可以根據乘法原理算出某個魔法值充當A或B或C或D的數值,最後再分別輸出物品相應的魔法值的充當A、B、C、D的次數。——完美!80分。

100分做法:列舉、字首和、數學分析

這題的正解的確很666。打完模擬後,你就開始尋思哪裡有你忽略的地方或者重複計算的地方。

,,

回想一下剛才模擬的方法裡面,先算出某個魔法值其充當A、B、C、D的次數,而物品的資料只是用來計算的時候起到一點作用。就可以想到是不是可以直接在魔法值上面做。枚舉出A、B、C、D來。首先,由模擬思路得出,A,B,C得出後D就確定,時間為O(n * n * n)不過,我們重新分析一下式子②d和c的距離(魔法值差值)為b和a的一半,③的原式——xb-xa<(xc-xb)/3,有c和b的距離和b和c的距離關係。

設:d - c = i

則 a - b = 2 * i         c - b > 6 * i

因此,我們可以列舉這個距離差,然後分別列舉a和d的距離。由於距離關係,我們省去了一點時間。敏感的人會發現,這其中有重複的計算。即a確定,i(距離)確定,列舉c到b的距離即可,但是當前a的計算和前一個a的計算有重疊!!!所以應該調整一下計算順序,用字首和或者(字尾和)記錄一下就好了。

程式碼:

模擬80

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

char ch;
const int MAX_N = 40000 + 4;
int a[MAX_N],b[MAX_N],c[MAX_N],num[MAX_N][5],aa,bb,cc,dd,map[5],n,m,tip[MAX_N];
int getnum()
{
    ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar());
    int ret = 0;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) ret = ret * 10 + ch - 48;
    return ret;
}
bool cmp(int a,int b)
{
    return a < b;
}
int qfind(int num, int l, int r)
{
    int mid;
    while (l < r)
    {
        mid = (l + r) / 2;
        if (num <= b[mid]) r = mid - 1; else l = mid;
    }
    return l;
}
void dfs(int k)
{
    if (k == 4)
    {
        aa = map[1]; bb = map[2]; cc = map[3];
        if ((b[bb] + 2 * b[cc] - b[aa]) % 2 !=0) return;
        dd=(b[bb] + 2 * b[cc] - b[aa]) / 2;
        if (tip[dd] && dd > b[cc]){
            num[b[aa]][1] += tip[b[bb]] * tip[b[cc]] * tip[dd]; 
            num[b[bb]][2] += tip[b[aa]] * tip[b[cc]] * tip[dd];
            num[b[cc]][3] += tip[b[aa]] * tip[b[bb]] * tip[dd];
            num[dd][4] += tip[b[aa]] * tip[b[bb]] * tip[b[cc]];
            //cout << b[aa] << ' ' << b[bb] << ' ' << b[cc] << ' ' << dd << endl;
        } 
        return;
    }
    int i;
    for (i = map[k - 1] + 1; i <= n; i++)
    {
        if (k == 3 && b[i] <= b[map[2]] * 4 - b[map[1]] * 3) continue;
        map[k] = i;
        dfs(k + 1);
    }
}

int main()
{
    
    int i,j;
    n = getnum(); m = getnum();
    for (i = 1; i <= m; i++) c[i] = a[i] = getnum();
    sort(a+1,a+m+1,cmp);
    j = 1;
    for (i = 1; i <= m; i++)                        //去重 
    {
        tip[a[i]]++;
        if (a[i] == b[j - 1]) continue;
        b[j] = a[i]; j++;
    }
    n = j - 1;
    dfs(1);
    for (i = 1; i <= m; i++)
        cout << num[c[i]][1] << ' ' << num[c[i]][2] << ' ' << num[c[i]][3] << ' ' << num[c[i]][4] << endl;
    return 0;
}
100分:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string.h>
using namespace std;

char ch;
const int MAX_N = 40000 + 4;
int a[MAX_N],b[MAX_N],c[MAX_N],d[MAX_N],sum,n,m,tip[MAX_N],v[MAX_N];
int getnum()
{
    ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar());
    int ret = 0;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) ret = ret * 10 + ch - 48;
    return ret;
}
int main()
{
    
    int i,j;
    n = getnum(); m = getnum();
    for (i = 1; i <= m; i++) 
    {
        v[i] = getnum(); tip[v[i]]++;
    }
    for (i = 1; i * 9 + 1 <= n; i++)
    {
        sum = 0;
        for (j = n - 9 * i  - 1; j >= 1; j--)
        {
            sum += tip[j + 8 * i + 1] * tip[j + 9 * i + 1];
            a[j] += tip[j + 2 * i] * sum;
            b[j + 2 * i] += tip[j] * sum;
        }
        sum = 0;
        for (j = 1; j <= n - 9 * i  - 1; j++)
        {
            sum += tip[j] * tip[j + 2 * i];
            c[j + 8 * i + 1] += tip[j + 9 * i + 1] * sum;
            d[j + 9 * i + 1] += tip[j + 8 * i + 1] * sum;
        }
    }
    for (i = 1; i <= m; i++)
        cout << a[v[i]] << ' ' << b[v[i]] << ' ' << c[v[i]] << ' ' << d[v[i]] << endl;
    return 0;
}