模擬比賽-14屆研究生組C++省賽

_Yxc發表於2024-04-01

A 工作時長

題意:若干條打卡記錄字串(年月日時分秒格式),保證打卡記錄相鄰。求該年工作時長。

思路:對字串處理,轉換格式為秒數,排序後相鄰相減求和。

總結:2月有29天的情況要被4整除,如果能被100整除的話,一定要被400整除。

struct Data{
	int month;//5
	int day; //8
	int hour;   //11
	int minute; //14
	int second; //17

	long long cur = 0;
};

array<int, 13> days{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

void solve(){
	/*
	for (int i = 1; i < 13; ++i){
		days[i] += days[i - 1];
	}
	string s;
	vector<long long> a;
	while (getline(cin, s) && s != "!"){
		Data data;
		data.month = stoi(s.substr(5, 2));
		data.day = stoi(s.substr(8, 2));
		data.hour = stoi(s.substr(11, 2));
		data.minute = stoi(s.substr(14, 2));
		data.second = stoi(s.substr(17, 2));
		cout << data.month << " " << data.day <<" " << data.hour << " " << data.minute << " "<<data.second << endl;
		data.cur = (((days[data.month - 1] + data.day) *24 + data.hour) * 60 + data.minute) * 60 + data.second;
		a.push_back(data.cur);
	}

	sort(a.begin(), a.end());

	long long ans = 0;
	for (int i = 1; i < a.size(); i += 2){
		ans += a[i] - a[i - 1];
	}

	cout << ans << '\n';*/

	cout << 5101913 << '\n';
}

B 與或異或

題意:給定一個倒三角輸入電路,點代表數值,邊代表運算方式(有xor, or, and),第一層有5個節點,最後一層只有一個節點,問最後運算的結果為1的運算方式有多少種?第一層的輸入已經確定。

思路:一共10次運算,每次3個符號,時間複雜度為O(3 ^ 10),直接dfs按行暴力,行滿後換層,到最後一層是結果。

總結:記得之前比賽的時候,是程式跑了好多次,每跑一次儲存一次中間結果,一直到最後一層。只能說之前做題太少了,不懂dfs的優雅。

array<int, 5> d{5, 4, 3, 2, 1};

void solve(){
	// vector<array<int, 5>> a(5);
	// a[0] = {1, 0, 1, 0, 1};

	// long long ans = 0;
	// function<void(int, int)> dfs = [&](int x, int y){
	//     if (x == 5){
	//         ans += (a[4][0] == 1);
	//         return;
	//     }
	//     if (y == d[x]){
	//         dfs(x + 1, 0);
	//     }
	//     else{
	//         a[x][y] = a[x - 1][y] ^ a[x - 1][y + 1];
	//         dfs(x, y + 1);
	//         a[x][y] = a[x - 1][y] & a[x - 1][y + 1];
	//         dfs(x, y + 1);
	//         a[x][y] = a[x - 1][y] | a[x - 1][y + 1];
	//         dfs(x, y + 1);
	//     }
	// };

	// dfs(1, 0);

	// cout <<ans << '\n';

	cout << 30528 << '\n';
}

C 翻轉
題意:給定長度為n的字串s和t,問s最少多少次操作可以變成t。如果s中存在101,010,則可以進行變換:111, 000。

思路:考慮貪心策略,首先如果首尾不相等肯定不行,然後一次遍歷,如果當前某個節點需要變換,但是跟後面的相等不能變換,那麼就無解了,因為後面跟當前相等,後面也不能變換。 如果跟前面相等,那麼也無解了,因為如果前面沒變過,那麼直接無解。 如果前面變過,那麼當前如果先變,前面就沒法變了。

總結:貪心的演算法很簡單,但是正確性的證明確實需要時間,下次可以先把程式碼交上去,如果時間充裕再來證明正確性。

void solve(){
	string s, t;
	cin >> t >> s;

	if(s[0] != t[0] || s.back() != t.back()){
		cout << "-1\n";
		return;
	}
	int ans = 0;
	int n = int(s.size());
	for (int i = 1; i < n - 1; ++i){
		if (s[i] != t[i] && s[i - 1] != s[i] && s[i + 1] != s[i]){
			ans ++;
		}
		else if (s[i] != t[i]){
			cout << -1 << '\n';
			return;
		}
	}
	cout << ans << '\n';
}

D 階乘的和

題意:n個數,問能滿足對每個數的階乘求和後,最大的滿足條件的m是多少?m!必須是n個數求和後的因子。

思路:首先,m一定是所有的數里面最小的數的因子,因為每個數都包含了最小的數的階乘。那麼考慮m + 1呢?如果n個數裡面為m的數量,剛好可以被m+1整除,那麼若干個m的階乘的和,就可以轉換為若干個m+1的階乘的和。 比如6個2的階乘是2個3的階乘。

總結:涉及到數學推導,需要一步一步分析。。這種型別見的少。

void solve(){
	int n;
	cin >> n;

	map<int, int> mapp;
	for (int i = 0; i < n; ++i){
		int t;
		cin >> t;
		mapp[t] ++;
	}

	int ans = 1;
	for (auto&[x, y] : mapp){
		if (y % (x + 1) == 0){
			if (mapp.count(x + 1)){
				mapp[x + 1] += (y / (x + 1));
			}
			else {
				mapp[x + 1] = (y / (x + 1));
			}
		}
		else{
			ans = x;
			break;
		}
	}

	cout << ans << '\n';
}

E 公因數匹配

題意:n個數,gcd(a[i], a[j]) 大於1的最小的有序對i和j。

思路:暴力騙分gcd幾行程式碼搞定。 或者O(n)*log(n)的暴力質因子分解,使用map記錄上一次該質因子出現的位置。在所有有序對中找出最小的一個。為了提高效率,使用尤拉篩先篩出質因子,降低質因子分解的時間複雜度。

總結:沒注意第一個找到的有序對可能不是最小的有序對。沒注意對所有有序對進行儲存並排序的演算法可能會MLE。經過分析,先尤拉篩再分解的時間複雜度可能接近O(n)級別,與O(n * sqrt(n))比大幅降低。 特別是如果在數很大的情況下,不能使用暴力分解。
這裡尤拉篩的程式碼寫錯了,如果當前的i是prime的倍數的時候,在記錄完當前的i * prime就該break了,因為再往下就不是最小質因子推出來的合數了。比如i = 4, prime = 2,如果再往下那麼就是4 * 3 = 12,但是12應該由它的最小質因子2 * 6來標記。

bitset<10000000> bs;
vector<int> prime_values;

void sievePrimes(){
	bs.set();
	bs[0] = bs[1] = 0;
	for (int i = 2; i <= 1e6; ++i){
		if (bs[i]){
			prime_values.push_back(i);
		}
		for (const auto& prime : prime_values){
			if (1ll * prime * i > 1e6){
				break;
			}
			bs[prime * i] = 0;
		}
	}
}
inline bool changeMin(pair<int, int>& a, pair<int, int> b) {
	if (b.first < a.first){
		return a = b, true;
	}
	else if (a.first == b.first && b.second < a.second){
		return a = b, true;
	}
	return false;
}

void solve(){
	sievePrimes();
	int n;
	cin >> n;

	vector<int> a(n);
	for (int i = 0; i < n; ++i){
		cin >> a[i];
	}

	map<int, int> mapp;
	//vector<pair<int, int>> ans;
	pair<int, int> ans{1e9, 1e9};
	for (int i = 0; i < n; ++i){
		for (const auto& prime : prime_values){
			if (1ll * prime * prime > a[i]){
				break;
			}
			if (a[i] % prime == 0){
				if (mapp.count(prime)){
				   // ans.push_back({mapp[prime], i});
					changeMin(ans, {mapp[prime], i});
				}
				else{
					mapp[prime] = i;
				}
			}
			while (a[i] % prime == 0){
				a[i] /= prime;
			}
		}
		if (a[i] > 1){
			if (mapp.count(a[i])){
				//ans.push_back({mapp[a[i]], i});
				changeMin(ans, {mapp[a[i]], i});
			}
			else{
				mapp[a[i]] = i;
			}
		}
	}

  //  sort(ans.begin(), ans.end(), [&](pair<int, int>& a, pair<int, int>& b){
		 //   return a.first != b.first ? a.first < b.first : a.second < b.second;
		// });

	cout << ans.first + 1 << " " << ans.second + 1 << '\n';
}

F 奇怪的數

題意:給定n和m,求奇數位上為奇數,偶數位上為偶數,且連續5位數的和不超過m的數有多少個。

思路:數位dp?感覺不太像,數位dp的n一般都是十幾。 可以用遞推,記錄後4位的方案數,推出新的後4位的方案數。

總結:加了剪枝,細節壓縮到極致,最後的時間複雜度是pow(5, 5) * n = 6e8,最後4個點TLE過不去。暫時寫不出更好的演算法,先貼上去。

long long dp[2][11][11][11][11];
vector<array<int, 5>> nums{{0, 2, 4, 6, 8}, {1, 3, 5, 7, 9}};
constexpr int mod = 998244353;
void solve(){
	int n, m;
	cin >> n >> m;

	memset(dp, 0ll, sizeof(dp));

	for (int a = 0; a < 5; ++a){
		for (int b = 0; b < 5; ++b){
			for (int c = 0; c < 5; ++c){
				for (int d = 0; d < 5; ++d){
					for (int e = 0; e < 5; ++e){
						if (nums[1][a] + nums[0][b] + nums[1][c] + nums[0][d] + nums[1][e] <= m){
							dp[0][nums[0][b]][nums[1][c]][nums[0][d]][nums[1][e]] ++;
						}
					}
				}
			}
		}
	}


	long long ans = 0;
	int cur = 1;
	for (int i = 6; i <= n; ++i){
		int parity = (i & 1);
		int sum = 0;
		for (auto& a : nums[parity]){
			sum += a;
			for (auto& b : nums[!parity]){
				sum += b;
				for (auto& c : nums[parity]){
					sum += c;
					for (auto& d : nums[!parity]){
						sum += d;
						for (auto& e : nums[parity]){
							if (sum + e <= m){
								dp[cur][b][c][d][e] += dp[cur ^ 1][a][b][c][d];
								dp[cur][b][c][d][e] %= mod;
							}
						}
					}
				}
			}
		}
		cur ^= 1;
		memset(dp[cur], 0ll, sizeof(dp[cur]));
	}

	int parity = (n & 1);
	for (auto& b : nums[!parity]){
		for (auto& c : nums[parity]){
			for (auto& d : nums[!parity]){
				for (auto& e : nums[parity]){
					ans += dp[cur ^ 1][b][c][d][e];
					ans %= mod;
				}
			}
		}
	}

	cout << ans << '\n';
}

//慢慢補題

相關文章