1. 程式人生 > 實用技巧 >洛谷 11 月月賽 I & MCOI Round 3 Div.2 -- 村國

洛谷 11 月月賽 I & MCOI Round 3 Div.2 -- 村國

題目(https://www.luogu.com.cn/problem/P7043?contestId=36089):

C 國一共有 N 個村莊,N−1條道路。這些道路都可以雙向通行。保證小 S 可以從一座村莊到其他任何一座村莊。這 N 個村莊編號為 1 到 N。
剛開始小 S 對第 i 個村莊的好感值為 Ai。小 S 的假期一共有 M 天,他會在 C 國旅行一共M天。每一天他會選擇來到當前好感值最高的村莊。如果有好感值相同的村莊,他會選擇編號最小的村莊。假設這一天他來到村莊 X,那麼這一天結束後,與村莊X直接相鄰所有村莊的好感值都會增加 1。即能從 X 出發僅經過一條道路到達的村莊好感值會增加 1。因為小 S 已經在村莊 X 待過一天了,所以這一天結束後村莊 X 的好感值並不會增加。

現在小 S 想要知道經過 MM 天的旅行後好感值最高的村莊。

如果有多個好感值最高的村莊,輸出編號最小的。

輸入格式
本題單個測試點包含多組測試資料。
第一行一個正整數 T 表示資料組數。
對於每組資料:
第一行包括兩個正整數 N,M,表示村莊個數和旅行天數。
接下來一行 N 個整數,第 i 個整數表示第 i 座村莊的好感值 Ai。
接下來 N−1 行。每行兩個整數 x,y 表示村莊x和村莊y之間有一條雙向通行的道路。

輸出格式
一個整數表示 M 天結束後好感值最高的村莊的編號。如果有多個好感值最高的村莊,輸出編號最小的。

輸入輸出樣例
輸入
2
2 3
2 6
1 2
3 5
2 6 4
1 3
2 3

輸出
2
3

樣例說明
對於第一組資料,小 S 在 2 號村莊旅行了 3 天,結束時村莊 1,2 的好感值分別為 5,6。所以答案輸出 2。

對於第二組資料,結束時三個村莊的好感值分別為 3,7,8,所以答案輸出 3。

資料規模與約定
對於 100% 的資料,1≤N≤2×10^6,1≤M≤10^18,1<=Ai<=2^31 - 1,1 <= T <= 10。

題解:
先列坑點:

  1. N = 1的情況(特判)
  2. “在村莊 X 待過一天了,所以這一天結束後村莊 X 的好感值並不會增加。”這句話的意思是他去訪問那天不會增加,而不是說下一次訪問到他相鄰的時村莊時他再也不會增加(閱讀理解,直接打臉)

解題思路:

  1. 暴力騙分(30分),按照題意直接每次找出最大好感度村莊,然後將它周圍村莊依次好感度加一,最後輸出最大好感度村莊就完事。

  2. 模擬思維(100分):實際上最終結果肯定是一開始最大的好感度的村莊A,或者是A相鄰的最大好感度村莊B,肯定是這二者之一,核心就在這。接著開始模擬:

    • 如果只有一個村莊,直接輸出它的編號(特判)
    • 如果A與B相差的好感度小於旅行的天數,那麼必然是A
    • 如果A與B相差的好感度等於旅行的天數,或者A與B的好感度一開始就是相同的,那麼肯定是取A與B編號最小的(A與B字典序最小)那個村莊
    • 如果A與B相差的好感度大於旅行的天數,此時就要看剩餘的天數是偶數還是奇數,若是奇數,則取A與B編號最大的(A與B字典序最大)那個村莊;若是偶數,則取A與B編號最小的(A與B字典序最小)那個村莊。

30分程式碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<utility>
#include<string.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 5;
pair<LL, LL> a[N];
vector<LL> map[N]; 
int T;
LL n, m , ans = -1, ansCode = -1;
int main()
{
	ios::sync_with_stdio(false);
	cin >> T;
	while(T --)
	{

		memset(map, 0, sizeof(map));
		memset(a, 0, sizeof(a));
		ans = -1, ansCode = -1;
		cin >> n >> m;
		for(int i = 0; i < n; i++)
		{
			cin >> a[i].second; //村莊好感度 
			a[i].first = i; //村莊編號 
		}
		for(int i = 0; i < n - 1; i++)
		{
			int u, v;
			cin >> u >> v;
			u--, v--;
			map[u].push_back(v);
			map[v].push_back(u);
		} 
		while(m --)
		{
			LL nowCode = -1, tempVal = -1;
			for(int i = 0; i < n; i++)
			{
				if(a[i].second > tempVal)
				{
					tempVal = a[i].second;
					nowCode = i;
				}
			}
			for(int i = 0; i < map[nowCode].size(); i++)
			{
				LL code = map[nowCode][i];
				a[code].second ++;
			}	
		}
		for(int i = 0; i < n; i++)
		{
			if(a[i].second > ans)
			{
				ans = a[i].second;
				ansCode = i;
			}
		}
		ansCode ++;
		cout << ansCode << endl;
	}
	return 0;
}

100分程式碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<utility>
#include<string.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 5;
pair<LL, LL> a[N];
vector<LL> map[N]; 
int T;
LL n, m;
int main()
{
	ios::sync_with_stdio(false);
	cin >> T;
	while(T --)
	{
		memset(map, 0, sizeof(map));
		memset(a, 0, sizeof(a));
		cin >> n >> m;
		for(int i = 0; i < n; i++)
		{
			cin >> a[i].second; //村莊好感度 
			a[i].first = i; //村莊編號 
		}
		for(int i = 0; i < n - 1; i++)
		{
			int u, v;
			cin >> u >> v;
			u--, v--;
			map[u].push_back(v);
			map[v].push_back(u);
		}
		/*
		最大好感度村莊序號A(字典序最小),A相鄰最大好感度村莊B(字典序最小)。
		結果必其一 
		*/
		LL pos1 = -1, pos2 = -1, tempVal1 = -1, tempVal2 = -1; 
		for(int i = 0; i < n; i++) //找村莊A 
		{
			if(a[i].second > tempVal1)
			{
				tempVal1 = a[i].second;
				pos1 = i;
			}
		}
		for(int i = 0; i < map[pos1].size(); i++) //找村莊B
		{
			LL nowPos = map[pos1][i];
			if(a[nowPos].second > tempVal2)
			{
				tempVal2 = a[nowPos].second;
				pos2 = nowPos;
			}
		}
		LL disVal = a[pos1].second - a[pos2].second; //A與B相差的好感度
		if(n == 1) cout << pos1 + 1 << endl;
		else
		{
			if(disVal > m) cout << pos1 + 1 << endl;
			else if(disVal == m || disVal == 0) cout << min(pos1 + 1, pos2 + 1) << endl;
			else
			{
				m -= disVal;
				if(m % 2 != 0) cout <<  max(pos1 + 1, pos2 + 1) << endl;
				else cout << min(pos1 + 1, pos2 + 1) << endl;
			} 
		}
	}
	return 0;
}