Codeforces Round #627 (Div. 3)题解 cf 1324A 1324B 1324C 1324D 1324E 1324F

微笑、不失礼 提交于 2020-03-13 05:12:21

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\)的最大价值。

image

如图,切开\(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吧(?

image

(ak一个div3有什么好开心的,这不是应该的吗(

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!