contents
- 写在前面
- ==day01==
- T1【模拟】
- T2【模拟】
- T3【模拟】
- T4【哈希、组合数学】
- T5【贪心、双指针】
- ~~T6【01背包】~~
- ==day02==
- T1【最长公共子序列】
- ==day03==
- T1【模拟】
- T2【模拟】
- T3【模拟】
- T4【数学】
- T5【bfs | dfs】
- ~~T6【dp】~~
- ~~T7【数学】~~
- 补题
- *1【list】
- 出题
- *1【二进制枚举 | dfs+剪枝】
- ~~==day04== & ==day05==~~
- T1【模拟】
- T2【并查集】
- ~~T3【分治】~~
- ~~T4【背包】~~
- T5【数论】
- ~~T6【dp】~~
- ~~T7【二分】~~
- T【】
- ==day06==
- T1【模拟】
- T2【数学】
- T3【双指针 & 组合数学】
- T4【图 | 思维题】
- T5【贪心 & 排序】
- T6【贪心】
- ==day07==
- ~~==day08==~~
- T1【最长上升子序列】
- T2【二分】
- ~~==day09==~~
- ==day10==
- 出题
- *1【数学 | 排序 | 思维】
- ~~==day11== & ==day12== & ==day13== & ==day14==~~
- T1【数学】
- T2【数学 & 模拟】
- 这是我在学校留校的第一个暑假,为时两周,进行算法集训(
摸鱼)- 划线的标题表示还没做出来
比赛传送门
原题链接
原题链接
我的思路
观察样例,按照要求进行模拟即可
时间复杂度 O ( n ) O(n) O(n)
AC代码
void solve()
{
int n;
cin >> n;
for (int i = 0; i < 3 * n; i++)
{
for (int j = 0; j < n; j++) cout << "*";
for (int j = 0; j < 2 * n; j++) cout << ".";
for (int j = 0; j < n; j++) cout << "*";
cout << "\n";
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j ++) cout << ".";
for (int j = 0; j < n; j ++) cout << "*";
for (int j = 0; j < 2 * n - 2 * i; j ++) cout << ".";
for (int j = 0; j < n; j ++) cout << "*";
for (int j = 0; j < i; j ++) cout << ".";
cout << "\n";
}
}
原题链接
原题链接
我的思路
遍历字符串:若是猫,检查是否还有猫粮,如果没有,结束遍历;如果还有,猫粮-1。若是狗,检查是否还有狗粮,如果没有,结束遍历;如果还有,狗粮-1,同时猫粮+m。
最后从上述遍历结束的指针继续往后检查是否还存在没有吃到狗粮的狗即可。
时间复杂度 O ( n ) O(n) O(n)
AC代码
#include
using namespace std;
void solve(int num)
{
cout << "Case #" << num << ": ";
int n, d, c, m;
cin >> n >> d >> c >> m;
string s;
cin >> s;
int i;
for (i = 0; i < n; i++)
if (s[i] == 'C')
{
if (!c) break;
--c;
}
else
{
if (!d) break;
--d;
c += m;
}
bool ok = true;
for (int j = i; j < n; j++)
if (s[j] == 'D')
{
ok = false;
break;
}
if (ok) cout << "YES" << "\n";
else cout << "NO" << "\n";
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T; T = 1;
cin >> T;
// while(T--) solve();
for (int i = 1; i <= T; i ++) solve(i);
return 0;
}
原题链接
原题链接
我的思路
按照要求进行迭代计算即可。按照贪心的策略,每次将最左边的一个数以最右边的一个数为中点进行跳跃,然后判断最右边的数是否达到了n即可
时间复杂度 O ( l o g n ) O(log\ n) O(log n):精准计算过程个人不太会,于是我就按照最笨的方法计算最高的时间复杂度。即,假设 a = 2 , b = 3 , c = 4 a=2,b=3,c=4 a=2,b=3,c=4,进行迭代,迭代后的数组为:
2 3 4 6 9 14 22 35 56
不难发现,这个数列的后一项与前一项的差构成了一个斐波那契数列。我们可以近似的将这一串数字看做斐波那契额数列的前缀和。由于斐波那契额数列的增长是指数级别的,那么其前缀和自然也是指数级别的。因此想要让前缀和达到n的时间复杂度就是对数级别的,即 O ( l o g n ) O(log\ n) O(log n),证毕。
AC代码
void solve()
{
int l, m, r, n;
cin >> l >> m >> r >> n;
int res = 0;
while (r < n)
{
res++;
int t = l;
l = m;
m = r;
r = 2 * r - t;
}
cout << res << "\n";
}
原题链接
原题链接
我的思路
一开始WA了两发;
一发是因为纯暴力,想看过了几个测试点,结果就过了一个hhh;暴力很好写,就是枚举数字,然后查找相同数字,如果颜色不同计数器+1即可,不再赘述。
还有一发是思考角度有问题,我从数字出发,想要统计每种数字中颜色的分布关系。但其实很难实现,也就没能成功。
最后AC思路是,统计两种颜色中数字的组成情况,与上述第二个思路相反。即:每个颜色统计数字的分布情况,最后对照两个颜色数组中数字的组成情况,按照组合数学的计算公式计算即可。
时间复杂度: O ( n log n ) O(n\log{n}) O(nlogn)
AC代码
void solve()
{
int n; cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i ++) cin >> a[i];
string s; cin >> s;
vector<int> r, b;
for (int i = 0; i < n; i++)
if (s[i] == 'R') r.emplace_back(a[i]);
else b.emplace_back(a[i]);
sort(r.begin(), r.end());
sort(b.begin(), b.end());
ll res = 0;
for (int i = 0, j = 0; i < r.size() && j < b.size(); )
if (r[i] < b[j]) i++;
else if (r[i] > b[j]) j++;
else
{
int flag = r[i];
int x = 0, y = 0;
while (r[i] == flag) x++, i++;
while (b[j] == flag) y++, j++;
res += x * y;
}
cout << res << "\n";
}
原题链接
原题链接
我的思路
这道题比赛的时候没做出来,qaq。也想到了和原串对比,也想到了下标作差,但是唯独没想到正确的贪心思路。
即最小交换次数就是将每一个0或1按照排列的顺序依次从左往右移到模板串中正确的位置上即可。
代码实现使用双指针。一个指针是在模板串上,寻找0或1的位置;一个指针是在给定的串上寻找模板串开头的数字的下标,比如如果给的串0比1多,则在给定串上寻找每一个0的位置进行计算累加即可,而如果1比0多,则在给定串上寻找每一个1的位置进行计算累加即可。而对于0和1的数量相同的情况,取上述两种计算结果的最小值即可。
需要注意的是这道题要开long long,原题测试是需要开的,计算最大结果的方式如下:假设是最坏的情况,即给点串全部都是左边的一般半0和右边的一半1组成,那么第一个1移第一个位置需要 n 2 \frac{n}{2} 2n,第二个1移到第二个位置需要 n 2 − 1 \frac{n}{2}-1 2n−1,以此类推,最后一个1移到最后一个位置需要1,共有 n 2 \frac{n}{2} 2n次移动,那么这 n 2 \frac{n}{2} 2n次移动步数之和就是 n 2 n^2 n2级别的,因此需要开long long
时间复杂度 O ( n ) O(n) O(n)
AC代码
typedef long long ll;
ll calc(string& s, char c)
{
// i是标准01串的下标,j是给定串的下标
ll cnt = 0;
for (int i = 0, j = 0; i < s.size(); i += 2, j++)
{
while (s[j] != c) ++j;
cnt += abs(i - j);
}
return cnt;
}
void solve()
{
string s; cin >> s;
int cnt0 = count(s.begin(), s.end(), '0');
int cnt1 = count(s.begin(), s.end(), '1');
ll res = 0;
if (cnt1 > cnt0)
res = calc(s, '1');
else if (cnt1 < cnt0)
res = calc(s, '0');
else
res = min(calc(s, '1'), calc(s, '0'));
cout << res << "\n";
}
原题链接
原题链接
我的思路
这题比赛的时候都没怎么看,因为来不及看了qwq,显然是动态规划问题。然后此题是动规中01背包问题的变式。
当然,对于01背包问题,可以使用指数级别的枚举,时间复杂度是 O ( 2 n ) O(2^n) O(2n)
AC代码
题解文件 - zh
讲题、补题,无比赛
这是曾经做过的一道题,但是今天上台讲解的时候连题目都说不明白,特此补一下题解加深记忆。
原题链接
原题链接
大致题意:给定 s s s串与 t t t串,可以对 t t t串进行以下操作
问:最少可以进行多少次操作可以将 t t t串转化为 s s s串,如果不可以输出 − 1 -1 −1.
我的思路
首先需要注意的是t串一定要包含s串的所有字符及其相应的个数,只能多不能少。
其次就是算法设计的问题了:假设s串的固定长度为len,那么t串的最少移动次数就是len-s与t已经匹配的最长公共子序列
。
于是这道题的核心思路就是计算s与t串的最长公共子序列。
时间复杂度 O ( n m ) O(nm) O(nm),其中n为给定串的长度,即18;m为每一个测试点给的长度,总长不超过 5 × 1 0 5 5\times10^5 5×105,大约是 1 0 7 10^7 107级别,完全OK。
AC代码
#include
#include
#include
using namespace std;
bool ok(string& s, string& t)
{
for (int i = 0; i < s.size(); i++)
if (count(t.begin(), t.end(), s[i]) < count(s.begin(), s.end(), s[i]))
return false;
return true;
}
void solve()
{
string s = "crazythursdayvme50";
string t;
cin >> t;
if (!ok(s, t))
{
cout << -1 << "\n";
return;
}
// 便于使用最长公共子序列的模板
s = '0' + s;
t = '0' + t;
// f[i][j]表示s串的[1->i]个字符与t串的前[1->j]个字符的最长公共子序列
vector<vector<int>> f(20, vector<int>(t.size() + 10));
for (int i = 1; i <= 18; i++)
for (int j = 1; j <= t.size(); j++)
if (s[i] == t[j])
f[i][j] = f[i - 1][j - 1] + 1;
else
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
cout << 18 - f[18][t.size() - 1] << "\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T; cin >> T;
while (T--) solve();
return 0;
}
比赛传送门
原题链接
原题链接
我的思路
AC代码
void solve()
{
string s = "codeforces";
string t; cin >> t;
int res = 0;
for (int i = 0; i < s.size(); i++)
if (s[i] != t[i])
res ++;
cout << res << "\n";
}
原题链接
原题链接
我的思路
AC代码
void solve()
{
int n; cin >> n;
string s, t;
cin >> s >> t;
for (int i = 0; i < n; i++)
if (s[i] == 'G')
s[i] = 'B';
for (int i = 0; i < n; i++)
if (t[i] == 'G')
t[i] = 'B';
if (s == t) cout << "YES" << "\n";
else cout << "NO" << "\n";
}
原题链接
原题链接
题意大致是:给定字符串s,将其中的每个字符s[i]做一下变化:
给了这个变化后的字符串,让你找到原始的那个字符串
我的思路
AC代码
void solve()
{
int n; cin >> n;
string s; cin >> s;
char ch = s[0];
string res;
res += ch;
for (int i = 1; i < n;)
{
// 寻找到与ch相同的字符
while (s[i] != ch) i++;
// 再往后一位重新寻找ch
i++;
// 在没有越界的情况下
if (i < n)
{
// 添加当前字符到答案串
res += s[i];
// 重新定义ch
ch = s[i];
// 指针右移寻找后面与ch相同的字符
i++;
}
}
cout << res << "\n";
}
原题链接
原题链接
我的思路
思路其实非常简单,由于x轴与y轴是相互独立的,因此我们可以将二维图形转化为一维,即分别判断x轴与y轴的三个点的相对情况(n维也是同理)
如果A点在B、C一侧,即与B or C的最近距离
如果在B、C之间,则为0
AC代码
void solve()
{
int xa, ya; cin >> xa >> ya;
int xb, yb; cin >> xb >> yb;
int xc, yc; cin >> xc >> yc;
int res = 1;
if ((xa <= xb && xa <= xc) || (xa >= xb && xa >= xc))
res += min(abs(xa - xb), abs(xa - xc));
if ((ya <= yb && ya <= yc) || (ya >= yb && ya >= yc))
res += min(abs(ya - yb), abs(ya - yc));
cout << res << "\n";
}
原题链接
原题链接
大致题意是:给定一个数num和一个目标值target,要求对num进行以下操作
我的思路
问的是能否找到即可,那么其实宽搜和深搜都是可以实现的
方法一:宽搜
逻辑很简单,对于每一个数,如果等于target,那么直接跳出循环即可,如果可以被3整除,那么就拆成两个数然后放入队列
AC代码
void solve()
{
int num, tar;
cin >> num >> tar;
queue<int> q;
q.push(num);
bool ok = false;
while (!q.empty())
{
int t = q.front();
q.pop();
if (t == tar)
{
ok = true;
break;
}
if (t % 3 == 0)
{
q.push(t / 3);
q.push(2 * t / 3);
}
}
cout << (ok ? "Yes" : "No") << "\n";
}
方法二:深搜
代码很简单,逻辑的话其实就是画出一棵二叉搜索树,然后加上一些限制条件进行搜索就可以了。非常适合我这种搜索小进行练习与理解
AC代码
void dfs(int num, int tar, bool& ok)
{
if (num == tar)
{
ok = true;
return;
}
if (num % 3 == 0)
{
dfs(num / 3, tar, ok);
dfs(2 * num / 3, tar, ok);
}
}
void solve()
{
int num, tar;
cin >> num >> tar;
bool ok = false;
dfs(num, tar, ok);
cout << (ok ? "Yes" : "No") << "\n";
}
- 这道题在cf里面分数评定才为1000(还是我太菜了)。
- 在用两种方法写完了这道题后,我想出了一个改编的思路:
- 比如加强数据使得只能用bfs,时间复杂度的计算也很简单,对于bfs的时间复杂度就是结点数;
- 强化数据后可能会涉及long long;
- 当然问题也要随之改变,即求num到target的最短距离(但是发现貌似num到target的长度的都是相等的),于是我就想着换一下拆分数的规则,比如如果是4的倍数就拆成1 : 3的数据,对于3和4的公倍数,就还是按照3的倍数进行拆分,那么最终答案就是
dist[num][target]
- 可能会在下一次出题的时候完善出来吧hhh。感觉对于dfs这种深搜问题,先画出搜索树,再进行编码比较容易理解与掌握
原题链接
原题链接
我的思路
AC代码
原题链接
原题链接
我的思路
AC代码
主要是介绍一下C++标准库中list
的reverse_iterator
迭代器用法
即++操作就是让迭代器从右往左移动
void solve()
{
list<int> L;
int a[] = {3, 1, 5, 1, 8, 4}, n = 6;
for (int i = 0; i < n; i++)
L.push_back(a[i]);
list<int>::reverse_iterator it = L.rbegin();
for (int i = 0; i < n; i++)
cout << *(it++) << ' ';
}
输出为
4 8 1 5 1 3
大致题意就是从n个数中选k个数使得这k个数之和为质数,问共有多少种选法
方法有两种
一种是二进制枚举,时间复杂度是满的 O ( n 2 n max a [ i ] ) O(n\ 2^n\sqrt {\max a[i]}) O(n 2nmaxa[i]),如下
#include
#include
using namespace std;
// 判断质数的函数
bool is_prime(int x)
{
if (x <= 1) return false;
for (int i = 2; i <= x / i; i++)
if (x % i == 0)
return false;
return true;
}
int main()
{
// 读取数据
int n, k;
cin >> n >> k;
// 读取数据
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
// 定义答案
int res = 0;
// 二进制枚举
for (int i = 0; i < (1 << n); i++)
{
// 统计是否选择了k个数
int cnt = 0;
vector<int> v(25);
for (int j = 0; j < n; j++)
if (i & (1 << j))
v[cnt++] = a[j];
// 如果不是k个数就继续枚举
if (cnt != k) continue;
// 如果是k个数就计算和是否为质数
int sum = 0;
for (int j = 0; j < k; j++)
sum += v[j];
if (is_prime(sum)) res++;
}
// 输出答案
cout << res << "\n";
return 0;
}
另一种思路是搜索,加剪枝,虽然也是指数级别的算法,但是加上剪枝就会比上面的满 O ( 2 n ) O(2^n) O(2n)好,如下
#include
#include
using namespace std;
// 判断质数的函数
bool is_prime(int x)
{
if (x <= 1) return false;
for (int i = 2; i <= x / i; i++)
if (x % i == 0)
return false;
return true;
}
// 返回:选了m个数,起始枚举的数组下标为idx的情况下,这m个数之和sum为质数的方案数
int dfs(int m, int sum, int idx, int n, int k, vector<int>& a)
{
int res = 0;
if (m == k && is_prime(sum))
{
res++;
return 1;
}
// 剪枝
if (m + n - idx < k)
return 0;
for (int i = idx; i < n; i++)
res += dfs(m + 1, sum + a[i], i + 1, n, k, a);
return res;
}
int main()
{
// 读取数据
int n, k;
cin >> n >> k;
// 读取数据
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
cout << dfs(0, 0, 0, n, k, a) << "\n";
return 0;
}
比赛传送门
原题链接
原题链接
我的思路
AC代码
void solve()
{
int n; cin >> n;
ll sum = 0;
for (int i = 0; i < n; i ++)
{
ll x; cin >> x;
sum += x;
}
cout << ((sum % 2) ? "No" : "Yes") << "\n";
}
原题链接
原题链接
我的思路
AC代码
int n, m;
int fa[55], sum[55];
ll qmi(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
int find(int x)
{
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
void merge(int x, int y)
{
int fx = find(x), fy = find(y);
if(fx != fy)
{
sum[fy] += sum[fx];
fa[fx] = fy;
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
fa[i] = i;
sum[i] = 1;
}
while (m--)
{
int x, y;
cin >> x >> y;
merge(x, y);
}
unordered_map<int, bool> ha;
for (int i = 1; i <= n; i++)
{
int father = find(i);
ha[father] = true;
}
int cnt = 0, hi = 0;
for (auto x : ha)
cnt++, hi += sum[x.first];
hi -= cnt;
cout << qmi(2, hi) << "\n";
}
原题链接
原题链接
我的思路
merge_sort
函数的内容并不能完全理解,特此留一个坑AC代码
const int N = 1e5 + 10;
int n;
vector<ll> q(N), tmp(N);
ll res;
ll merge_sort(int l, int r)
{
if (l >= r) return 0;
ll mid = (l + r) >> 1;
merge_sort(l, mid), merge_sort(mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j])
tmp[k++] = q[i++];
else
{
tmp[k++] = q[j++];
res += mid - i + 1;
}
while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++)
q[i] = tmp[j];
return res;
}
void solve()
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> q[i];
res = merge_sort(0, n - 1);
if (res < 6)
{
cout << "YES" << "\n";
return;
}
else
cout << "NO" << ' ' << res << "\n";
}
原题链接
原题链接
我的思路
AC代码
原题链接
原题链接
我的思路
AC代码
void solve()
{
int n, k;
cin >> n >> k;
cout << (n - n % k) / k << "\n";
}
原题链接
原题链接
我的思路
AC代码
原题链接
原题链接
我的思路
WA代码
void solve()
{
int l, n, k;
cin >> l >> n >> k;
priority_queue<int> q;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 1; i < n; i++)
q.push(a[i] - a[i - 1]);
while (k--)
{
int t = q.top(); q.pop();
if (t % 2) q.push(t / 2), q.push(t / 2 + 1);
else q.push(t / 2), q.push(t / 2);
}
cout << q.top() << "\n";
}
原题链接
原题链接
我的思路
AC代码
记录人生中的首次AK!!!
- 虽然是小小的一次为时2h的小比赛
- 虽然最高难度只是cf的Div3的C题
- 但是身为算法小的我还是很开心的hhh
原题链接
原题链接
我的思路
AC代码
void solve()
{
double M, m, v;
cin >> M >> m >> v;
cout << fixed << setprecision(2) << (m * v / (M - m)) << "\n";
}
原题链接
原题链接
大致题意是:给定一个数n,可以进行以下两种操作
代价是所有的x之和,然后求让n变为0的最小代价
我的思路
根据给的数据范围就知道一定是找规律推公式
时间复杂度 O ( 1 ) \mathcal O(1) O(1)
AC代码
void solve()
{
ll n;
cin >> n;
int res = 0;
if (n == 0)
res = 0;
else if (n == 1)
res = 1;
else if (n % 2 == 0)
res = 2;
else
res = 3;
cout << res << "\n";
}
原题链接
原题链接
我的思路
其实就是判断字符串首尾的字符情况,分奇数与偶数讨论,但其实最终落实到代码上奇数与偶数的是一样的
对于奇数位只需要讨论最中间的那个字符
最后一种特判就是如果两个都是字符但是不相同,那么就不可能构成回文串,那么答案就是0
然后统计26方案的数量hi,最后根据组合数学的思路,答案就是 2 6 h i 26^{hi} 26hi
时间复杂度 O ( n ) \mathcal O(n) O(n)
AC代码
void solve()
{
string s; cin >> s;
int i = 0, j = s.size() - 1;
ll hi = 0;
bool ok = true;
while (i <= j)
{
if (s[i] == '?' && s[j] == '?')
hi++;
if (s[i] != '?' && s[j] != '?' && s[i] != s[j])
{
ok = false;
break;
}
i++, j--;
}
if (ok)
cout << qmi(26, hi, MOD) << "\n";
else
cout << 0 << "\n";
}
原题链接
原题链接
定义基环树为:一个n个点,n条边,没有自环和重边的无向连通图,
定义一个图的直径为:任意两点最短路的最大值,
那么由n个结点构成的基环树中的最小直径是多少
我的思路
看似很离谱的问题,因为数据很大,建图是不太可能的,那么就可以仔细理解直径同时画图进行实操hhh
为了让直径尽可能的小,那么我们画出的图中每个点之间的距离就一定要尽可能的近,于是我们想到画一个星射状的图,此时点数为n,边数为n-1,还差一条边就选任意两个还没有连起来的点连起来即可
最终得到的结论就是:只要是 n ≥ 4 n \ge 4 n≥4,则答案一定是2,如果 n = 3 n=3 n=3,则答案是1
时间复杂度 O ( 1 ) \mathcal O(1) O(1)
AC代码
void solve()
{
int n; cin >> n;
if (n == 3)
{
cout << 1 << "\n";
return;
}
cout << 2 << "\n";
}
原题链接
原题链接
我的思路
毕竟是cf的Div2的A题,是吧,那就没什么好多说的了,其实就是对于n个数,那么就一共有n-1个间隔数 n u m [ i ] num[i] num[i],而如果划分成k个组,其实就是减掉前面n-1个间隔中的k-1个间隔,而为了让这最终的攻击力最小,即求
m i n { ∑ i = 1 ( n − 1 ) − ( k − 1 ) n u m [ i ] } ,即 m i n { ∑ i = 1 n − k n u m [ i ] } min\{\sum_{i=1}^{(n-1)-(k-1)}num[i]\},即\ min\{\sum_{i=1}^{n-k}num[i]\} min{i=1∑(n−1)−(k−1)num[i]},即 min{i=1∑n−knum[i]}
于是想到将num数组进行排序,最终得到的最小攻击力就是num数组的前n-k个数之和
时间复杂度 O ( n log n ) \mathcal O(n\log{n}) O(nlogn)
AC代码
void solve()
{
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
// 计算num[i]
vector<int> num;
for (int i = 1; i < n; i++)
num.emplace_back(abs(a[i] - a[i - 1]));
sort(num.begin(), num.end());
int res = 0;
for (int i = 0; i < n - k; i++)
res += num[i];
cout << res << "\n";
}
原题链接
原题链接
我的思路
AC代码
// 某人的AC数与总罚时的计算函数
pair<int, int> calc(vector<int>& a, int m, int h)
{
// cnt为AC数,t为总罚时
int cnt = 0, t = 0;
// 贪心策略,按照罚时的升序来做题
sort(a.begin(), a.end());
// 每道题的罚时是:开始做这道题时比赛已经经过的时间s[i - 1] + 完成此题需要的时间a[i],因此需要计算前缀和
// 那么s[i]就表示完成第i题后的时间戳,显然这个s[i]是升序的
vector<int> s;
s.push_back(a[0]);
for (int i = 1; i < m; i++)
s.push_back(a[i] + s[i - 1]);
// 枚举每一个时间戳
for (int i = 0; i < m; i++)
{
// 如果AC掉这道题比赛时间还没结束,那么这道题就可以被AC掉并且总罚时加上这道题的罚时s[i]
if (h >= s[i])
{
cnt++;
t += s[i];
}
else break;
}
return make_pair(cnt, t);
}
// 第一关键词是AC的题目,第二关键词是总罚时
bool cmp(pair<int, int>& a, pair<int, int>& b)
{
if (a.first == b.first) return a.second < b.second;
return a.first > b.first;
}
void solve()
{
// n个人,m道题,总比赛时间h
int n, m, h;
cin >> n >> m >> h;
// 存每个人的AC数与总罚时
vector<pair<int, int>> arr;
// 第一个人的做每道题需要的时间
vector<int> a(m);
for (int i = 0; i < m; i++)
cin >> a[i];
// 获得第一个人的AC数与总罚时,即题目要求需要找排名的这个人
pair<int, int> flag = calc(a, m, h);
arr.push_back(flag);
// 计算其余选手的AC数与总罚时
for (int x = 1; x < n; x++)
{
vector<int> v(m);
for (int i = 0; i < m; i++)
cin >> v[i];
arr.push_back(calc(v, m, h));
}
// 按照ICPC的比赛规则,利用C++中的STL进行排序
sort(arr.begin(), arr.end(), cmp);
// 目标角色的下标
int idx = 0;
// 寻找这个人的具体下标从而得到排名
for (int i = 0; i < arr.size(); i++)
if (arr[i] == flag)
{
idx = i + 1;
break;
}
// 输出下标
cout << idx << "\n";
}
放假日~
比赛传送门
原题链接
原题链接
我的思路
抛开本道题的数据量,此题理论上有两种做法
法一:朴素动规
dp[i]
表示前 i i i 个数字中比当前数字小的数字的个数(由于求的是最长上升子序列,因此自身也算一个)
动规三步骤
dp[i] = max(dp[i], dp[j] + 1)
,如果是2. 则dp[i]
保持不变第一层枚举每个数,第二层枚举当前数之前的所有的数。就可以确保不漏答案
时间复杂度 O ( n 2 ) \mathcal O(n^2) O(n2)
AC代码
void solve()
{
// 读数据
int n; cin >> n;
// 读数据
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
// dp[i]表示前i个数字中比自己小的数字的个数
vector<int> dp(n + 1, 0);
// 初始化dp数组
dp[1] = 1;
for (int i = 1; i <= n; i++)
{
dp[i] = 1;
for (int j = 1; j < i; j++)
if (a[j] < a[i])
dp[i] = max(dp[i], dp[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i++)
res = max(res, dp[i]);
cout << res << "\n";
}
法二:二分优化动规(贪心思想)
可以发现,我们在每次更新dp[i]
时,需要枚举从1
到i-1
的每个dp[j]
,这其中是有很多重复的,于是思考优化
我们放弃dp
数组,采用last
数组与len变量
其中last[len]
表示长度为len
的最长上升子序列的最后一个数字
为了让一个子序列尽可能的长,就需要这个子序列的最后一个数尽可能的小【贪心】
最后得到的这个last
数组中每个数都是严格单调递增的
在利用a数组中的每个数更新last
数组时,对于每个last[i]
,我们想要找一个比a[i]
小的且最大的那个数
可以采用遍历整个last
数组来寻找这个target
数
当然,由于last
中的数有单调性,因此可以采用二分来加速查询
由于每个长度对应的数都是唯一的,因此理论上两个模板都是可行的
模板1:查左边的那个数
int l = 1, r = len;
while (l < r)
{
int mid_len = (l + r) >> 1;
if (last[mid_len] >= a[i]) r = mid_len;
else l = mid_len + 1;
}
模板2:查右边的那个数
int l = 1, r = len;
while (l < r)
{
int mid_len = (l + r + 1) >> 1;
if (last[mid_len] >= a[i]) r = mid_len - 1;
else l = mid_len;
}
二分查到这个数后需要进行特判
a[i]
大或相等,说明整个last
数组中没有比a[i]
严格小的数,那么直接更新这个last[r]
即可(其实这个last[r]
一定是last[1]
a[i]
小,需要特判这个数能否让len
进行扩增
last
数组中最大的那个数(其实就是最后一个数)还要大,那么len
就可以+1
last[r + 1]
为last[r + 1]
与a[r]
中的最小值时间复杂度 O ( n log n ) \mathcal O(n \log{n}) O(nlogn)
AC代码
void solve()
{
int n; cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
int len = 0;
vector<int> last(n + 1, 1e9);
last[++len] = a[1];
for (int i = 2; i <= n; i++)
{
int l = 1, r = len;
while (l < r)
{
int mid_len = (l + r + 1) >> 1;
if (last[mid_len] >= a[i]) r = mid_len - 1;
else l = mid_len;
}
if (last[r] >= a[i]) last[r] = a[i];
else
{
if (last[r + 1] == 1e9) len++;
last[r + 1] = min(last[r + 1], a[i]);
}
}
cout << len << "\n";
}
原题链接
原题链接
我的思路
AC代码
bool check(int m, ll n, ll k, vector<pair<ll, ll>>& a)
{
ll sum = 0;
for (int i = 1; i <= n; i++)
if (a[i].first >= m)
sum += a[i].second;
else break;
return sum <= k;
}
void solve()
{
ll n, k;
cin >> n >> k;
vector<pair<ll, ll>> a(n + 1);
ll max_day = 0;
for (int i = 1; i <= n; i++)
{
ll day, num;
cin >> day >> num;
max_day = max(max_day, day);
a[i].first = day, a[i].second = num;
}
sort(a.begin() + 1, a.begin() + n + 1, [&](pair<ll, ll> x, pair<ll, ll> y) {
return x.first > y.first;
});
int l = 1, r = max_day + 1;
while (l < r)
{
int m = (l + r) >> 1;
if (check(m, n, k, a)) r = m;
else l = m + 1;
}
cout << r << "\n";
}
比赛传送门
今天主要是出题,下面是我出题目的全部内容,具体OJ评测在CF上
OJ评测
原题:
AcWing 4653. 数位排序
改编思路:
单纯的数位之和没意思,多给几个选项输出最高排名即可。需要注意的是对于第二种方法理论上要开long long,同时第二种方法的时间复杂度无法在1秒内通过本题
有两种重新排位的方法
时空限制
时间 1 s 1s 1s
空间 256 M B 256MB 256MB
题面
牛魔王在美好的高中时代情窦初开,从高一就开始暗恋班里的一个安静可爱的女生–小美。高三的一个明朗的午后,阳光正好,他决定表白了❤️❤️
小美很上进,十分在意自己的考试排名,机智的牛魔王看着手里从咩霸那里偷来的两块水晶,想出了一个好方法来进行表白。那就是通过水晶修改世界的排名规则,让女孩的排名强行上升,以此来取悦小美。
已知两块水晶分别有不同的功效:
红色水晶的功效是:按照原始排名的数字的各数位之和进行重排名(当数位之和相等时,数值小的排在前面)
比如 2023 2023 2023 排在 602 602 602 前面,因为 2 + 0 + 2 + 3 = 7 < 6 + 0 + 2 = 8 2+0+2+3=7<6+0+2=8 2+0+2+3=7<6+0+2=8
紫色水晶的功效是:按照原始排名数字的 2 2 2 的每一个数位次方之积进行重排名(当指数之积相等时,数值小的排在前面)
比如 99 99 99 排在 100 100 100 后面,因为 2 9 × 2 9 > 2 1 × 2 0 × 2 0 2^9 \times 2^9>2^1 \times2^0 \times2^0 29×29>21×20×20
为了提高表白成功的概率,牛魔王需要选择合适的水晶(当然可以不选),来尽可能的让小美的排名升到最高。
请你帮助每次都考倒数第 K K K 名的牛魔王进行最佳决策,让小美的排名尽可能的高
输入
一行三个整数 K , N , M K,N,M K,N,M,均用一个空格隔开,分别代表倒数第 K K K 名,牛魔王的原始排名 N N N,小美的原始排名 M M M(保证数据合法)
输出
一个整数,表示修改后小美的最高排名
数据范围
对于 70 % 70\% 70% 的数据, 班级总人数 ≤ 1 0 3 班级总人数\le10^3 班级总人数≤103
对于 100 % 100\% 100% 的数据, 班级总人数 ≤ 1 0 6 班级总人数\le10^6 班级总人数≤106
样例
输入
1 13 2
输出
2
NOTE
牛魔王不需要进行任何选择,就能让小美保持住最高排名
时间复杂度计算:
本题不难发现第二种方法是一个幌子
虚晃一枪,因为次幂之积的指数其实就是数位之和,因此排序后的顺序是一样的如果没有发现这个性质直接进行模拟的话可以通过前 70 % 70\% 70% 的数据,时间复杂度是 O ( 10 × n log 2 n ) \mathcal O(10 \times n \log_2{n}) O(10×nlog2n)
但是!后面的三个数据点应该是过不去了,因为在 1 0 6 10^6 106 级别下,进行 s o r t sort sort 的时间复杂度理论上是 n log 2 n n \log_2{n} nlog2n,但是由于进行数位拆分以及算指数运算,常数会变得非常大。比如数位拆分均摊下来是 O ( 3 ) \mathcal O(3) O(3),快速幂均摊下来差不多也是 O ( 3 ) \mathcal O(3) O(3),于是 1 0 6 log 2 1 0 6 × 10 10^6 \log_2{10^6} \times10 106log2106×10,已经超过 1 1 1 秒了
还有一个坑点就是假如纯纯模拟,那么需要开 l o n g l o n g long\ long long long ,因为对于 1 0 6 10^6 106 的数据量,假设有一个数是 999999 999999 999999,那么 ( 2 9 ) 6 = 1 0 16 (2^9)^6=10^{16} (29)6=1016
代码实现:
TLE代码
#include
#include
#include
using namespace std;
typedef long long ll;
ll qmi(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
bool rule1(int x, int y)
{
int t1 = 0, t2 = 0;
for (int i = x; i; i /= 10) t1 += i % 10;
for (int i = y; i; i /= 10) t2 += i % 10;
if (t1 != t2) return t1 < t2;
else return x < y;
}
bool rule2(int x, int y)
{
ll t1 = 1, t2 = 1;
for (int i = x; i; i /= 10) t1 *= qmi(2, i % 10);
for (int i = y; i; i /= 10) t2 *= qmi(2, i % 10);
if (t1 != t2) return t1 < t2;
else return x < y;
}
void solve()
{
int k, n, m;
cin >> k >> n >> m;
// 全班人数
n = n + k - 1;
// 每个人的原始排名
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
a[i] = i;
// 数位之和规则下的排名
sort(a.begin() + 1, a.begin() + n + 1, rule1);
int res1;
for (int i = 1; i <= n; i++)
if (a[i] == m)
res1 = i;
// 2次幂之积规则下的排名
sort(a.begin() + 1, a.begin() + n + 1, rule2);
int res2;
for (int i = 1; i <= n; i++)
if (a[i] == m)
res2 = i;
cout << min(m, min(res1, res2)) << "\n";
}
int main()
{
solve();
return 0;
}
AC代码
#include
#include
#include
using namespace std;
bool rule1(int x, int y)
{
int t1 = 0, t2 = 0;
for (int i = x; i; i /= 10) t1 += i % 10;
for (int i = y; i; i /= 10) t2 += i % 10;
if (t1 != t2) return t1 < t2;
else return x < y;
}
void solve()
{
int k, n, m;
cin >> k >> n >> m;
// 全班人数
n = n + k - 1;
// 每个人的原始排名
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
a[i] = i;
// 数位之和规则下的排名
sort(a.begin() + 1, a.begin() + n + 1, rule1);
int res1;
for (int i = 1; i <= n; i++)
if (a[i] == m)
res1 = i;
cout << min(m, res1) << "\n";
}
int main()
{
solve();
return 0;
}
比赛传送门
原题链接
原题链接
我的思路
void solve()
{
ll x;
cin >> x;
if (x == 1)
cout << "YES\n";
else
cout << "NO\n";
}
原题链接
原题链接
我的思路
我们可以将单独存在的字符分为三种情况,即在开头;在中间;在结尾
举个例子比如HHHHGHHH,那么G作为开头,答案共2种;作为结尾,答案共三种;作为中间,答案共6种
我们将G左边连续的与它不同的字符个数记作L,右边的记作R,那么答案就是 L − 1 + R − 1 + L ∗ R L - 1 + R - 1 + L* R L−1+R−1+L∗R
于是我们枚举每一个字符,计算左右两边与他不相同的字符的个数代入上式计算求和即可
时间复杂度不太会计算,但是肯定是一次方级别的,姑且认为是 O ( n ) \mathcal O(n) O(n)
AC代码
void solve()
{
int n; cin >> n;
string s; cin >> s;
s = '0' + s;
ll res = 0;
for (int i = 1; i <= n; i++)
{
ll cntl = 0, pl = i - 1;
while (pl >= 1 && s[pl] != s[i])
pl--, cntl++;
ll cntr = 0, pr = i + 1;
while (pr <= n && s[pr] != s[i])
pr++, cntr++;
res += max(0ll, cntl - 1) + max(0ll, cntr - 1) + cntl * cntr;
}
cout << res << "\n";
}
感谢观看至此,为时两周的集训就完结撒花啦 感谢观看至此,为时两周的集训就完结撒花啦 感谢观看至此,为时两周的集训就完结撒花啦