NOIP 模拟赛总结集合

One to one

题意描述

给定一个数组 \(X_i\) ,F(X) 值的定义为,在一个第 \(i\) 条边为 \((i, X_i)\) 的无向连通图的连通块个数。
现有一个序列 \(a_i\) ,一些位置为 \(-1\) , 则这个位置可以为 \(1\) ~ \(n\) 中的任意一个数,问所有可能的 \(a\)\(F(a)\) 之和对 \(998244353\) 取模的值。
\(n \le 2000\)

解题思路

对于给出的边,可以构成若干个 树和环(奇环树)
如果是一棵奇环树,那么这个连通块内所有的边都已经确定,可以直接算贡献。记奇环树个数为 \(a\),树的数量(-1的个数)为 \(b\), 则贡献为 \(a \times n^b\)
如果是一棵树,则仅有一条边为确定(根的边)。记 \(f_{i,j}\) 表示前 \(i\) 棵树,选 \(j\) 棵组成一个连通块的方案数,有转移方程如下: \(f_{i,j} = f_{i-1,j} + f{i-1, j-1} \times size_i\)。若共有 \(k\) 棵树,答案为 \(\sum\limits_{i=1}^k f_{k,i} \times n^{k-i}(i-1)!\)

Code

#define long long long
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int MAXN=2e3+5;
const int p = 998244353;
struct node{
    int from, to;
    int next;
}a[MAXN<<1];
int n, head[MAXN], cnt; long ans;
int _siz[MAXN], siz[MAXN], x[MAXN];
long f[MAXN][MAXN], fact[MAXN], nxp[MAXN];
bool flag, vis[MAXN]; int cnt1, cnt2;
auto prework() -> void;
auto dfs(int x) -> void;
auto add_edge(int from, int to) -> void;
auto main() -> signed
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i += 1)
    {
        scanf("%d", &x[i]);
        if(x[i] == -1) continue;
        add_edge(i, x[i]);
        add_edge(x[i], i);
    }
    for(int i = 1; i <= n; i += 1)
    {
        if(vis[i]) continue;
        flag = false;dfs(i);
        if(!flag) cnt1 += 1;
        else siz[++cnt2] = _siz[i];
    }
    prework();
    for(int i = 1; i <= cnt2; i += 1)
        for(int j = 1; j <= i; j += 1)
            f[i][j] = (f[i-1][j]+f[i-1][j-1]*siz[i])%p;
    for(int i = 1; i <= cnt2; i += 1)
        (ans += f[cnt2][i]*fact[i-1]%p*nxp[cnt2-i]%p) %= p;
    (ans += cnt1*nxp[cnt2]%p) %= p;
    printf("%lld", ans);
    return 0;
}
auto add_edge(int from, int to) -> void
{
    cnt += 1;
    a[cnt].from = from;
    a[cnt].to = to;
    a[cnt].next = head[from];
    head[from] = cnt;
}
auto prework() -> void
{
    fact[0] = 1, nxp[0] = 1;
    for(int i = 1; i <= cnt2; i += 1)
    {
        fact[i] = fact[i-1]*i%p;
        nxp[i] = nxp[i-1]*n%p;
    }
    for(int i = 0; i <= cnt2; i += 1) 
        f[i][0] = 1;
}
auto dfs(int x) -> void
{
    if(::x[x] == -1) flag = true;
    _siz[x] = 1; vis[x] = true;
    for(int i = head[x]; i; i = a[i].next)
    {
        int y = a[i].to;
        if(!vis[y]) dfs(y),_siz[x] += _siz[y];
    }
}

D&D

题意描述

给定不可重集合 \(A\),定义其伴随子集为:
\(f(A) = \{a \in a | \forall b \in A-\{a\}, a | b \not= b \}\)

给定一个长度为 \(n\) 的序列 \(a_i\),把这个序列分成连续的若干个子段,要求这些子段的不重集的伴随子集相同。
输出方案数对 \(10^9+7\) 取模的结果。

解题思路

记原序列的伴随子集为 \(F\), 这有几个结论:

  1. \(x \in F\), 则 \(x\) 在子段的伴随子集中也出现。

  2. \(x \not\in F\), 则 \(x\) 在子段的伴随子集中也不会出现。

  3. 划分完子段的伴随子集与原序列的伴随子集相同。

  4. \(B\) 为原序列的子集,且 \(F \subseteq B\), 则 \(F\)\(B\) 的伴随子集。

第一步我们需要快速求出原序列的伴随子集。
我们对 \(a\) 从大到小排序,依次 dfs 标记处二进制下被 \(a_i\) 包含的数。
如果 \(a_i\) 在 dfs 开始时没有被标记,他就是伴随子集中的一员。

第二步考虑计算方案。
若区间 \([l,r]\) 有合法的伴随子集,那么区间 \([l-1,r]\) 也有。我们就需要求出对于每一个 \(r\), 满足条件的最大左端点 \(L_i\) ,按照 \(f_i = \sum\limits_{i=0}^{L_i-1}f_j\) (前缀和优化)。

Code

