个人思路,仅作记录,可以参考,欢迎交流。
比赛地址:传送门
A. Number Transformation
【题意】给定两个[1,100]内的整数x和y,求正整数a,b,使x*b^a=y
【思路】枚举[1,100]找b并求对应的a
【代码】
void solve()
{
int x, y;
cin >> x >> y;
if (y % x)
{
cout << "0 0\n";
return;
}
else if (y == x)
{
cout << "1 1\n";
return;
}
int a = 0, b = 0;
for (int i = 2; i <= 100; ++i)
{
int num = y / x;
if (num % i == 0)
{
b = i;
while (num > 1)
{
a++;
num /= b;
if (num > 1 && num % i)
{
a = 0;
break;
}
}
}
if (num == 1)
{
break;
}
}
cout << a << " " << b << '\n';
return;
}
B. Dictionary
【题意】给定一个含两个不同小写字母的单词s,求它在「所有含两个不同小写字母的单词」中的字典序
【思路】字典中以同一字母开头的单词有25个。
答案=首字母比s小的单词数+与s同首字母但第二个字母比s小的单词数+1
【代码】
void solve()
{
string s;
cin >> s;
cout << (s[0] - 'a') * 25 + (s[1] > s[0] ? s[1] - 'a' - 1 : s[1] - 'a') + 1 << '\n';
return;
}
C. Infinite Replacement
【题意】给定一个含n个’a’的字符串s和一个字符串t,求任意将s中的’a’用t替换能产生多少种不同的字符串
【思路】先考虑t中含’a’的特殊情况:
- ①如果t==”a”,怎么替换都不变,答案为1;
- ②如果t含’a’且长度大于1,可以无限套娃替换,答案为∞;
再考虑一般情况:
- ③如果t不含’a’,s中每个’a’都有2种情况——替换或不替换,答案为2^n
【代码】
void solve()
{
string s, t;
cin >> s >> t;
long long ans;
if (t == "a")
{
ans = 1;
}
else if (t.find('a') != string::npos)
{
ans = -1;
}
else
{
ans = (1ll << s.length());
}
cout << ans << '\n';
return;
}
D. A-B-C Sort
【题意】给定一个正整数数组A和一个包含两个操作的过程:
- 1. 不断取出A中最后一个数,放入空数组B的中间或中间数的两边
- 2. 不断取出B的中间数或中间两数的任一个,放入空数组C的末尾
求经过这一过程后C是否有可能为非降数组
【思路】题所说的过程即:每次从A末尾取出两个数,任意顺序分别放入两个栈中。A为空后,再每次分别取出两栈中等高度的两个数,任意顺序放入C末尾(如果A数组大小为奇数,则最后一个数单独特殊考虑)。经过分析,这个过程的作用其实就可以简化为:从A末尾开始算,每两个数之间可以调换顺序。显然,每次取出的两个数都小于等于上一次取出的两个数,最后才可能排序成功。
【代码】
int a[200005];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
if (n <= 2)
{
cout << "YES\n";
return;
}
for (int i = n - 2; i >= 1; i -= 2)
{
if (i > 1)
{
if (a[i] <= a[i + 2] && a[i - 1] <= a[i + 1] && a[i] <= a[i + 1] && a[i - 1] <= a[i + 2])
{
continue;
}
else
{
cout << "NO\n";
return;
}
}
else
{
if (a[i] <= a[i + 1] && a[i] <= a[i + 2])
{
continue;
}
else
{
cout << "NO\n";
return;
}
}
}
cout << "YES\n";
return;
}
E. Breaking the Wall
(第一次被Hack,居然是犯了个低级错,呜呜)
【题意】给定一个正整数组和一个操作:
- 选定数组中一个数,使该数-2且与该数相邻的数-1
求最少几次操作可以使数组中出现两个或以上小于等于0的数
【思路】最朴素的想法是尽量挑两个软柿子捏。但是由于相邻的数也会-1这一特性,还要考虑一些被减到0的数原来不是最小的特殊情况。可以分三种情况讨论:
- 1. 这两个数相邻,则每次优先对两个数中较大的一个进行操作(总之两数都大于0时两数之和每次要减少3),直到成功
- 2. 这两个数间隔1,则可以先对两数之间的数进行操作,直到其中两数之一减为0后,再对剩下的一个数进行操作(总之两数之和每次要减少2,我就栽在这了)
- 3.这两个数间隔大于1,则分别对它们进行操作直到都减为0
把这三种情况都考虑一遍,就能得到最少操作数。
【代码】
int a[200005];
int adj(int a, int b)
{
if (a < b)
{
swap(a, b);
}
if (a > b * 2)
{
return a / 2 + a % 2;
}
else
{
return (b - (a - b)) / 3 * 2 + (a - b) + (b - (a - b)) % 3;
}
}
int xox(int a, int b)
{
if (a < b)
{
swap(a, b);
}
return (a - b) / 2 + (a - b) % 2 + b;
}
int min_min(int a, int b)
{
return a / 2 + a % 2 + b / 2 + b % 2;
}
void solve()
{
int n;
cin >> n;
int ans = 0x3f3f3f3f;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
for (int i = 2; i <= n; ++i)//相邻
{
ans = min(ans, adj(a[i], a[i - 1]));
}
for (int i = 3; i <= n; ++i)//隔1
{
ans = min(ans, xox(a[i], a[i - 2]));
}
//最小次小
sort(a + 1, a + 1 + n);
ans = min(ans, min_min(a[1], a[2]));
cout << ans << '\n';
return;
}
F. Desktop Rearrangement
【题意】给定一个二维二进制数组和一系列将给定点取反的操作,求每一次取反操作后最少要交换几次任意两点才能使数组满足「每个1的左边和上面都是1」
【思路】
朴素的想法很简单:所有「当数组满足要求后应该为1的位置」上有多少个0就需要多少次交换操作,因为每次交换可以使一个应当为1的位置由0变为1。但是每次都O(nm)遍历数组找的话显然不满足时间复杂度要求,所以需要优化。
然而优化的想法也很简单:初始时记录数组中应该为1的位置数cnt和这些位置中0的个数empty_cnt。每次取反,只要进行如下三个O(1)操作:
- 1. 记录数组变化
- 2. 由数组的变化得到cnt变化
- 3. 由数组的变化和cnt的变化得到empty_cnt变化
每次取反后的答案即为empty_cnt。需要注意empty_cnt的变化既与数组变化有关(应该为1的点被取反),又与cnt的变化有关(应该为1的点增加或减少,即范围变化)。
【代码】
char map[1005][1005];
int n, m, q;
int cnt = 0;
int empty_cnt = 0;
void GetCnt()
{
for (int i = 1; i <= n; ++i)
{
cin >> map[i] + 1;
}
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
if (map[i][j] == '*')
{
cnt++;
}
}
}
return;
}
void GetEmptyCnt()
{
for (int j = 1; j <= cnt / n; ++j)
{
for (int k = 1; k <= n; ++k)
{
if (map[k][j] == '.')
{
empty_cnt++;
}
}
}
for (int j = 1; j <= cnt % n; ++j)
{
if (map[j][cnt / n + 1] == '.')
{
empty_cnt++;
}
}
return;
}
void solve()
{
cin >> n >> m >> q;
GetCnt();
GetEmptyCnt();
int row = 0, col = 0;
for (int i = 1; i <= q; ++i)
{
cin >> row >> col;
//改变地图
if (map[row][col] == '*')
{
if (map[cnt % n ? cnt % n : n][cnt / n + !!(cnt % n)] == '.')
{
empty_cnt--;
}
map[row][col] = '.';
cnt--;
}
else
{
cnt++;
if (map[cnt % n ? cnt % n : n][cnt / n + !!(cnt % n)] == '.')
{
empty_cnt++;
}
map[row][col] = '*';
}
//计算答案
if (col <= cnt / n || col == cnt / n + 1 && row <= cnt % n)
{
if (map[row][col] == '*')
{
empty_cnt--;
}
else
{
empty_cnt++;
}
}
cout << empty_cnt << '\n';
}
return;
}
G. Remove Directed Edges
【题意】给定一个有向无环图和一个过程:
- 删掉若干条边,使得图中所有出度不为0的结点出度至少减少1,所有入度不为0的结点入度至少减少1
求经过这一过程后,图的最大单向连通子图的大小
【思路】求最大单向连通子图大小其实就是求一条最长路(上的结点个数)。由于所有结点的出入度都要减少,所以只要一条路上所有结点的初始出入度(起点入度和终点出度除外)都至少为2,这条路就可能在删边过程后存在。所以只要以所有结点为起点进行记忆化dfs即可。细节见代码及注释。
【代码】
struct NODE
{
vector<int> nxt;
int suffix = -1;//以该点为起点的最大单向连通图
int in = 0;//该点入度
};
int n, m;
NODE node[200005];
int ans = 0;
void dfs(int num)
{
if (node[num].nxt.size() >= 2)//父出度≥2
{
for (unsigned i = 0; i < node[num].nxt.size(); ++i)//遍历子
{
if (node[node[num].nxt[i]].in >= 2)//子入度≥2
{
if (node[node[num].nxt[i]].suffix < 0)//子未被搜过
{
dfs(node[num].nxt[i]);
}
}
else//子入度<2
{
node[node[num].nxt[i]].suffix = 0;
}
node[num].suffix = max(node[num].suffix, node[node[num].nxt[i]].suffix + 1);//由子更新父
}
}
else//父出度<2
{
node[num].suffix = 1;
}
return;
}
void solve()
{
//选出一条最长路,满足每个点出度和入度都大于等于2(边界除外)
int u, v;
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
cin >> u >> v;
node[u].nxt.push_back(v);
node[v].in++;
}
for (int i = 1; i <= n; ++i)//注意:每个点都有机会作为起点
{
dfs(i);
ans = max(ans, node[i].suffix);
}
cout << ans << '\n';
return;
}
开心。
不过也是第一次掉分,简单题卡半天,还被Hack。还是太菜了,读题慢,代码功底弱,心态不够稳。继续加油吧!