Codeforces Round #627 (Div. 3)题解 cf 1324A 1324B 1324C 1324D 1324E 1324F
https://codeforces.com/contest/1324
A. Yet Another Tetris Problem
n列的俄罗斯方块画面,现在第\(i\)列已经有\(a_i\)块方块了,问能不能用若干个一列两行的方块把所有方块Sakuzyo。
判所有数字奇偶性是否相同就行,有手就能过
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;
int T, n, a[108];
bool gao()
{
for (int i = 1; i <= n; ++i)
{
if (a[i] % 2 != a[1] % 2)
return false;
}
return true;
}
int main()
{
cin >> T;
while (T--)
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
cout << (gao()? "YES":"NO") << endl;
}
}
B. Yet Another 有手就行Palindrome Problem
给一个长为n(\(\leq5000\))的数组,问是否存在一个长度至少为3的子序列是回文的。回文的定义是把序列reverse,序列不变,如[10,20,10]就是回文的。
考虑奇数长度的回文序列,删除其首尾元素仍然回文;再考虑偶数长度的回文序列,删除最中间那一对的某个元素,变成奇数长度的回文序列;因此原题等价于判断是否存在一个长度为3的子序列。for两遍就行。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;
int T, n, a[5008], b[5008];
bool gao()
{
for (int i = 1; i <= n; ++i)
{
for (int j = i + 1; j <= n; ++j)
{
if (b[a[j]])
return true;
}
b[a[i]] = 1;
}
return false;
}
int main()
{
cin >> T;
while (T--)
{
memset(b, 0, sizeof(b));
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
cout << (gao()? "YES":"NO") << endl;
}
}
C. Frog Jumps
给你一个由L和R组成的字符串\(s=Rs_1s_2...s_n(n\leq2\cdot10^5)\)。有只🐸站在最左边那个R那一格上,要跳到\(s_{n+1}\)的位置。规定站在L上只能往左跳,站在R上只能往右跳,一次最多跳k格。问k最小能到多少使得🐸至少有一种方案能跳过去。
很显然🐸应该避免跳到L上。那么原问题等价于求字符串里最长的全L子串的长度。
假设最长的全L字串的长度为\(k-1\),那么🐸必须要\(k\)的最大步长才能跳过这一段L,否则必定跳到其中某一个L上而只能往回跳。而很显然\(k\)的步长足够了,因为此时🐸在R上必定能花不多于k步往后跳到另一个R上。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;
int T;
char s[200008];
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%s", s + 1);
int n = strlen(s + 1);
int c = 0, ans = 0;
for (int i = 1; i <= n + 1; ++i)
{
if (s[i] == 'L')
{
c++;
}
else
{
ans = max(ans, c);
c = 0;
}
}
cout << ans + 1 << endl;
}
}
D. Pair of Topics
给两个长为\(n(\leq2\cdot 10^5)\)的数组\([a_1,a_2,...,a_n]\)和\([b_1,b_2,...,b_n]\)(\(1\leq a_i,b_i\leq 10^9\)),问存在多少对\(i,j(1\leq i<j\leq n)\)使得\(a_i+a_j>b_i+b_j\)。
先变个形,\(a_i-b_i>-(a_j-b_j)\)。设\(c_i=a_i-b_i\)那就是\(c_i+c_j>0\)。那就相当于给了一个数组问有多少对数的和大于0。两个正数的和必定大于0,因此关键就是统计有多少对一正一负的数。
不妨把所有正数提到一个数组\(P\)里,把0和负数提到另一个数组\(N\)里。把\(P\)升序排序。从前往后遍历\(P\)中的每一元素\(p_i\)。维护\(N\)的一个子集使得\(N\)中任意元素\(x\)都满足\(p_i+x>0\)。抽象一点的话,我们记\(N_i=\{x|x\in N\ and\ x+p_i>0\}\)。因为\(p_i<p_{i+1}\),所以若\(x+p_i>0\),那么\(x+p_{i+1}>0\)一定成立,所以有\(N_i\subseteq N_{i+1}\)。简单来说,就是已经满足条件了的负数在\(i\)前进时肯定满足条件。
那么只要把\(N\)降序排序,在N上二分查找就行。\(O(n\ logn)\)。
也可以排序之后维护\(N\)的一个指针\(j\),在枚举\(i\)时把\(j\)适当向后移使得\(p_i+n_j>0,p_i+n_{j+1}\leq 0\)就行了。总的时间复杂度还是\(O(n\ logn)\)。很经典的双指针做法。
(我没用这个做法所以不贴代码了
当然如果脑抽了,想不到这个办法,还可以上线段树:枚举\(j\)求有多少个\(i<j\)使得\(c_i>-c_j\)。那就直接区间查询,单点更新就完事了。当然值域太大,需要上一个离散化。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;
int n, a[200008], b[200008], tre[(200004 << 2)];
LL ans = 0;
map<int, int> mp;
void pushup(int rt)
{
tre[rt] = tre[rt << 1] + tre[rt << 1 | 1];
}
void update(int rt, int l, int r, int loc)
{
if (l == r)
{tre[rt]++;return;}
int mid = (l + r) >> 1;
if (loc <= mid)
update(rt << 1, l, mid, loc);
else
update(rt << 1 | 1, mid + 1, r, loc);
pushup(rt);
}
int query(int rt, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return tre[rt];
int mid = (l + r) >> 1;
int ret = 0;
if (L <= mid)
ret += query(rt << 1, l, mid, L, R);
if (mid + 1 <= R)
ret += query(rt << 1 | 1, mid + 1, r, L, R);
return ret;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i)
{
int x;
scanf("%d", &x);
a[i] -= x;
}
for (int i = 1; i <= n; ++i)
{
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int c = 0;
for (int i = 1; i <= n; ++i)
{
if (!mp[b[i]])
{
mp[b[i]] = ++c;
}
}
for (int i = 1; i <= n; ++i)
{
int* v = upper_bound(b + 1, b + n + 1, -a[i]);
if (v <= b + n)
{
ans += query(1, 1, n, mp[*v], n);
}
update(1, 1, n, mp[a[i]]);
}
cout << ans << endl;
}
E. Sleeping Schedule
某个星球上一天有\(h\)小时\((0,1,2,...,h-1)(h\leq 2000)\),现在有个人在某一天的0点整睡醒了。他现在要睡\(n(\leq 2000)\)次觉,其中第\(i\)次睡觉必定会在他醒来的\(a_i(1\leq a_i\leq h-1)\)小时之后发生。他每次都正好睡\(h\)小时醒来。
给定一对\(l,r(0\leq l<r\leq h-1)\),他觉得如果一次睡觉在\(l,l+1,...,r\)这几个时刻中的某一个时刻开始,那这次睡觉比较好。
现在他成为了大脑升级人,可以用大脑控制自己的清醒时间了,使得自己正好提早一小时睡觉(即选择一些数把它们减去1)。问他最多能得到多少次好的睡觉。
动态规划。\(f(i,j)\)表示睡完了前\(i-1\)次,在时刻\(j\)醒来,能得到的好睡觉的次数最大值。
那么两种状态转移:\(f(i,j)\to f(i+1,(j+a_i)\%h)\)和\(f(i,j)\to f(i+1,(j+a_i-1)\%h)\)。
答案就是\(\mathop{max}\limits_{j=0}^{h-1}{f(n+1,j)}\)。
非法状态注意处理一下就行。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;
int n, h, l, r, dp[2008][2008], a[2008];
int gao(int x)
{
return l <= x && x <= r;
}
int main()
{
scanf("%d %d %d %d", &n, &h, &l, &r);
for (int i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
}
memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < h; ++j)
{
if (dp[i][j] < 0) continue;
//printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
dp[i + 1][(j + a[i] - 1) % h] = max(dp[i + 1][(j + a[i] - 1) % h], dp[i][j] + gao((j + a[i] - 1) % h));
dp[i + 1][(j + a[i]) % h] = max(dp[i + 1][(j + a[i]) % h], dp[i][j] + gao((j + a[i]) % h));
}
}
int ans = 0;
for (int j = 0; j < h; ++j)
ans = max(ans, dp[n][j]);
cout << ans << endl;
}
F. Maximum White Subtree
给一棵无根树,一些点是黑的,另外的点是白的。定义一棵树的价值是它的白点数量减去黑点数量。现在对树上的每个点\(v\),询问所有包含点\(v\)的子树的最大价值是多少(换句话说,就是切掉一些子树,不能切掉\(v\),使得价值最大)。
点数\(n\leq 2\cdot10^5\)。
先考虑单个询问\(v\)的问题,不妨考虑\(v=1\):
把这棵树看成以\(1\)为根的有根树,设\(f(u)\)为以\(u\)为根的子树能达到的最大价值(必须包含\(u\)),那么很显然它满足一个子结构性:对\(u\)的每个儿子\(v\),要么把\(v\)这棵子树切掉,那么贡献是0,要么把\(v\)这棵子树留下,那么贡献是\(f(v)\)。子树之间的选择不相互干扰。那就有了\(f(u)=\sum\limits_{v\in son_u}max(0,f(v))\)。这就是个树形dp。
现在算出了以\(1\)为根的一系列\(f(u)\)值。设\(g(u)\)为点\(u\)的答案,那么就有了\(g(1)=f(1)\)。但是如果对每个点都去dp一次的话,复杂度是\(O(n^2)\)的,自然无法接受。
还是在以\(1\)为根的有根树上考虑:
假如说我知道了\(u\)这个点的答案\(g(u)\),那么我考虑它的一个儿子\(v\),假如把\(v\)这颗子树切出来,\(g(u)\)就会变成\(g(u)-max(0,f(v))\),这就是包含\(u\)但不包含\(v\)的最大价值。现在算\(g(v)\),就相当于要以\(v\)为根去dp了,那么事实上\(g(v)\)包含两部分:一是\(f(v)\),因为\(v\)这个点必须要选;二是原来是它的父亲,现在是它的儿子了(大雾???)的\(u\)的贡献,也就是包含\(u\)但不包含\(v\)的最大价值。
如图,切开\(u,v\)这条边,分开考虑。
因此,\(g(v)=g(u)-max(0,f(v))+f(v)\)。
那么我就能以\(O(1)\)的时间把答案从根转给儿子。
#include <bits/stdc++.h>
#define LL long long
#define pb push_back
#define eb emplace_back
#define pii pair<int,int>
#define pll pair<LL,LL>
using namespace std;
int n, a[200008], dp[200008], ans[200008];
vector<int> mp[200008];
void dfs(int rot, int pre)
{
for (auto nxt: mp[rot])
{
if (nxt == pre)
continue;
dfs(nxt, rot);
dp[rot] += max(0, dp[nxt]);
}
dp[rot] += (a[rot]?1:-1);
}
void dfs2(int rot, int pre)
{
for (auto nxt: mp[rot])
{
if (nxt == pre)
continue;
int x = ans[rot] - max(0, dp[nxt]);
ans[nxt] = dp[nxt] + max(0, x);
dfs2(nxt, rot);
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for (int i = 1; i < n; ++i)
{
int x, y;
scanf("%d %d", &x, &y);
mp[x].pb(y);
mp[y].pb(x);
}
dfs(1, 0);
ans[1] = dp[1];
dfs2(1, 0);
for (int i = 1; i <= n; ++i)
printf("%d ", ans[i]);
printf("\n");
}
后记
这好像还是我第一次在赛中ak了,之前还有两三次都是差一题ak,赛后就过了。
不过学弟也ak了,而且他还在1:50的时候截了个ak图出来,而我是在1:51才交的最后一题......
那就先祝他fst吧(?
(ak一个div3有什么好开心的,这不是应该的吗(
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步