#define long long long
#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN = 3e6+10;
const int p = 1e9+7;
int n, a[MAXN], b[MAXN], t[MAXN];
int lsh[MAXN], cnt, L[MAXN], l, k;
bool vis[MAXN]; int s[MAXN];
auto dfs(int x) -> void;
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i += 1)
        scanf("%d", &a[i]), b[i] = a[i];
    sort(b+1, b+n+1,[](int a, int b){return a > b;});
    for(int i = 1; i <= n; i += 1)
        if(!vis[b[i]]) lsh[b[i]] = ++cnt, dfs(b[i]);
    for(int i = 1; i <= n; i += 1)
        a[i] = lsh[a[i]];
    for(int i = 1; i <= n; i += 1)
    {
        if(a[i] != 0)
        {
            t[a[i]] += 1;
            if(t[a[i]] == 1) k += 1;
        }
        if(!l and k < cnt) L[i] = n+2;
        while(k == cnt)
        {
            l += 1;
            if(a[l])
            {
                t[a[l]] -= 1;
                if(!t[a[l]]) k -= 1;
            }
        }
        if(!L[i]) L[i] = l;
    }
    s[0] = 1;
    for(int i = 1; i <= n; i += 1)
        s[i] = (s[i-1]+s[L[i]-1])%p;
    printf("%d", (s[n]-s[n-1]+p)%p);
    return 0;
}
auto dfs(int x) -> void
{
    if(vis[x]) return ; vis[x] = true;
    for(int i = 0; i <= 20; i += 1)
        if(x&(1<<i)) dfs(x-(1<<i));
}

Count

题意描述

现有 \(n\) 个红珠子和 \(An+B\) 个蓝珠子,从左往右放置,要保证在任何时候,红柱子有 \(x\) ,蓝珠子不超过 \(Ax+B\) 个,请你求出摆完所有珠子的方案数。
答案对 \(10^9+7\) 取模。

解题思路

题意可以转化为在一个 \(n \times m\) 的网格图中,从 \((0,0)\) (左下角) 走到 \((n,m)\) (右上角),不穿过直线 \(y = Ax+B\) 的方案数。

若不考虑直线的限制,方案总数为 \(C_{n+m}^{n}\)

考虑不合法的方案数:
记第一次走到直线的位置为 \((p, Ap+B)\),然后穿过直线,走到 \((p, Ap+B+1)\),然后再走到终点的方案数为

\[\begin{aligned} C_{m-Ap-B-1+n-p}^{n-p} &= \frac{(m-Ap-B-1+n-p)!}{(m-Ap-B-1)!(n-p)!} \\ &= \frac{(m-Ap-B-1+n-p)!}{(m-Ap-B)!(n-p-1)!} \times \frac{m-Ap-B}{n-p}\\ &= A \times C_{m-Ap-b-1+n-p}^{n-p-1} \end{aligned} \]

化简后的式子结果等于从 \((p, Ap+b+1)\) 走到 \((n-1,m+1)\) 的方案数。

记从 \((0,0)\) 走到 \((p, Ap+B+1)\) 的合法方案数为 \(W_p\), 不合法的方案总数为 \(\sum\limits_{p=0}^{n-1}W_p \times C_{m-Ap-b-1+n-p}^{n-p-1} = C_{n+m}^{n-1}\) (相当于从原点走到 \((n-1,m+1)\) 的方案数)。

\(ANS = C_{n+m}^{n} - A \times {C_{n+m}^{n-1}}\)

Code

#define long long long
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
using namespace fast_IO;
const int p = 1e9+7;
const int MAXN = 4e7+10;
int T, n, m, A, B, fact[MAXN];
auto prework(int n) -> void;
auto C(int n, int m) -> long;
auto ksm(long a, int b) -> long;
auto main() -> signed
{
    read(T); prework(4e7);
    for(int i = 1; i <= T; i += 1)
    {
        read(n, A, B);
        long ans = 0; m = A * n + B;
        ans = C(n+m,n)-A*C(n+m,n-1);
        print((ans%p+p)%p, '\n');
    }
    return 0;
}
auto prework(int n) -> void
{
    fact[0] = 1;
    for(int i = 1; i <= n; i += 1)
        fact[i] = (long)fact[i-1] * i % p;
}
auto C(int n, int m) -> long
{
    if(n < m) return 0;
    return fact[n] * ksm(fact[m], p-2) % p * ksm(fact[n-m], p-2) % p;
}
auto ksm(long a, int b) -> long
{
    long ans = 1;
    for(; b; (a *= a) %= p, b >>= 1)
        if(b&1) (ans *= a) %= p; return ans;
}

Expand

题目描述

刺豚小T出去买菜,在 \(n \times m\) 的坐标系上,现在在位置 \((x,y)\), 坐标系上有一些菜店和障碍,他需要在所有菜店买菜。他比较喜欢大的体格(膨胀),定义体格为 \(x\) 的他占据的格子数为 \(2x+1 \times 2x+1\)。他最大体格为 \(s\) (膨胀的上限)。在不考虑回去的情况下,找一条路径,在满足路径最短的前提下,走到每个格子的体格和最大。
请输出最短的距离和满足最短距离下最大的体格和。
\(n,m \le 300; \; s \le 10; \; p \le 15\)

解题思路

首先可以预处理出每个格子能做到的最大体格,可以枚举判断,也可以从每个障碍出发进行 bfs。
然后再图上跑多源最短路 (其实就是 \(p+1\) 遍 dijkstra),以距离为第一关键字(取较小值为优),路径体格和为第二关键字(取较大值为优),把 \(p\) 个菜市场和起点之间两两最优路径求出来。
要求每个菜市场都到过,显然重复经过仅去一个去过的不优,\(p\) 的范围很小,状压就好了。

Code

