noip2021训练3(CF)

Link

构造场

CF1348D Phoenix and Science

Description

在第 \(1\) 天,有一个质量为 \(1\) 的细菌

接下来每一天,\(1\) 个细菌可以分裂成 \(2\) 个,然后每个细菌的质量 \(+1\)

给定一个 \(n\),求在若干天后细菌质量和为 \(n\) 的方案。

输出最小的天数和每天分裂出多少个细菌,如果不存在方案输出 \(-1\)

\(2\le n \le 10^9\)

Solution

因为所有细菌的质量每天都会 \(+1\),所以不难发现早分裂比晚分裂更优。

每个细菌只能分裂成两个细菌,类似于二进制拆分,拆出来的结果就是每天细菌的个数,再做个差分就是每天分裂的个数了。

注意二进制拆分后要排序,最后可能会多出来一个数。

Code
#include <bits/stdc++.h>

using namespace std;

int ans[35], cnt;
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int n;
        scanf("%d", &n);
        int t = 1;
        cnt = 0;
        while(t <= n)
        {
            ans[++cnt] = t;
            n -= t;
            t <<= 1;
        }
        if(n) ans[++cnt] = n;
        sort(ans + 1, ans + 1 + cnt);
        printf("%d\n", cnt - 1);
        for(int i = 2; i <= cnt; i++)
            printf("%d ", ans[i] - ans[i - 1]);
        putchar('\n');
    }
    return 0;
}

CF1348B Phoenix and Beauty

Description

给出一个长度为 \(n\) 的序列 \(a\ (1\le a_i \le n)\),在其中任意位置插入若干个 \([1,n]\) 中的数,使得新序列中 \(b\) 的连续 \(k\) 项和都相等。

你可以输出任意一组长度在 \([n,10^4]\) 之间的解,若无解输出 \(-1\)

\(1\le k \le n \le 100\)

Solution

不难想到要构造一个长为 \(k\) 的循环节。

注意到 \(100\times100=10^4\),所以考虑暴力 \(O(nk)\) 的做法,对 \(a\) 序列中的每一个数都补成长为 \(k\) 的循环节。

循环节就是 \(a\) 序列中不同的数,如果个数 \(>k\) 则无解,如果 \(<k\),可以在后面补 \(1\)

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 110;
int T, n, m, a[N], t[N];
vector <int> ans;

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n, &m);
        memset(t, 0, sizeof(t));
        ans.clear();
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            if(!t[a[i]]) ans.push_back(a[i]);
            t[a[i]] = 1;
        }
        if((int)ans.size() > m)
        {
            printf("-1\n");
            continue;
        }
        printf("%d\n", n * m);
        for(int i = 1; i <= n; i++)
        {
            for(auto j : ans) printf("%d ", j);
            for(int j = (int)ans.size() + 1; j <= m; j++)
                printf("1 ");
        }
        printf("\n");
    }
    return 0;
}

CF1304D Shortest and Longest LIS

Description

给定一个有 \(n\) 个元素的排列中相邻两个元素的大小关系,分别构造一组解,使得序列的 \(LIS\) 最长和最短。

\(2\le n \le 2\times 10^5\)

Solution

在构造最长时,先将序列初始化成 \([1,n]\) 的排列,这样是最长的,然后将输入中连续的 < 取出来,并将这一段区间翻转。

感性理解一下 \(LIS\) 是最长的。

最短同理。

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5;
int T, n;
char s[N];
int ans[N];

void Shortest_LIS()
{
    for(int i = 1; i <= n; i++)
        ans[i] = n - i + 1;
    for(int i = 1, j; i <= n; i = j + 1)
    {
        j = i;
        while(s[j] == '<') j++;
        for(int k = j; k >= i; k--)
            printf("%d ", ans[k]);
    }
    putchar('\n');
    return;
}

void Longest_LIS()
{
    for(int i = 1; i <= n; i++)
        ans[i] = i;
    for(int i = 1, j; i <= n; i = j + 1)
    {
        j = i;
        while(s[j] == '>') j++;
        for(int k = j; k >= i; k--)
            printf("%d ", ans[k]);
    }
    putchar('\n');
    return;
}

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%s", &n, s + 1);
        Shortest_LIS();
        Longest_LIS();
    }
    return 0;
}

