首页 > 其他分享 >2024.2.26

2024.2.26

时间:2024-02-26 22:15:02浏览次数:27  
标签:std pre 2024.2 int 26 limit dp SIZE

前言

还有 \(4\) 天就结束了呜呜呜,我还不想走,我还没打过国赛呜呜呜。

博弈

以为是个吊题,结果真是签到题啊QAQ。

首先我们要读明白题,我们一个点可以放多个棋子,所以可以得出一个结论:每个点是互不影响的。

所以我们可以每个点分开来算。

正如题解所说:“因为在自己所属点上的棋子是完全由自己操控的,不会莫名其妙地被对方抢走”,所以最优策略就是让自己可以走的步数减去对面可以走的步数。

然后我们就可以很轻易的得到一个 \(dp_i\),表示若 \(i\) 有棋子,\(A\) 可以走的比 \(B\) 多的步数。若 \(i\) 为白色,那么 \(dp_i\) 为最大值;若 \(i\) 为黑色,那么 \(dp_i\) 为最小值。

注意要把每个 \(dp_i\) 赋为 \(0\),因为如果这个点实在不符合当前出手的人的利益,那么就不管这个点了。同时也可以将棋子全在黑点的情况抛去。

最后的统计答案的时候背包,找大于 \(0\) 的方案就行。

博客园

#include <bits/stdc++.h>

const int SIZE = 310;
const int mod = 998244353;

int N, M;
int cnt = 1, head[SIZE], to[SIZE * SIZE], next[SIZE * SIZE];
int degree[SIZE], dp[SIZE], f[2 * SIZE * SIZE];

char str[SIZE];
bool color[SIZE];

void AddEdge(int u, int v) {
    ++ cnt;
    next[cnt] = head[u];
    head[u] = cnt;
    to[cnt] = v;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);

    std::cin >> N >> M;
    std::cin >> (str + 1);
    for (int i = 1; i <= N; ++ i)
        color[i] = (str[i] == 'W' ? 0 : 1);
    for (int i = 1, x, y; i <= M; ++ i) {
        std::cin >> x >> y;
        if (x < y)
            std::swap(x, y);
        degree[y] ++;
        AddEdge(x, y);
    }
    for (int now = N; now >= 1; -- now) {
        for (int i = head[now]; i; i = next[i]) {
            if (color[to[i]] == 0) 
                dp[to[i]] = std::max(dp[to[i]], dp[now] + 1);
            if (color[to[i]] == 1) 
                dp[to[i]] = std::min(dp[to[i]], dp[now] - 1);
        }
    }

    int begin = 301 * 301;
    f[begin] = 1;
    for (int i = 1; i <= N; ++ i) {
        if (dp[i] < 0) {
            for (int j = begin - 300 * 300; j <= begin + 300 * 300; ++ j) {
                if (f[j] == 0)
                    continue;
                f[j + dp[i]] = (f[j + dp[i]] + f[j]) % mod;
            }
        }
        else {
            for (int j = begin + 300 * 300; j >= begin - 300 * 300; -- j) {
                if (f[j] == 0)
                    continue;
                f[j + dp[i]] = (f[j + dp[i]] + f[j]) % mod;
            }
        }
    }
    int answer = 0;
    for (int i = begin + 1; i <= begin + 300 * 300; ++ i)
        answer = (answer + f[i]) % mod;
    std::cout << answer << '\n';
    return 0;
}

排列

呃呃呃,这道题属实有些逆天了。

依靠人类智慧,我们发现对于题目的限制,我们可以将点对 \((a,b)\) 连边。

然后我们称一个区间是好(或区间是有传递性的)的,当且仅当在一个区间内,若 \(a\) 能到达 \(c\),则 \(a\) 与 \(c\) 一定有连边。

这样我们对于一个题目要求的合法排列一定存在:排列任意的前缀和任意的后缀都是好的。

然鹅上面和正解关系不大

根据爆搜,我们可以知道对于一个 \(DAG\)(有向无环图),自身和补图(若自身是前缀,则补图是除了自身以外的后缀)同时是好的的个数是 \(n!\) 的。可以爆搜!

那我们怎么维护呐?

继续人类智慧QAQ:

我们先拿出来的个 \(1\) 到 \(n\) 的严格单调递增的排列。对于相邻的 \(a,b\) 并且 \(a < b\),我们若交换 \(a,b\) 则表示 \(a, b\) 连边。最后我们求变成排列 \(n\) 到 \(1\) 的方案数就行。

对于题目里要求的情况,我们每次交换 \(c,d\) 的时候判断一下 \(a,b\) 有没有交换过就行。

洛谷
// ubsan: undefined
// accoders
#include <bits/stdc++.h>

typedef long long ll;

const int mod = 998244353;