#define long long long
#include<iostream>
#include<cstring>
#include<climits>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
struct node{
    int sumt, dis;
    friend bool operator<(node a, node b){
        if(a.dis != b.dis)  return a.dis < b. dis;
        return a.sumt > b.sumt;
    }
    friend bool operator>(node a, node b){
        if(a.dis != b.dis)  return a.dis > b. dis;
        return a.sumt < b.sumt;
    }
    friend node operator+(node a, node b){
        return {a.sumt+b.sumt, a.dis+b.dis};
    }
}a[17][17], dis[310][310], f[65536][17], ans;
struct cyr{
    int x, y;
    node dis;
};
int n, m, s, x, y, p, mp[310][310];
int px[20], py[20], tig[310][310];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
auto bfs(int sid) -> void;
auto calc(int x, int y) -> int;
auto prework() -> void;
int main()
{
    scanf("%d%d%d", &n, &m, &s);
    for(int i = 1; i <= n; i += 1)
    for(int j = 1; j <= m; j += 1)
        scanf("%d", &mp[i][j]);
    scanf("%d%d%d", &x, &y, &p);
    x += 1, y += 1; px[0] = x, py[0] = y;
    for(int i = 1; i <= p; i += 1) 
        scanf("%d%d", &px[i], &py[i]),
        px[i] += 1, py[i] += 1;
    prework();
    int N = (1<<p)-1;
    for(int i = 0; i <= N; i += 1)
    for(int j = 0; j <= p; j += 1)
        f[i][j] = {-1, inf};
    for(int i = 1; i <= p; i += 1)
        f[1<<(i-1)][i] = {a[0][i].sumt+tig[px[0]][py[0]], a[0][i].dis};
    for(int i = 1; i <= N; i += 1)
    {
        for(int j = 1; j <= p; j += 1) 
        {
            if(!(i&(1<<(j-1)))) continue;
            for(int k = 1; k <= p; k += 1)
            {
                if(i&(1<<(k-1))) continue;
                f[i|(1<<(k-1))][k] = min(
                    f[i|(1<<(k-1))][k], f[i][j]+a[j][k]);
            }
        }
    }
    ans = {-1, inf};
    for(int i = 1; i <= p; i += 1)
        ans = min(ans, f[N][i]);
    printf("%d %d", ans.dis, ans.sumt);
    return 0;
}
auto prework() -> void
{
    for(int i = 1; i <= n; i += 1)
        for(int j = 1; j <= m; j += 1)
        {
            if(mp[i][j] == 1) continue;
            tig[i][j] = calc(i, j);
        }
        
    for(int i = 0; i <= p; i += 1) bfs(i);
}
auto calc(int x, int y) -> int
{
    for(int r = s; r >= 0; r -= 1)
    {
        bool flag = false;
        if(x-r < 1 or x+r > n or y-r < 1 or y+r > m) continue;
        int lx = x-r, rx = x+r;
        int ly = y-r, ry = y+r;
        for(int i = lx; i <= rx; i += 1)
        for(int j = ly; j <= ry; j += 1)
            if(mp[i][j] == 1) flag = true;
        if(!flag) return r;
    }
    return 0;
}
auto bfs(int sid) -> void
{
    for(int i = 1; i <= n; i += 1)
    for(int j = 1; j <= m; j += 1)
        dis[i][j] = {-1, inf};
    dis[px[sid]][py[sid]] = {0, 0};
    queue<cyr> q; q.push({px[sid], py[sid], dis[px[sid]][py[sid]]});
    cyr x; int nx, ny;
    while(!q.empty())
    {
        x = q.front(); q.pop();
        if(x.dis > dis[x.x][x.y]) continue;
        for(int i = 0; i < 4; i += 1)
        {
            nx = x.x+dx[i];
            ny = x.y+dy[i];
            if(nx < 1 or nx > n or ny < 1 or ny > m) continue;
            if(mp[nx][ny] == 1) continue;
            if(dis[nx][ny] > x.dis+(node){tig[nx][ny], 1})
            {
                dis[nx][ny] = x.dis + (node){tig[nx][ny], 1};
                q.push((cyr){nx, ny, dis[nx][ny]});
            }
        }
    }
    for(int i = 0; i <= p; i += 1)
        a[sid][i] = dis[px[i]][py[i]];
}

Birthday

题目描述

\(n\) 个小朋友,每个小朋友的礼物的初始价格为 \(a_i\),价格的上限为 \(v\)
有两种操作共 \(m\) 个:

  1. 操作一:在区间 \([l,r]\),选择两个不相交的子集(但不一定全都选上),使两个集合对应的小朋友的礼物花费和相等(第 \(i\) 个小朋友礼物的花费为 \(a_i+1\))。如果存在这么一个集合就输出 Yes,否则输出 No
  2. 操作二:使区间 \([l,r]\) 的每个小朋友礼物价值变为 \(a_x^3 \% v\)

\(n,m \le 10^5; \; v \le 1000; \; a[i] < v\)

解题思路

考虑操作一:
当区间足够大的话是一定有合法方案的。
若区间长度为 \(k\),那么子集就有 \(2^{k}\) 种选择方法,一种选法价格和的值域在 \([1,v*k]\) 之间。考虑抽屉原理, \(2^{k} > v*k\) 时,就至少有一种值的方案最少为两种(就算相交,减去相交部分也相等),这时候就一定有合法方案。解出 \(k \ge 14\)
\(k \le 13\) 时,我们只能暴力统计,但 \(3^k\) 的复杂度难以接受,我们可以将区间砍成两半(折半搜索),将左半部分的可能取值存起来,再在搜右半部分是判断有没有取值相等的选法。当然,搜到了和为 \(0\) 的选法也可以直接 return。
单词询问的时间复杂度最差为 \(O(3^7 + 3^6)\)