CF1312E Array Shrinking

Description

给你一个长度为 \(n\) 的数组 \(a\),每次你可以进行以下两步操作:

  1. 找到 \(i \in [1, n)\),使得 \(a_i = a_{i + 1}\)
  2. 它们 替换为 \(a_i + 1\)

每轮操作之后,显然数组的长度会减小 \(1\),问剩余数组长度的最小值。

\(1\le n \le 500,1\le a_i \le 1000\)

Solution

本题并不是构造(

考虑 dp

\(f_{i,j}\) 表示区间 \([i,j]\),最少能变成几个数,\(g_{i,j}\) 表示若区间 \([i,j]\) 能变成一个数那这个数是多少。

转移也比较简单

\(f_{i,j}=min(f_{i,j},f_{i,k}+f_{k+1,j})\ (i\le k <j)\)

\(f_{i,k}=1\ \& \ f_{k+1,j}=1\ \&\ g_{i,k}=g_{k+1,j}\)

\(f_{i,j}=1,g_{i,j}=g_{i,k}+1\)

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 510;
int n, f[N][N], g[N][N];

int main()
{
    scanf("%d", &n);
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &g[i][i]);
        f[i][i] = 1;
    }
    for(int len = 2; len <= n; len++)
    {
        for(int i = 1; i + len - 1 <= n; i++)
        {
            int j = i + len - 1;
            for(int k = i; k < j; k++)
            {
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
                if(f[i][k] == 1 && f[k + 1][j] == 1 && g[i][k] == g[k + 1][j])
                    f[i][j] = 1, g[i][j] = g[i][k] + 1;
            }
        }
    }
    printf("%d\n", f[1][n]);
    return 0;
}

CF1110E Magic Stones

Description

给定一个 \(n\) 个数的初始序列 \(c\) 和目标序列 \(t\),一次操作选择 \(i\in (1,n)\),使 \(c_i\) 变为 \(c_i'=c_{i+1}+c_{i-1}-c_i\)

是否能通过若干次操作,使得 \(c_i=t_i\)

\(2\le n \le 10^5,0\le c_i,t_i \le 2\times 10^9\)

Solution

比较巧妙

操作后的序列会变成 \(c_{i-1},c_{i+1}+c_{i-1}-c_i,c_{i+1}\)

发现差分数组为 \(c_{i+1}-c_i,c_i-c_{i-1}\)

而原序列的差分数组为 \(c_i-c_{i-1},c_{i+1}-c_i\)

值相同!!!

然后代码实现就比较简单了

只需要判断初始序列和目标序列的差分数组是否相同

无解还需要判断第一个和最后一个是否相同,因为操作无法修改这两个数。

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;
int n;
map <int, int> mp;

int main()
{
    scanf("%d", &n);
    int x, y;
    for(int i = 1, a, b = 0; i <= n; i++, b = a)
    {
        scanf("%d", &a), mp[a - b]++;
        if(i == 1) x = a;
        if(i == n) y = a;
    }
    for(int i = 1, a, b = 0; i <= n; i++, b = a)
    {
        scanf("%d", &a), mp[a - b]--;
        if(mp[a - b] < 0)
        {
            printf("No\n");
            return 0;
        }
        if((i == 1 && a != x) || (i == n && a != y))
        {
            printf("No\n");
            return 0;
        }
    }
    printf("Yes\n");
    return 0;
}

CF1110C Meaningless Operations

Description

定义函数 \(f(a)\) 的值为:

\(f(a)=\max_{0<b<a}\gcd(a\oplus b,a\ \&\ b)\)

给出 \(q\) 个询问,每个询问为一个整数 \(a_i\)。你需要对于每个询问,求出 \(f(a_i)\) 的值。

Solution

打表找规律

发现只要 \(a \neq 2^k-1\),那么 \(f(a)=2^k-1\),其中 \(2^k\) 为大于等于 \(a\) 的第一个 \(2\) 的整次幂。

对于 \(a=2^k-1\),如果看不出来规律,直接打表也是可以的

规律就是 \(f(a)=\dfrac{a}{mindiv}\)

这种题打表最舒服了

Code
#include <bits/stdc++.h>

using namespace std;

int main()
{
    // for(int i = 1; i <= 1e8; i++)
    // {
    //     if(i != pow(2, (int)log2(i) + 1) - 1) continue;
    //     int ans = 1;
    //     for(int j = 1; j < i; j++)
    //         if(__gcd(i ^ j, i & j) > __gcd(i ^ ans, i & ans)) ans = j;
    //     cout << i << " " << log2(i) << ": " << ans << " " << __gcd(i ^ ans, i & ans) << endl;
    // }
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int a;
        scanf("%d", &a);
        int ans = pow(2, (int)log2(a) + 1) - 1;
        if(a == ans)
        {
            for(int i = 2; i * i <= a; i++)
                if(a % i == 0)
                {
                    ans = a / i;
                    break;
                }
            if(ans == a) ans = 1;
        }
        printf("%d\n", ans);
    }
    return 0;
}