int N, M;
std::vector<std::pair<int, int>> limit[11][11];
std::vector<int> edge[4000000];
int array[11], pos[11], small[11];
ll fac[11], dp[4000000];

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    freopen("perm.in", "r", stdin);
    freopen("perm.out", "w", stdout);

    std::cin >> N >> M;
    for (int i = 1, a, b, c, d; i <= M; ++i) {
        std::cin >> a >> b >> c >> d;
        limit[c][d].emplace_back(a, b);
    }

    fac[0] = 1;
    for (int i = 1; i <= N; ++i) {
        fac[i] = fac[i - 1] * i;
        array[i] = i;
    }

    int id = 0;
    do {
        ++id;
        for (int i = 1; i <= N; ++i) {
            pos[array[i]] = i;
            small[i] = 0;
            for (int j = i + 1; j <= N; ++j)
                if (array[i] > array[j])
                    ++small[i];
        }

        for (int i = 2; i <= N; ++i) {
            if (array[i - 1] > array[i])
                continue;

            int c = array[i - 1];
            int d = array[i];
            bool flag = true;

            for (const std::pair<int, int> &iter : limit[c][d]) {
                if (pos[iter.first] < pos[iter.second]) {
                    flag = false;
                    break;
                }
            }

            if (!flag)
                continue;

            int to = id;

            to -= fac[N - i] * small[i];
            to -= fac[N - i + 1] * small[i - 1];
            to += fac[N - i] * small[i - 1];
            to += fac[N - i + 1] * (small[i] + 1);

            // std::cout << id << ' ' << to << ' ' << i - 1 << ' ' << i << '\n';
            // for (int j = 1; j <= N; ++ j)
            //     std::cout << small[j] << ' ';
            // std::cout << '\n';

            edge[id].emplace_back(to);
        }

    } while (std::next_permutation(array + 1, array + 1 + N));

    dp[1] = 1;
    for (int i = 1; i <= id; ++i)
        for (const int &to : edge[i]) dp[to] = (dp[to] + dp[i]) % mod;
    std::cout << dp[id] << '\n';

    return 0;
}

子段和

先想一想暴力。

我们发现:若我们现在求删完了 \(k\) 的值不能从 \(k-1\) 转移过来。那么我们只能每次求一遍 \(k\) 能到的最小值。

然后我就不会了。

但是题解告诉我:我们二分答案最后的最小值就行了。

我的内心:哈哈_,你真幽默。

具体说说做法。

暴力写法1

我们每次二分答案可以到达的最小值。

在二分的 \(check\) 里面,我们维护一个前缀和的最小值,然后用现在的前缀和减去前缀和最小值,看一下是不是大于二分的值,若大于则让现在这个点的值减去大于的部分就行了。

注意我们对于每个点减去的值,在这个点之后的前缀和里面都要减去。

最后判断减去的值打不大于我们的 \(k\) 就行了。

但是我的写法好像没办法优化。

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

typedef long long ll;

const int SIZE = 1100;
const int mod = 998244353;

int N, K;
ll a[SIZE], pre[SIZE], answer;

bool check(int max, int rest) {
    max -= 2e7;
    ll min = 0, differ = 0;
    for (int i = 1; i <= N; ++ i) {
        ll now = pre[i] - differ;
        if (now - min > max)
            differ += (now - min - max);
        now = pre[i] - differ;
        min = std::min(min, now);
    }
    return (differ <= rest);
}

ll Answer(int k) {
    int l = 1, r = 4e7 + 1, result = 0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid, k)) {
            result = mid;
            r = mid - 1;
        }
        else {
            l = mid + 1;
        }
    }
    return result - 2e7;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    std::cin >> N;
    for (int i = 1; i <= N; ++ i) {
        std::cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }
    std::cin >> K;
    for (int i = 1; i <= K; ++ i) {
        answer += Answer(i);
        answer %= mod;
    }
    std::cout << answer << '\n';
    return 0;
}

暴力写法2

同样是二分,只不过改一下 \(check\)。

我们先设 \(limit\) 表示我们前缀和可以到的最大值。

然后若当前位置的前缀和大于 \(limit\),那么我们就减去大于的部分就行。

但是这里把减法变成加法了,我们将此时的 \(limit\) 赋值成 \(pre_i\)(表示 \(1\) 到 \(i\) 的前缀和) 就行。

若我们之后一直保持现在的 \(limit\),那么意味着后半部分一直受修改之前的影响。

若 \(limit\) 被更新成 \(pre_i+max\)(\(max\) 表示二分的区间最大和为 \(max\)) 了,那么就不受修改之前的影响了。

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

typedef long long ll;

const int SIZE = 1100;
const int mod = 998244353;

int N, K;
ll a[SIZE], pre[SIZE], answer;