考虑操作二:
将乘法变为加法,即每个数进行的操作二的次数,值域为 \([0,1000)\),可以倍增预处理求值,用线段树维护区间加,每次将用到的数的标记下传,毕竟一次最多用 \(13\) 个数。

Code

#define Meteorshower_Y Aniciry
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define ls(i) (i<<1)
#define rs(i) (i<<1|1)
using namespace std;
const int MAXN = 1e6+10;
struct node
{
    int l, r;
    int lz;
}tr[MAXN<<2];
int n, m, v, a[MAXN];
int f[2000][24], flag;
int mp[20010];
auto prework() -> void;
auto change(int x, int k) -> int;
auto build(int l, int r, int i) -> void;
auto add(int l, int r, int i) -> void;
auto query(int x, int i) -> void;
auto pushdown(int i) -> void;
auto check(int l ,int r) -> bool;
auto dfs1(int pos, int n, int now, int tot) -> void;
auto dfs2(int pos, int n, int now, int tot) -> void;
int main()
{
    scanf("%d%d%d", &n, &m, &v);
    for(int i = 1; i <= n; i += 1) scanf("%d", &a[i]);
    prework(); build(1, n, 1); int opt, l, r;
    for(int i = 1; i <= m; i += 1)
    {
        scanf("%d%d%d", &opt, &l, &r);
        if(opt == 1)
        {
            if(r-l >= 13) printf("Yes\n");
            else
            {
                for(int j = l; j <= r; j += 1) query(j, 1);
                if(check(l, r)) printf("Yes\n");
                else printf("No\n");
            }
        }
        else add(l, r, 1);
    }
    return 0;
}
auto prework() -> void
{
    for(int i = 0; i < v; i += 1) f[i][0] = i*i%v*i%v;
    for(int k = 1; k <= 19; k += 1)
        for(int i = 0; i < v; i += 1)
            f[i][k] = f[f[i][k-1]][k-1];
}
auto build(int l, int r, int i) -> void
{
    tr[i].l = l;
    tr[i].r = r;
    if(l == r) return ;
    int mid = (l+r)>>1;
    build(l, mid, ls(i));
    build(mid+1, r, rs(i));
}
auto add(int l, int r, int i) -> void
{
    if(tr[i].l > r or tr[i].r < l) return ;
    if(tr[i].l >= l and tr[i].r <= r) return (void)(tr[i].lz += 1);
    if(tr[i].l == tr[i].r) return ;
    pushdown(i);
    add(l, r, ls(i));
    add(l, r, rs(i));
}
auto query(int x, int i) -> void
{
    if(tr[i].l > x or tr[i].r < x) return ;
    if(tr[i].l == x and tr[i].r == x)
    {
        a[x] = change(a[x], tr[i].lz);
        return (void)(tr[i].lz = 0); 
    }
    pushdown(i);
    query(x, ls(i));
    query(x, rs(i));
}
auto pushdown(int i) -> void
{
    if(!tr[i].lz) return ;
    tr[ls(i)].lz += tr[i].lz;
    tr[rs(i)].lz += tr[i].lz;
    tr[i].lz = 0;
}
auto change(int x, int k) -> int
{
    int y = 19;
    while(k and y >= 0)
    {
        if(k >= (1<<y))
        {   
            x = f[x][y];
            k -= (1<<y);
        }   y -= 1;
    }
    return x;
}
auto check(int l, int r) -> bool
{
    memset(mp, 0, sizeof(mp));
    flag = false;
    dfs1(l, (l+r)>>1, 0, 0);
    if(flag) return true;
    dfs2(((l+r)>>1)+1, r, 0, 0);
    return flag;
}
auto dfs1(int pos, int n, int now, int tot) -> void
{
    if(flag) return ;
    if(pos == n+1)
    {
        if(tot and !now) return (void)(flag = true);
        if(now) mp[abs(now)] = 1;
        return ;
    }
    dfs1(pos+1, n, now+a[pos]+1, tot+1);
    dfs1(pos+1, n, now-a[pos]-1, tot+1);
    dfs1(pos+1, n, now, tot);
}
auto dfs2(int pos, int n, int now, int tot) -> void
{
    if(flag) return ;
    if(pos == n+1)
    {
        if(tot and !now) return (void)(flag = true);
        if(now and mp[abs(now)]) return (void)(flag = true);
        return ;
    }
    dfs2(pos+1, n, now+a[pos]+1, tot+1);
    dfs2(pos+1, n, now-a[pos]-1, tot+1);
    dfs2(pos+1, n, now, tot);
}

Trees

题目描述

在长度为 \(n\) 的序列里选出 \(k\) 个数,这些数的贡献为最大的数的数值。为可选择的所有方案的权值和。

解题思路

不方便去快速求出子序列的最大值,我们可以枚举这个最大值,然后乘上选剩下 \(k-1\) 个数的方案数就能求出一个最大值的贡献了。把所有数作为最大值的贡献加起来就是答案。至于选数的的方案数,组合数学求下就好。