CF540C Ice Cave

Description

给你一个 \(n×m\) 的表格,每个点有两种状态,一种是好冰(‘.’),一种是碎冰(‘X’),并且好冰被走过后就变成碎冰了。

然后再告诉你起点和终点四个数 \((t1,c1),(t2,c2)\),问起点是否能到终点。

保证起点是碎冰,终点不一定,最后要求掉进起点(即是碎冰,可以踩一下到另一个点再回来),在这期间(除了起点和终点)不得掉进任何一个碎冰,如果可行输出 YES,不行 NO

Solution

也不是构造

普通的 dfs,只需要将 \(vis\) 数组做点变化,存一下是第几次到的即可。

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 510;
int n, m, sx, sy, tx, ty;
char s[N][N];
bool vis[N][N];
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
bool flag;

void dfs(int x, int y)
{
    if(x == tx && y == ty && vis[x][y] == 1)
    {
        flag = true;
        return;
    }
    if(vis[x][y] == 1 || flag) return;
    vis[x][y] = 1;

    for(int i = 0; i < 4; i++)
    {
        int wx = x + dx[i], wy = y + dy[i];
        if(wx < 1 || wx > n || wy < 1 || wy > m) continue;
        dfs(wx, wy);
        if(flag) return;
    }
    return;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%s", s[i] + 1);
        for(int j = 1; j <= m; j++)
            if(s[i][j] == 'X') vis[i][j] = 1;
    }
    scanf("%d%d", &sx, &sy);
    scanf("%d%d", &tx, &ty);
    for(int i = 0; i < 4; i++)
    {
        int x = sx + dx[i], y = sy + dy[i];
        if(x < 1 || x > n || y < 1 || y > m) continue;
        dfs(x, y);
        if(flag) break;
    }
    printf(flag ? "YES" : "NO");
    return 0;
}

CF989C A Mist of Florescence

Description

让你构造一个\(n\times m\) 矩阵,这个矩阵由 \(4\) 种字符填充构成,给定 \(4\) 个整数,即矩阵中每种字符构成的四联通块个数,\(n,m\) 需要你自己定,但是不能超过 \(50\)

\(1\le a,b,c,d \le 100\)

Solution

既然最大 \(50\times 50\),那就全用满,初始为 \(ABCD\) 各占一块

A | B
——|——
C | D

然后将 A 不够的在 B 中补,B 不够的在 C 中补,以此类推

因为最大不超过 \(100\) 个联通块,而 \(25\times 25 = 625\),显然是够的。

Code
#include <bits/stdc++.h>

using namespace std;

int a, b, c, d;
char ans[55][55];

void init(int x, int y, char c)
{
    for(int i = x; i <= x + 24; i++)
        for(int j = y; j <= y + 24; j++)
            ans[i][j] = c;
    return;
}

void work(int x, int y, char c, int num)
{
    for(int i = x + 1; i <= x + 24 && num; i += 2)
        for(int j = y + 1; j <= y + 24 && num; j += 2)
            ans[i][j] = c, num--;
    return;
}

void print()
{
    printf("50 50\n");
    for(int i = 1; i <= 50; i++)
    {
        for(int j = 1; j <= 50; j++)
            putchar(ans[i][j]);
        putchar('\n');
    }
    return;
}

