模拟比赛-14届研究生组C++省赛
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';
}
G 太阳
题意:给定n条线段和一个制高点XY,以及每条线段的长度。求在这个制高点XY可以照到的线段有多少条。
思路:并查集,从左往右统计斜率,并作为整数映射到并查集上,如果当前的点没有被合并,那么可以被照到,并合并它。
总结:但是只能得一半的分,应该是有细节没处理好,再好好看看。
class DisjointSet{
public:
DisjointSet(int sz): sz_(sz){
fa_.resize(sz_);
iota(fa_.begin(), fa_.end(), 0);
}
int findSet(int x) { return fa_[x] == x ? x : fa_[x] = findSet(fa_[x]); }
bool unionSet(int x, int y){
int px = findSet(x);
int py = findSet(y);
if (px == py){
return false;
}
while (x < y){
fa_[x ++] = py;
}
return true;
}
private:
int sz_;
vector<int> fa_;
};
double RATIO = 1e8;
void solve(){
int n, x, y;
cin >> n >> x >> y;
vector<pair<double, double>> points(n);
vector<int> len(n);
vector<double> rat;
for (int i = 0; i < n; ++i){
cin >> points[i].first >> points[i].second >> len[i];
double k = (y - points[i].second) / (x - points[i].first);
k = double((long long)(k * RATIO)) / RATIO;
if (x - points[i].first == 0){
k = 0;
}
rat.push_back(k);
k = (y - points[i].second) / (x - points[i].first - len[i]);
k = double((long long)(k * RATIO)) / RATIO;
if (x - points[i].first - len[i] == 0){
k = 0;
}
rat.push_back(k);
}
sort(rat.begin(), rat.end());
map<double, int> mapp;
rat.erase(unique(rat.begin(), rat.end()), rat.end());
{
//点从左往右,反映在斜率上,就是斜率从接近0变大到无穷(正值),再从负无穷到接近0
int cnt = 0;
int p = lower_bound(rat.begin(), rat.end(), 0) - rat.begin();
for (int q = p; q < rat.size(); ++q){
mapp[rat[q]] = cnt ++;
}
mapp[0] = cnt ++;
for (int q = 0; q < p; ++q){
mapp[rat[q]] = cnt ++;
}
}
//for (auto x : rat){
// cout << x << " " << mapp[x] << endl;
// }
int m = rat.size();
DisjointSet dsu(m + 233);
vector<int> pos(n);
iota(pos.begin(), pos.end(), 0);
sort(pos.begin(), pos.end(), [&](int& a, int& b){
return points[a].second > points[b].second;
});
int ans = 0;
for (const auto& p : pos){
double k_left = (y - points[p].second) / (x - points[p].first);
k_left = double((long long)(k_left * RATIO)) / RATIO;
if (x == points[p].first){
k_left = 0;
}
int pl = mapp[k_left];
double k_right = (y - points[p].second) / (x - points[p].first - len[p]);
k_right = double((long long)(k_right * RATIO)) / RATIO;
if (x - points[p].first - len[p] == 0){
k_right = 0;
}
int pr = mapp[k_right];
//cout << k_left << " " << k_right << " " << pl << " " << pr << endl;
ans += dsu.unionSet(pl, pr);
}
cout << ans << '\n';
}
G 更新
理解一下,当斜率是负数的时候,可能比斜率是正数还要更大,所以直接的相除给斜率排序是没意义的。 给斜率排序,就要交叉相乘排序。斜率直接从小到大排序,左端点一定比右端点先出现。映射在x轴上也是从左往右。
bool operator != (const array<int, 3>& a, const array<int, 3>& b){
return 1ll * a[1] * b[0] != 1ll * a[0] * b[1];
}
void solve(){
int n, X, Y;
cin >> n >> X >> Y;
vector<array<int, 3>> points(2 * n);
for (int i = 0; i < n; ++i){
int x, y, l;
cin >> x >> y >> l;
points[2 * i] = (array<int, 3>{X - x, Y - y, i + 1});
points[2 * i + 1] = (array<int, 3>{X - x - l, Y - y, -i - 1});
}
sort(points.begin(), points.end(), [&](const array<int, 3>& a, const array<int, 3>& b){
return 1ll * a[1] * b[0] < 1ll * a[0] * b[1];
});
array<int, 3> last{-1, 1, 0};
set<pair<int, int>> sett;
vector<bool> vis(n + 1);
for (const auto& point : points){
if (last != point){
if (!sett.empty()){
vis[sett.begin()->second] = true;
}
last = point;
}
if (point[2] > 0){
sett.insert({point[1], point[2]});
}
else{
sett.erase({point[1], -point[2]});
}
}
int ans = 0;
for (int i = 1; i <= n; ++i){
ans += vis[i];
}
cout << ans << '\n';
}
H 子树的大小
题意:给定一个n个节点的m叉树,问第k个节点为根的树有多少个节点。
思路:根据多叉树的特性,确定左孩子,右孩子节点,一直到左右孩子的范围超过n,处理最后一层节点。
总结:题目不难,忘记开long long了。
void solve(){
long long n, m, k;
cin >> n >> m >> k;
long long cur = 1;
long long leftmost = k;
long long rightmost = k;
long long ans = 0;
while (rightmost <= n){
ans += cur;
leftmost = (leftmost - 1) * m + 2;
rightmost = (rightmost * m) + 1;
cur *= m;
}
if (leftmost <= n){
ans += n - leftmost + 1;
}
cout << ans << '\n';
}
//慢慢补题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效