#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
#define long long long
const int MAXN = 100010;
const int p = 1e9+7;
int n, k; long ans;
long a[MAXN], fact[MAXN], finv[MAXN];
auto prework(int n) -> void;
auto C(int n, int m) -> long;
auto ksm(long a, int b) -> long; 
int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i += 1)
        scanf("%lld", &a[i]);
    sort(a+1, a+n+1);
    prework(n);
    for(int i = k; i <= n; i += 1)
        (ans += a[i]*C(i-1, k-1)%p) %= p;
    printf("%lld", ans);
    return 0;
}
auto prework(int n) -> void
{
    fact[0] = finv[0] = 1;
    for(int i = 1; i <= n; i += 1) fact[i] = fact[i-1] * i % p;
    for(int i = 1; i <= n; i += 1) finv[i] = ksm(fact[i], p-2);
}
auto C(int n, int m) -> long
{
    if(n < m) return 0;
    return fact[n] * finv[m]%p * finv[n-m]%p;
}
auto ksm(long a, int b) -> long
{
    long ans = 1;
    for(; b; (a *= a) % p, b >>= 1)
        if(b&1) (ans *= a) %= p; return ans;
}

bridge

题目描述

在一个一位数轴上的一个点开始走,走 \(n\) 步,可以往左也可以往右,最后回到这个点,每两次经过这个点的时间间隔不能超过 \(m\)。询问可以行走的路线方案数。

解题思路

考虑 DP 转移求方案数。
首先我们设计一个比较简单的状态,用 \(f_i\) 表示走 \(i\) 步回到起点且满足题目时间限制的方案数。因为左右两个方向对称,所以我们算出来一种就行,另一边乘 \(2\) 即可。
接下来我们要算的就是走 \(x\) (\(x\) 为偶数) 步的之后回到起点的方案数,因为保证不重复计算方案数,所以不能让他中途经过起点。为了满足这个性质,我们可以让他先迈出一步,在这个点及外边反复横跳,最后留一步回到起点。
我们记 \(d_x\) 为走 \(x\) 步再回到起点的方案数,\(f_i\) 的转移方程就是 \(f_i = \sum\limits_{j = 2}^{m} f_{i-j} \ast 2 \cdot d_{j-2}\)
常系数转移方程的优化交给矩阵就好,由 \(O(nm)\) 的时间复杂度降为\(O(m^3 \log n)\), 可以接受。

#include<iostream>
#include<cstdio>
using namespace std;
#define long long long
const long MAXN = 110 ;
const long p = 100000007 ;

int m, n;
long f[MAXN][MAXN];
long ans[MAXN][MAXN];
long d[MAXN][MAXN] ;

struct node
{
    long v[MAXN][MAXN];
    node(){
        for(int i = 1; i <= m; i += 1)
        for(int j = 1; j <= m; j += 1)
            v[i][j] = 0;
    }
    friend node operator*(node a, node b){
        node c;
        for(int i = 1; i <= m; i += 1)
        for(int j = 1; j <= m; j += 1)
        for(int k = 1; k <= m; k += 1)
        (c.v[i][k] += a.v[i][j]*b.v[j][k]%p) %= p;
        return c;
    }
}A, B;

auto ksm(node a, int b, node &ans) -> void;

int main()
{
	scanf("%d%d", &n, &m) ;
	int t = m >> 1 ;
	d[0][0] = 1;
    for(int i = 1; i <= m; i += 1)
    for(int j = 0; j <= i; j += 1)
    {
        if(j != 0) (d[i][j] += d[i-1][j-1]) %= p;
        if(j != i) (d[i][j] += d[i-1][j+1]) %= p;
    }
    for(int i = 1; i < m; i += 1) B.v[i+1][i] = 1;
    for(int i = 1; i < m; i += 1) B.v[i][m] = 2*d[m-i-1][0]%p;
	A.v[1][m] = 1;
    ksm(B, n, A);
    printf("%lld", A.v[1][m]);
	return 0;
}
auto ksm(node a, int b, node &ans) -> void
{
    for(; b; a = a*a, b >>= 1)
        if(b&1) ans = ans*a;
}

flowers

题目描述

地上一开始有 \(A\) 片樱花,接下来每秒掉落 \(B\) 片,从第一秒开始,过 \(n\) 秒,地上每秒的樱花数的二进制下 \(1\) 的数量总和。

解题思路

这是一道需要找规律的题,每一位上的 01 都有循环节,第 \(i\) 位的循环节为 \(2_{i+1}\)。而且总所周知的一点是,在一个循环节里,一位上的 01 数量对半分。
所以对于循环的部分,直接算就好,反正是 \(O(1)\) 的。
那么对于不是整个循环节的剩下部分,要没事低位剩下的少,高位进位不频繁,都允许我们去暴力算。对于每一位,每次计算连续的 01 的个数会比较方便(只要算出还有多少才进位就行)。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define long long long
int T, n; long A, B, m, ans;
auto work() -> void;
int main()
{
    scanf("%d", &T); 
    while(T--) work();
    return 0;
}
auto calc(long x, long t) -> void
{
    static long now, pos;
    for(long i = 1; i <= x; i += 1)
    {
        now = (B + A * i)%t;
        if(now >= (t>>1))
        {
            pos = min(x, i + (t-1 - now)/A);
            ans += pos-i+1; i = pos;
        }
        else i = min(x, i + ((t>>1)-1 - now)/A);
    }
}
auto work() -> void
{
    ans = 0;
    scanf("%lld%lld%lld", &A, &B, &n);
    while((!(A&1)) and A) 
        ans += n*(B&1), A >>= 1, B >>= 1;
    m = B + A * n;
    for(long i = 2; i < (m<<1); i <<= 1)
        ans += (n/i)*(i/2), calc(n%i,i);
    printf("%lld\n", ans);
}