int main()
{
    scanf("%d%d%d%d", &a, &b, &c, &d);

    init(1, 1, 'A');
    init(1, 26, 'B');
    init(26, 1, 'C');
    init(26, 26, 'D');
    
    work(1, 26, 'A', a - 1);
    work(26, 1, 'B', b - 1);
    work(26, 26, 'C', c - 1);
    work(1, 1, 'D', d - 1);

    print();

    return 0;
}

HDU6592 Beauty Of Unimodal Sequence

Description

给出 \(n\) 个数的序列 \(a_1,a_2\dots a_n\),找出 最长的单峰序列(先严格递增后严格递减,允许仅严格递增或仅严格递减)

要求分别输出所有最长的单峰序列 字典序最小 和 字典序最大 的序列(按其下标排序,输出时也是输出下标)

\(1\le n \le 3 \times 10^5,1\le a_i \le 10^9\)

Solution

树状数组+贪心

\(f_i\) 表示以 \(a_i\) 为开头的最长下降子序列长度

\(g_i\) 表示以 \(a_i\) 为开头的最长单峰子序列长度

这个可以用权值树状数组或权值线段树来维护

最长下降子序列就不说了,来说说最长单峰子序列

其实和最长下降子序列差不多,只是要维护后缀最大值,在查询时

\(g_i=max(f_i,qry(a_i+1)+1)\)

维护后缀只需要把插入改成 \(x>0\),把查询改成 \(x\le n\)

有了这两个数组后,就可以贪心构造序列了

字典序最小:正着枚举,能选就选,对于 \(a_i\),如果在选了 \(a_i\) 后可以达到最长,那就选上。

字典序最大:要把 \(a\) 数组反过来,其余同理,就是在输出时也要反着输出,并且要把下角标对应回去。

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 3e5 + 5;
int n, a[N], b[N], tot;
int c[N], f[N], g[N], len;
int pre, cnt, ans[N];

void add1(int x, int y)
{
    for(; x <= tot; x += x & -x)
        c[x] = max(c[x], y);
}

int qry1(int x)
{
    int res = 0;
    for(; x; x -= x & -x)
        res = max(res, c[x]);
    return res;
}

void add2(int x, int y)
{
    for(; x; x -= x & -x)
        c[x] = max(c[x], y);
}

int qry2(int x)
{
    int res = 0;
    for(; x <= tot; x += x & -x)
        res = max(res, c[x]);
    return res;
}

void prework()
{
    pre = 0, cnt = 0, len = 0;

    memset(c, 0, sizeof(c));
    for(int i = n; i >= 1; i--)
    {
        f[i] = qry1(a[i] - 1) + 1;
        add1(a[i], f[i]);
    }

    memset(c, 0, sizeof(c));
    for(int i = n; i >= 1; i--)
    {
        g[i] = max(f[i], qry2(a[i] + 1) + 1);
        add2(a[i], g[i]);
        len = max(len, g[i]);
    }

    return;
}

void solve()
{
    prework();
    for(int i = 1; i <= n; i++)
    {
        if(a[i] > pre && g[i] + cnt == len)
        {
            ans[++cnt] = i;
            pre = a[i];
        }
        else if(a[i] < pre && f[i] + cnt == len)
        {
            ans[++cnt] = i;
            pre = a[i];
        }
    }
    return;
}

int main()
{
    while(scanf("%d", &n) != EOF)
    {
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]), b[i] = a[i];
        
        sort(b + 1, b + 1 + n);
        tot = unique(b + 1, b + 1 + n) - b - 1;
        for(int i = 1; i <= n; i++)
            a[i] = lower_bound(b + 1, b + 1 + tot, a[i]) - b;

        solve();
        for(int i = 1; i <= cnt; i++)
            printf("%d%c", ans[i], i < cnt ? ' ' : '\n');

        reverse(a + 1, a + 1 + n);
        solve();
        for(int i = cnt; i >= 1; i--)
            printf("%d%c", n - ans[i] + 1, i > 1 ? ' ' : '\n');
    }
    return 0;
}
posted @ 2021-11-04 21:35  Acestar  阅读(143)  评论(1)    收藏  举报