CSP-S模拟赛6

T1.玩水

一道非常简单的结论题(但是赛时最后半个小时才想出来)。首先考虑两个人的情况,显然只要有一个岔路就可以了。如图:
abbc
像这样的c点,我们把它称为一个岔路点。

以此类推,三个人只需要两个岔路点就行。但是分为两种情况:
1.在一个岔路点的严格左上角方向有一个岔路点(因为在经过第一个岔路点之后可以走相同的路来到达第二个岔路点);
2.在一个岔路点上方或左方相邻的地方有一个岔路点(一步到位)。(没有判这个但只挂了20分)

如图所示:
abklabcabebcgfbcdbceopfheeecde

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; const int Z = 1100;

int n, m, ans;
char a[Z][Z];
int dp[Z][Z], sum[Z][Z];

sandom main()
{
    fre(water, water);
    int T; cin >> T;
    while (T--)
    {
        cin >> n >> m; ans = 0;
        for (re i = 1; i <= n; i++) scanf("%s", a[i] + 1);
        for (re i = 2; i <= n; i++)//前i行前j列是否有岔路
            for (re j = 2; j <= m; j++)
            {
                dp[i][j] = a[i - 1][j] == a[i][j - 1];
                sum[i][j] = dp[i][j] | sum[i - 1][j] | sum[i][j - 1];
            }
        for (re i = 2; i <= n; i++)//1.在(i, j)的左上方有岔路
            for (re j = 2; j <= m; j++)//2.在与(i, j)相邻的位置有岔路
                if (dp[i][j] && (dp[i - 1][j] | dp[i][j - 1] | sum[i - 1][j - 1]))//有两个岔路就行
                { ans = 1; break; }
        ans ? puts("1") : puts("0");
    }
    return 0;
}

T2.AVL树

树上贪心。因为把根删除,孩子也全没了,所以中序遍历贪心等价于先序遍历贪心。
我们想要尽可能的保留左孩子,而舍弃右孩子,我们可以优先递归左孩子。对于每一个节点,我们需要判断它是否可以保留,回溯它的所有父亲,对于该节点是左孩子的情况,我们通过深度与AVL的定义,计算出保留它,至少需要右子树中有几个点,直到根,我们便算出了保留这个点需要整棵树至少多大。如果当前剩下的k足够,那就可以选。
首先可以预处理出来f[i],表示深度为i的AVL树,节点数至少多大。有递推式f[i]=f[i1]+f[i2]+1,可以看作一边深度为i1,一边深度为i2,在上面接上了根节点。
但是纯贪心会有错误,我们可能在左孩子选了过多的点,而导致右孩子不够选。所以需要预估AVL的深度。我们需要定义dis[rt]:以rt为根的子树的极限可能深度;nw[rt]:已选子树中的最大深度;mx[rt]:如果留下rt,需要至少往下延伸到第几层。在查询时,利用这些数组找到可能的最大深度,进而判定是否留下;当确定一个点被选时,向上更新它的父亲的最大深度信息。
由于这棵树本身就是一棵AVL,所以深度为logn,时间复杂度为O(nlogn)

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; 
const int Z = 5e5 + 100;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, k;
int ans[Z], f[50], root;
#define lk kid[rt][0]
#define rk kid[rt][1]
int kid[Z][2], dad[Z], dep[Z];
int nw[Z], mx[Z], dis[Z];
inline void update(int u)
{
    nw[u] = max(nw[u], dep[u]);
    int now = u, rt = dad[u];
    while (rt)
    {
        nw[rt] = max(nw[rt], dep[u]);//已选子树中的最大深度
        if (now == lk && rk) mx[rk] = max(mx[rk], nw[rt] - 1);//至少需要多少深度
        now = rt, rt = dad[rt];
    }
}
inline int query(int u)
{
    int now = u, rt = dad[u], sum = 0;
    while (rt)
    {
        if (now == lk) sum += f[max(max(nw[rt], dep[u]) - 1, mx[rk]) - dep[rt]];//把至少需要的深度取出来
        now = rt, rt = dad[rt];
    }
    return sum;
}
void dfs1(int rt)
{
    dis[rt] = dep[rt] = dep[dad[rt]] + 1;
    if (lk) dfs1(lk), dis[rt] = max(dis[rt], dis[lk]);//下限
    if (rk) dfs1(rk), dis[rt] = max(dis[rt], dis[rk]);
}
void dfs2(int rt)
{
    if (!rt) return;
    if (query(rt) <= k - 1)
    {
        ans[rt] = 1, k--;
        update(rt);
    }
    if (lk && dis[lk] >= mx[rt])//优先左子树
    {
        mx[lk] = max(mx[lk], mx[rt]);
        if (rk) mx[rk] = max(mx[rk], mx[rt] - 1);
    }
    else if (rk)//左子树不够就右子树
    {
        mx[rk] = max(mx[rk], mx[rt]);
        if (lk) mx[lk] = max(mx[lk], mx[rt] - 1);
    }
    dfs2(lk), dfs2(rk);
}

sandom main()
{
    fre(avl, avl);
    n = read(), k = read();
    f[1] = 1;
    for (re i = 2; i <= 30; i++) f[i] = f[i - 1] + f[i - 2] + 1;
    for (re i = 1; i <= n; i++)
    {
        dad[i] = read();
        if (dad[i] == -1) { dad[i] = 0, root = i; continue; }
        if (i < dad[i]) kid[dad[i]][0] = i;
        else kid[dad[i]][1] = i;
    }
    dfs1(root); dfs2(root);
    for (re i = 1; i <= n; i++) putchar(ans[i] | 48); putchar('\n');
    return 0;
}