robo

题目描述

小文给这个小机器人布置了 \(n*m\) 的场地(场地外用障碍围住),在场地上设置了障碍、水池和靶子。其中,障碍是不可以通过并且无法用水晶弹打掉的,靶子无法通过、但是可以被水晶弹打掉,水池也无法通过、但是水晶弹可以通过水池。小文检查了一下小机器人,发现它的指令系统很简单。该系统中一共有 \(6\) 种指令,指令的使用说明如下:

“FT x” :该指令表示炮台的 \(90°\) 转动,其中 \(x∈[0,1],x∈Z\),并且 \(0、1\) 分别表示逆时针和顺时针。

“FF i” :该指令表示填弹,填弹后弹仓剩余弹量减一,弹夹剩余弹量加一,其中 \(i∈[0,1]\)\(i∈Z\)\(i\)\(1\) 表示所填水晶弹为大弹丸,为 \(0\) 表示所填水晶弹为小弹丸。

“FE” :该指令表示发射水晶弹,水晶弹的发射方向同炮台的朝向,发射的水晶弹为最后一个填入弹夹的水晶弹,指令执行后弹夹容量减一。

“WT x” :该指令表示机器人的 \(90°\) 转动, 其中 \(x∈[0,1],x∈Z\) , 并且 \(0, 1\) 分别表示逆时针和顺时针,注意机器人转动时炮台不会转动。

“WG y” :该指令表示机器人前进 \(y\) 步,其中 \(y∈[0,max(m,n)),y∈Z\)

“END” :该指令将返回 “Complete” 并停机,不同于编译器先编译后运行,END(及其他将造成停机的情况)后的指令均被无视。

现在小文将要开始测试,但是为了避免自己的指令集让小机器人出现错误,小文给了你场地、小机器人的初始状态和指令集并拜托你帮他计算出小机器人的返回内容、停下的位置、打掉的靶子数量以及小机器人的状态。

注意:

(一)弹夹无弹的情况下将跳过 FE 指令,弹仓无相应弹丸的情况下将跳过 FF 指令;

(二)大水晶弹一发打掉靶子,小水晶弹需两发打掉靶子,靶子打掉后变成空地可通过;

(三)小机器人将在以下情况下返回 “ERROR” 并停机:

(1)在弹夹已满的情况下继续填弹;

(2)撞上障碍(包括未被打掉的靶子)或者撞进水池;

(3)指令后的参数不满足要求(例:“FE 10”);

(4)无 “END” 指令;

做题思路

此代模拟一道,细节还是不少。

  1. 每次要把所有指令读完。而不是到了 ENDERROR 就直接 break 开始下个数据。
  2. 不合法的指令参数只会出现在有参数的指令里,所以并不需要全部判断,可以减少一部分码量。
  3. 注意不合法的参里有小数。
  4. 发生 ERROR 后,输出的数据是发生 ERROR 前的那条指令执行完的数据。
  5. 言有尽而意无穷......