bool check(int max, int rest) {
    max -= 2e7;
    ll limit = max, has = 0;
    for (int i = 1; i <= N; ++ i) {
        if (pre[i] > limit) {
            has += pre[i] - limit;
            limit = pre[i];
        }
        limit = std::min(limit, pre[i] + max);
    }
    return (has <= rest);
}

ll Answer(int k) {
    int l = 1, r = 4e7 + 1, result = 0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid, k)) {
            result = mid;
            r = mid - 1;
        }
        else {
            l = mid + 1;
        }
    }
    return result - 2e7;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    std::cin >> N;
    for (int i = 1; i <= N; ++ i) {
        std::cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }
    std::cin >> K;
    for (int i = 1; i <= K; ++ i) {
        answer += Answer(i);
        answer %= mod;
    }
    std::cout << answer << '\n';
    return 0;
}

标签:std,pre,2024.2,int,26,limit,dp,SIZE
From: https://www.cnblogs.com/jueqingfeng/p/18034416

相关文章

  • 近期总结 2024.2.26
    dp专场*2。CF1608FMEXCounting题意:给出\(n,m,b_{1...n}\),求出有多少个长度为\(n\)的序列\(a\)满足\(\foralli\in[1,n],\space0\lea_i\len\)且\(|\operatorname{mex}\{a_1,a_2,...,a_i\}-b_i|\lem\)。\(1\len\le2000,\space1\lek\le50\)很简单的......
  • 2024.2.25模拟赛T3题解
    题目推出dp柿子之后,枚举\(i\)的时候用线段树维护\(1-i\)的\(mex\)段,对于每一段,分别使用线段树套李超树维护,对于每个\(mex\)再次使用线段树套李超树维护即可code#include<bits/stdc++.h>usingnamespacestd;#defineN600005#defineintlonglongintn,m;consti......
  • 2024.2.25模拟赛T1题解
    题目考虑DP式子之后,可以通过堆维护函数,求出对应值code#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#defineN200005intzu,n,d,tg,num;inta[N];priority_queue<int>q;signedmain(){ scanf("%lld",&zu); while(zu--){ scanf(&qu......
  • 2024.2.25模拟赛T2题解
    题目枚举根之后,考虑每次连边的贡献,通过贡献算出每个点的权值,每次找出权值最大的点,又要保证父亲在儿子之前,所以将父亲和儿子合并,权值也合并一下即可code#include<bits/stdc++.h>usingnamespacestd;#defineN2005intans,n,k;intsz[N],deg[N],du[N],fu[N],f[N],h[N];str......
  • 2024.2.26模拟赛T2题解
    题目对询问扫描线,建出\(PAM\)的失配树之后,每次查询相当于,把\(r\)对应节点到根路径染色之后,有多少个节点的值大于\(l\),可以树剖+ODT实现code#pragmaGCCoptimize("Ofast","inline","-ffast-math")#pragmaGCCtarget("avx,sse2,sse3,sse4,mmx")#include<bits/s......
  • 闲话2.26
    哎我2.25闲话呢你画我猜真好玩......
  • 2024.2.26 LGJ Round
    A给出一个\(n\)个顶点的有向图,求有多少个长度小于\(k\)的环(环可以经过重复的结点)。两个环不同当且仅当顶点序列不同。\(n\le35,k\le1e6\)。矩阵乘法模板题。枚举起点,求出走\(\lek\)步到达自己的方案数。只需要记录\(f_i\)表示以\(i\)结尾的路径个数,以及\(g_i\)......
  • 2024.2.26闲话——错误的时间复杂度
    推歌:猛独が襲う——一二三想了一个非常奇怪的逻辑。我们知道斐波那契数列是需要递推的。我们由前两个数推到第\(3\)个数的时间复杂度是\(O(1)\)。推第\(4\)个数是\(O(1)\)的基础上加\(1\)还是\(O(1)\)。然后我们以此类推下去,递推求斐波那契数列任意一项都是\(O(1)......
  • 2024.2.26模拟赛T1题解
    题目先建出圆方树,题目转换为数长度为\(2*L-1\)的路径数,长链剖分code#include<bits/stdc++.h>usingnamespacestd;#defineN2000005#definelllonglongintn,m,top,tot,cnt,L,k;intdfn[N],low[N],zhan[N],h[N];structAB{ inta,b,n;}d[N*4];voidcun(intx,int......
  • 91st 2024/2/26 省选联赛训练-1st
    迟来的新年快乐!这次的机会挺难得的,初三了,好说歹说从学校里跑出来训练了,就一定要珍惜时间进入正题今天的比赛难度高,但也没有省选那么难属于是思路比较难想那类T1题目有些疑惑,但总体表达还可,应该是太久没接触这种表达较专业的题目而一时难以适应看题需要认真且专心调代码时状......