T3.暴雨

先抛出一个问题,对于题面:如果一个物体,既不属于一个空间的内部,也不处于这个空间的边界,那么它处于这个空间的什么位置。(一个普通的物理积水能被出题人说的这么高深)。
定义dp[i][j][k][0/1]:前i块土地,最大高度为j,且之后存在一个比j高的土地(可以理解为之后有一个无限高的墙),铲平了k块,积水体积为偶数/奇数的方案数。这是前缀dp,同理,后缀dp为后i块土地。
虽然写出了dp式子,但我们发现即使离散化,复杂度也是O(n2k),发现k的范围很小,所以从这里入手。对于一个区间,初始最大高度只有一个而且是固定的,每铲平一块,最大高度值最多会变化一次,而且是连续的。所以一个区间的最大高度取值,只有k+1种,我们通过set或者map预处理出来每一段前k+1大的土地以及排名,转移时只用到这些。这样时间复杂度就是O(nk2)
最后合并统计答案,枚举最大值的位置,强制左侧都小于它,右侧小于等于它,避免了重复与非法。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; 
const int Z = 25010; const int M = 30; const int mod = 1e9 + 7; typedef long long ll;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

ll n, m, ans;
int h[Z], pre[Z][M], suf[Z][M];
int f[Z][M][M][2], g[Z][M][M][2];
struct cmp { bool operator ()(int A, int B) { return A > B; } };
set <int, cmp> fr, nx;
void DP(int i, int p, int dp[Z][M][M][2], int tot[Z][M])
{
    int t, tmp, o = 1;
    for (re j = 1; j <= tot[i][0]; j++) if (tot[i][j] == h[i]) t = j;
    for (re j = 1; j <= tot[i + p][0]; j++) if(tot[i + p][j] == h[i]) o = 0;
    for (re j = 1; j <= tot[i + p][0]; j++)
    {
        tmp = tot[i + p][j];
        for (re k = 0; k <= m; k++)
        {
            if (h[i] > tmp)
            {
                (dp[i][t][k][0] += dp[i + p][j][k][0]) %= mod;
                (dp[i][t][k][1] += dp[i + p][j][k][1]) %= mod;
                (dp[i][j + o][k + 1][(tmp + 0) % 2] += dp[i + p][j][k][0]) %= mod;
                (dp[i][j + o][k + 1][(tmp + 1) % 2] += dp[i + p][j][k][1]) %= mod;
            }
            else
            {
                (dp[i][j][k][(tmp - h[i] + 0) % 2] += dp[i + p][j][k][0]) %= mod;
                (dp[i][j][k][(tmp - h[i] + 1) % 2] += dp[i + p][j][k][1]) %= mod;
                (dp[i][j][k + 1][(tmp + 0) % 2] += dp[i + p][j][k][0]) %= mod;
                (dp[i][j][k + 1][(tmp + 1) % 2] += dp[i + p][j][k][1]) %= mod;
            }
        }
    }
}
void init(set <int, cmp> &s, int i, int tot[Z][M])
{
    s.insert(h[i]);
    for (auto it = s.begin(); it != s.end(); ++it)
    {
        tot[i][++tot[i][0]] = *it;
        if (tot[i][0] > m ) break;
    }
}

sandom main()
{
    fre(rain, rain);
    n = read(), m = read();
    for (re i = 1; i <= n; i++) h[i] = read();
    fr.insert(0), nx.insert(0);
    pre[0][++pre[0][0]] = 0, suf[n + 1][++suf[n + 1][0]] = 0;
    for (re i = 1; i <= n; i++) init(fr, i, pre);
    for (re i = n; i >= 1; i--) init(nx, i, suf);
    f[0][1][0][0] = g[n + 1][1][0][0] = 1;
    for (re i = 1; i <= n; i++) DP(i, -1, f, pre);
    for (re i = n; i >= 1; i--) DP(i, +1, g, suf);
    for (re i = 1; i <= n; i++)
        for (re k = 0; k <= m; k++)
        {
            ll tmp1 = 0, tmp2 = 0;
            for (re j = 1; j <= pre[i - 1][0]; j++) if (pre[i - 1][j] < h[i]) (tmp1 += f[i - 1][j][k][0]) %= mod;
            for (re j = 1; j <= suf[i + 1][0]; j++) if (suf[i + 1][j] <= h[i]) (tmp2 += g[i + 1][j][m - k][0]) %= mod;
            ans += tmp1 * tmp2 % mod;
            tmp1 = tmp2 = 0;
            for (re j = 1; j <= pre[i - 1][0]; j++) if (pre[i - 1][j] < h[i]) (tmp1 += f[i - 1][j][k][1]) %= mod;
            for (re j = 1; j <= suf[i + 1][0]; j++) if (suf[i + 1][j] <= h[i]) (tmp2 += g[i + 1][j][m - k][1]) %= mod;
            ans += tmp1 * tmp2 % mod;
            ans %= mod;
        }
    cout << ans << endl;
    return 0;
}

T4.置换

我只会O(n!)暴力

posted @   sandom  阅读(117)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示