#include<iostream>
#include<climits>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
const int inf = 11451419;
struct node{
    int magezine[35], rest, lightrest, heavyrest;
    int robodir, gundir, x, y, kill, state;
}data;
int T, n, m, k, mp[210][210], Hp[210][210];
int magezine[35], rest, limit, lightrest, heavyrest;
int robodir, gundir, x, y, kill, state;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, -1, 0, 1};
auto init() -> void;
auto work() -> void;
auto record() -> void;
auto FT(int x) -> int;
auto FF(int i) -> int;
auto FE() -> int;
auto WT(int x) -> int;
auto WG(int y) -> int;
auto END() -> int;
int main()
{
    cin >> T; 
    while(T--) work();
    return 0;
}
auto work() -> void
{
    string ch;
    int opt, len, num; init();
    cin >> n >> m;
    for(int i = 1; i <= n; i += 1)
        for(int j = 1; j <= m; j += 1)
            cin >> mp[i][j];
    cin >> x >> y >> limit; x += 1, y += 1;
    cin >> heavyrest >> lightrest >> k;
    for(int i = 1; i <= n; i += 1)
    for(int j = 1; j <= m; j += 1)
    if(mp[i][j] == 1) Hp[i][j] = inf;
    else if(mp[i][j] == 2) Hp[i][j] = 2;
    else if(mp[i][j] == 3) Hp[i][j] = -1;
    else if(mp[i][j] == 0) Hp[i][j] = 0;
    getline(cin, ch);int flag = false;
    for(int i = 1; i <= k; i += 1)
    {
        getline(cin, ch); len = ch.size(); num = 0;
        if(flag or state == -1) continue; record();
        if(ch[0] == 'F' and ch[1] == 'T')
        {    
            int pos = 3;
            if(len < 3)  {state = -1;continue;};
            for( ; pos < len; pos += 1)
            {
                if(ch[pos] <= '9' and ch[pos] >= '0')
                    num = num*10+ch[pos]-'0';
                else break;
            }
            if(len > pos) {state = -1;continue;};
            if(FT(num) == -1) {state = -1;continue;};
        }
        else if(ch[0] == 'F' and ch[1] == 'F')
        { 
            int pos = 3;
            if(len < 3)  {state = -1;continue;};
            for( ; pos < len; pos += 1)
            {
                if(ch[pos] <= '9' and ch[pos] >= '0')
                    num = num*10+ch[pos]-'0';
                else break;
            }
            if(len > pos) {state = -1;continue;};
            if(FF(num) == -1) {state = -1;continue;};
        }
        else if(ch[0] == 'F' and ch[1] == 'E')
        {
            if(FE() == -1) {state = -1;continue;};
        }
        else if(ch[0] == 'W' and ch[1] == 'T')
        {
            int pos = 3;
            if(len < 3)  {state = -1;continue;};
            for( ; pos < len; pos += 1)
            {
                if(ch[pos] <= '9' and ch[pos] >= '0')
                    num = num*10+ch[pos]-'0';
                else break;
            }
            if(len > pos) {state = -1;continue;};
            if(WT(num) == -1) {state = -1;continue;};
        }
        else if(ch[0] == 'W' and ch[1] == 'G')
        {
            int pos = 3;
            if(len < 3)  {state = -1;continue;};
            for( ; pos < len; pos += 1)
            {
                if(ch[pos] <= '9' and ch[pos] >= '0')
                    num = num*10+ch[pos]-'0';
                else break;
            }
            if(len > pos) {state = -1;continue;};
            if(WG(num) == -1) {state = -1;continue;};
        }
        else if(ch[0] == 'E' and ch[1] == 'N' and ch[2] == 'D')
        {
            flag = true; continue;
        }
    }
    if(!flag and state != -1)
    {
        cout << "ERROR\n";
        cout << x-1 << ' ' << y-1 << '\n';
        cout << kill << '\n';
        cout << gundir << ' ' << robodir << ' ' 
            << heavyrest << ' ' << lightrest << '\n';
    }
    else if(state == -1)
    {
        cout << "ERROR\n";
        cout << data.x-1 << ' ' << data.y-1 << '\n';
        cout << data.kill << '\n';
        cout << data.gundir << ' ' << data.robodir << ' ' 
            << data.heavyrest << ' ' << data.lightrest << '\n';
    }
    else
    {
        cout << "Complete\n";
        cout << x-1 << ' ' << y-1 << '\n';
        cout << kill << '\n';
        cout << gundir << ' ' << robodir << ' ' 
            << heavyrest << ' ' << lightrest << '\n';
    }
}
string ch;
auto init() -> void
{
    rest = lightrest = heavyrest = 0;
    robodir = gundir = kill = 0;
    state = 1; 
    memset(Hp, 0, sizeof(Hp));
    memset(magezine, 0, sizeof(magezine));
} 
auto FT(int x) -> int
{
    if(x != 0 and x != 1) return -1;
    if(x == 0) (gundir += 1) %= 4;
    if(x == 1) (gundir += 3) %= 4;
    return 1;
}
auto FF(int i) -> int
{
    if(i != 0 and i != 1) return -1;
    if(rest == limit) return -1;
    if(i == 0)
    {
        if(lightrest == 0) return 1;
        lightrest -= 1;
        magezine[++rest] = 0;
    }
    if(i == 1)
    {
        if(heavyrest == 0) return 1;
        heavyrest -= 1;
        magezine[++rest] = 1;
    }
    return 1;
}
auto FE() -> int
{
    if(rest == 0) return 1;
    int nx = x, ny = y, type = magezine[rest--]+1;
    while(1)
    {
        nx += dx[gundir];
        ny += dy[gundir];
        if(nx < 1 or nx > n) return 1;
        if(ny < 1 or ny > m) return 1;
        if(Hp[nx][ny] == 1)
        {
            kill += 1;
            Hp[nx][ny] = 0;
            return 1;
        }
        else if(Hp[nx][ny] == 2)
        {
            Hp[nx][ny] -= type;
            if(Hp[nx][ny] == 0) kill += 1;
            return 1;
        }
        else if(Hp[nx][ny] > 3)
            return 1;
    }
    return 1;
}
auto WT(int x) -> int
{
    if(x != 0 and x != 1) return -1;
    if(x == 0) (robodir += 1) %= 4;
    if(x == 1) (robodir += 3) %= 4;
    return 1;
}
auto WG(int t) -> int
{
    for(int i = 1; i <= t; i += 1)
    {
        x += dx[robodir];
        y += dy[robodir];
        if(Hp[x][y] != 0 or x < 1 or x > n or y < 1 or y > m)
            return -1;
    }
    return 1;
}
auto END() -> int
{
    return 1145141919;
}
auto record() -> void
{
    for(int i = 1; i <= rest; i += 1)
        data.magezine[i] = magezine[i];
    data.rest = rest;
    data.lightrest = lightrest;
    data.heavyrest = heavyrest;
    data.robodir = robodir;
    data.gundir = gundir;
    data.x = x;
    data.y = y;
    data.kill = kill; 
    data.state = state;
}

Or Plus Max

题目描述

有一个长度位 \(2^n\) 的数列 \(A\), 编号为 \(0, 1, 2...2^{n}-1\) 。对于每一个 \(k(0 < k < 2^n)\) , 求 \(\max(a_i, a_j),(i \,or\, j,\le k , i \not= j )\), or 为按位或运算。

解题思路

我们在枚举 \(k\) 时已经知道了 小于 \(k\) 的所有答案,这是我们只需要枚举 按位或等于 \(k\) 的两个数,和与之前答案取较大值即可。
由于按位或的性质,我们可以通过枚举子集的方式找到两个数,在枚举子集时记录下最大值次大值,答案的候选值即为最大值与次大相加。
当然直接枚举子集的时间复杂度是我们所不能接受的,所以我们不需要枚举所有子集,因为有的子集被其他子集所包含,所有一次只扣掉一个 \(1\) 也是个不错的选择。

#include<iostream>
#include<cstdio>
#define lowbit(i) (i&(-i))
using namespace std;
const int MAXN = 525010;
int n, a[MAXN], ans;
int smax[MAXN], kmax[MAXN], cnt[MAXN], id[MAXN];
auto prework() -> void;
auto calc(int k) -> int;
auto solve(int x) -> void;
int main()
{
    scanf("%d", &n); n = (1<<n)-1;
    for(int i = 0; i <= n; i += 1) 
    scanf("%d", &a[i]); prework();
    for(int i = 1; i <= n; i += 1)
    {
        ans = max(ans, calc(i));
        printf("%d\n",  ans);
    }
    return 0;
}
auto prework() -> void
{
    smax[0] = a[0]; ans = a[0];
    for(int i = 1; i <= n; i += 1)
    {
        smax[i] = a[i]; id[i] = i; 
        cnt[i] = 1; solve(i);
    }
}
auto solve(int x) -> void
{
    int k = x, p, now;
    while(k)
    {
        p = x-lowbit(k); now = smax[p];
        if(now > smax[x]) 
        {
            kmax[x] = smax[x]; smax[x] = now;
            cnt[x] = cnt[p]; id[x] = id[p];
        }
        else if(now == smax[x]){
            if(id[p] != id[x]) cnt[x] += 1;
        }
        else kmax[x] = max(kmax[x], now);
        k -= lowbit(k);
    }
}
auto calc(int x) -> int
{
    if(cnt[x] >= 2) return smax[x]*2;
    return smax[x] + kmax[x];
}

LEGOndary Grandmaster

题目描述

对于两个长度为 \(n\)01\(s,t\) ,你可以对 \(s\) 进行两种操作:把相邻两个 \(0\) 变成 \(1\) 或把相邻两个 \(1\) 变成 \(0\) ,定义 \(s\)\(t\) 的距离为最少操作次数使得 \(s\) 变成 \(t\) ,如过没法变则距离为 \(0\)

现在你有两个不完整的字符串,可以把其中的 ? 变成 \(0\)\(1\) ,求所有情况所得到的两个 01 串的距离之和。

解题思路

0011 操作有点不好搞,我们转化一下, 对于所有奇数位(偶数位也可以)的数字进行翻转,我们的操作就变成把 01 变成 10 和 把 10 变成 01,可以看作交换两个数字。由此一来就好搞多了。
对于交换两个相同的数,显然是浪费操作的不优解,因此并不会有这种情况。
首先,两个串 \(0\)\(1\) 的个数不相同肯定无解。
我们的新目标是,求所有第 \(k\)\(1\) 的位置的差的和。
因为有 ? 的存在,我们考虑每一个 \(1\) 的贡献, 用 DP 转移,记 \(pre_{i, j}\) 为串的前缀 \(1\) ~ \(i\) 中,\(1\) 的个数差为 \(j\) 的方案数,\(f_{i, j}\) 为方案之和。
通过下一位的 \(0\)\(1\) 的取值,我们可以将 \(pre_{i, j}\) 转移到 \(pre_{i+1,j+1}, pre{i+1,j}, pre{i+1,j-1}\),将方案数求和存在 \(f\) 里。
因为 \(j\) 可能为负数,我们可以整体加上一个数变成非负的就方便转移了。

#include<iostream>
#include<cstring>
#include<cstdio>
#define _(x) (x+N)
#define abs(x) (x<0?-x:x)
using namespace std;
const int p = 1e9+7;
const int N = 2e3+0;
const int MAXN = 2e3+10;
const int MAXA = MAXN<<1;
int T, n, a[MAXN], b[MAXN];
int pre[MAXN][MAXA], f[MAXN][MAXA];
char s[MAXN];
auto work() -> void;
int main()
{
    scanf("%d", &T);
    while(T--) work();
    return 0;
}
auto prework(int *A) -> void
{
    for(int i = 1; i <= n; i += 1)
    {
        if(s[i] == '?') A[i] = -1;
        else A[i] = s[i]-'0';
    }
    for(int i = 1; i <= n; i += 2)
        if(A[i] != -1) A[i] = !A[i];
}
inline auto check(int a, int x) -> bool
{
    return (a == -1) or (a == x);
}
auto work() -> void
{
    scanf("%d", &n);
    scanf("%s", s+1), prework(a);
    scanf("%s", s+1), prework(b);
    pre[0][N] = 1;
    for(int i = 1; i <= n; i += 1)
    for(int j = -n; j <= n; j += 1)
    for(int x = 0; x < 2; x += 1)
    for(int y = 0; y < 2; y += 1)
        if(check(a[i], x) and check(b[i], y))
            (pre[i][_(j+x-y)] += pre[i-1][_(j)]) %= p;
    for(int i = 1; i <= n; i += 1)
    for(int j = -n; j <= n; j += 1)
    for(int x = 0; x < 2; x += 1)
    for(int y = 0; y < 2; y += 1)
        if(check(a[i], x) and check(b[i], y))
            (f[i][_(j+x-y)] += ((long long)abs(j)*pre[i-1][_(j)]+f[i-1][_(j)])%p) %= p;
    printf("%d\n", f[n][N]);
    for(int i = 1; i <= n; i += 1)
    for(int j = -n; j <= n; j += 1)
    f[i][_(j)] = pre[i][_(j)] = 0;
}
posted @ 2022-09-10 19:57  Aniciry  阅读(107)  评论(0)    收藏  举报