PDF 题解以及测试数据和标程已发至牛客各群~

A - ★★比赛新机制★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★★☆☆
☆☆☆☆☆

标签:<label>递推</label><label>前缀和</label>

题解

,先考虑正序的情况:

若答题顺序为 ,那么总罚时为

若答题顺序为 ,那么总罚时为

不难发现,,以此类推,可得 ,反序同理。

于是,在每次递推时更新最小值即可。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 500005
#define INF 123456789000000000
ll T, n, sum, isum, risum, a[maxn], ans;
int main()
{
    scanf("%lld", &T);
    while (T--) {
        scanf("%lld", &n);
        sum = isum = risum = 0;
        ans = INF;
        for (ll i = 1; i <= n; i++) {
            scanf("%lld", &a[i]);
            sum += a[i];
            isum += i * a[i];
            risum += (n - i + 1) * a[i];
        }
        for (ll i = n; i >= 1; i--) {
            isum += sum - n * a[i];
            ans = min(ans, isum);
        }
        for (ll i = 1; i <= n; i++) {
            risum += sum - n * a[i];
            ans = min(ans, risum);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

B - ★★体育课排队★★

时间限制: 4000ms
空间限制: 256MB
难度: ★★★★★
★★☆☆☆

标签:<label>二分图</label><label>网络流</label><label>二分答案</label><label style="background&#45;color&#58;&#35;f39c11">Special Judge</label>

题解

根据题意,考虑将 位同学视为二分图的左部,将 个位置视为二分图的右部,将该问题转化为二分图匹配问题。

由于匹配是同时开始进行的,因此每次二分图匹配的时间只与最长的那组匹配有关,要求该时间的最小值,使用 等方法难以解决。于是考虑二分答案,每次只将所有范围之内的边加入二分图,然后进行二分图匹配。若能够完全匹配,说明在当前时间约束下可以完成排队任务;否则应扩大时间约束。

使用匈牙利 算法,总时间复杂度 ,无法通过。

注意到该图为二分图,可以用优化过的网络流算法进行二分图匹配,单次复杂度只有 ,相比匈牙利 算法的 具有明显优势。匹配完成后,利用残余网络按要求输出匹配情况。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
#define maxn 2005
#define maxm 2004005
#define INF 1234567890
int T, n, m, ss[maxn], sx[maxn], sy[maxn], sum[maxn], l, r, s, t, maxflow, cnt = 1, x[maxn], y[maxn], head[maxn], dis[maxn], cur[maxn], gap[maxn], ans[maxn];
struct Edge {int u, v, w, pre, next;} edge[maxm];
inline void add(int u, int v, int w) {
    edge[++cnt].u = u;
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].pre = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
    return;
}
inline int Dfs(int u, int lim) {
    if (!lim || u == t) return lim;
    int flow = 0, f;
    for (int i = cur[u]; i; i = edge[i].next) {
        int v = edge[i].v, w = edge[i].w;
        cur[u] = i;
        if (dis[v] + 1 == dis[u] && (f = Dfs(v, min(lim, w)))) {
            flow += f;
            lim -= f;
            edge[i].w -= f;
            edge[i ^ 1].w += f;
            if (!lim) return flow;
        }
    }
    gap[dis[u]]--;
    if (!gap[dis[u]]) dis[s] = t + 1;
    dis[u]++;
    gap[dis[u]]++;
    return flow;
}
inline void Bfs() {
    queue<int>q;
    for(int i = 1; i <= t; i++) {
        dis[i] = INF;
        gap[i] = 0;
    }
    dis[t] = 0;
    gap[0] = 1;
    q.push(t);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] >= INF) {
                dis[v] = dis[u] + 1;
                gap[dis[v]]++;
                q.push(v);
            }
        }
    }
    return;
}
inline void ISAP() {
    Bfs();
    while (dis[s] < t) {
        for (int i = 1; i <= t; i++)
            cur[i] = head[i];
        maxflow += Dfs(s, INF);
    }
}
inline bool Check(int mid) {
    cnt = 1;
    maxflow = 0;
    for (int i = 1; i <= t; i++)
        head[i] = dis[i] = cur[i] = gap[i] = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int k = 1; k <= ss[j]; k++) {
                int w = abs(sx[j] + k - 1 - x[i]) + abs(sy[j] - y[i]);
                if (w > mid) continue;
                add(i, n + sum[j-1] + k, 1);
                add(n + sum[j-1] + k, i, 0);
            }
    for(int i = 1; i <= n; i++) {
        add(s, i, 1);
        add(i, s, 0);
        add(i + n, t, 1);
        add(t, i + n, 0);
    }
    ISAP();
    if (maxflow == n) return 1;
    else return 0;
}
int main()
{
    cin >> T;
    while (T--) {
        cin >> n >> m;
        l = 0, r = 5000;
        for (int i = 1; i <= n; i++)
            cin >> x[i] >> y[i];
        for (int i = 1; i <= m; i++) {
            cin >> ss[i] >> sx[i] >> sy[i];
            sum[i] = sum[i-1] + ss[i];
        }
        s = 2 * n + 1;
        t = 2 * n + 2;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (Check(mid)) r = mid;
            else l = mid + 1;
        }
        cout<< l << endl;
        Check(l);
        for (int i = 1; i <= n; i++)
            for (int j = head[i]; j; j = edge[j].next) {
                int v = edge[j].v, w = edge[j].w, pre = edge[j].pre;
                if (pre > w) ans[v - n] = i;
            }
        for(int i = 1; i <= m; i++)
            for(int j = 1; j <= ss[i]; j++)
                cout << ans[sum[i-1] + j] << " \n"[j == ss[i]];
    }
    return 0;
}

C - ★★生命的游戏★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★☆☆☆
☆☆☆☆☆

标签:<label>暴力</label><label>模拟</label>

题解

按要求暴力模拟即可。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
#define maxn 105
int T, n, k, ans, a[maxn][maxn], now[maxn][maxn], s[maxn][maxn], di[] = {-1, -1, -1, 0, 0, 1, 1, 1}, dj[] = {-1, 0, 1, -1, 1, -1, 0, 1};
inline bool Check() {
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (a[i][j] != now[i][j]) return 0;
    return 1;
}
inline void Solve() {
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            s[i][j] = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (now[i][j]) {
                for (int k = 0; k < 8; k++) {
                    int ni = (i + di[k] + n) % n, nj = (j + dj[k] + n) % n;
                    s[ni][nj]++;
                }
            }
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (s[i][j] == 3) now[i][j] = 1;
            else if (s[i][j] < 2 || s[i][j] > 3) now[i][j] = 0;
}
int main()
{
    cin >> T;
    while (T--) {
        cin >> n >> k;
        ans = 0;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++) {
                cin >> a[i][j];
                now[i][j] = a[i][j];
            }
        do {
            Solve();
            ans++;
        }
        while (!Check() && --k);
        if (k) cout << "YES" << endl << ans << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

D - ★★飞马祝福语★★

时间限制: 4000ms
空间限制: 256MB
难度: ★★★★★
★★★☆☆

标签:<label>递推</label><label>动态规划</label><label>区间动态规划</label><label>矩阵</label><label>线段树</label>

题解

设祝福信为 FeiMa,下标均从 开始。

表示 的前 位以 FeiMa 中第 个字符结尾的不同子序列的数量( 表示没有出现其中任何一个字符),那么状态转移方程为:
$dp[n][5]$。

暴力求解,时间复杂度 ,无法通过。

考虑用矩阵进行状态转移,设

F,那么有:
$s[i]=s[i]6$ 种转移矩阵。

于是将问题转化为矩阵的区间乘积维护,考虑将矩阵作为线段树结点的元素。

进行区间修改使用矩阵快速幂求值然后赋值,总时间复杂度 ,无法通过。

考虑预处理出 种转移矩阵的 次方,在每次区间修改时可以直接 赋值。

时间复杂度

另外,此题还有复杂度更优的算法:线段树维护区间动态规划。

设祝福信为 FeiMa 的下标从 开始, 的下标从 开始。

使用线段树维护区间动态规划。令 表示线段树的 节点包含的字符子串范围内,拥有字符串 位到 位区间的子序列数量 (例如, 表示 节点中 F 的数量, 表示节点中 e 的数量, 表示节点中子序列为 iM 的数量),那么状态转移方程为:
$dp[1][0][4]O(5^{3}q \log{n})$。

参考代码

C++ - 1

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define p 998244353
struct Matrix {
    int a[6][6], n = 5;
    Matrix(const int & N = 5) : n(N) {memset(a, 0, sizeof(a));}
    void operator = (const Matrix & x) {
        n = x.n;
        for(int i = 0; i <= n; i++)
            for(int j = 0; j <= n; j++)
                a[i][j] = x.a[i][j];
    }
    Matrix operator * (const Matrix & x) const {
        Matrix ans = Matrix(n);
        for(int i = 0;i <= n; i++)
            for(int j = 0; j <= n; j++)
                for(int k = 0; k <= n; k++)
                    ans.a[i][j] = (ans.a[i][j] + 1ll * a[i][k] * x.a[k][j]) % p;
        return ans;
    }
};
int T, n, q, a[maxn];
char x;
map<char, int> m;
Matrix pre[6][maxn];
struct Tree {Matrix mul; int tag;}t[maxn << 2];
inline int ls(int k) {return k << 1;}
inline int rs(int k) {return k << 1 | 1;}
inline void push_up(int l, int r, int k) {
    t[k].mul = t[ls(k)].mul * t[rs(k)].mul;
}
inline void push_down(int l, int r, int k) {
    if (t[k].tag != -1) {
        t[ls(k)].tag = t[k].tag;
        t[rs(k)].tag = t[k].tag;
        int mid = (l + r) >> 1;
        t[ls(k)].mul = pre[t[k].tag][mid - l + 1];
        t[rs(k)].mul = pre[t[k].tag][r - mid];
        t[k].tag = -1;
    }
}
inline void build(int l, int r, int k) {
    t[k].tag = -1;
    if (l == r) {
        t[k].mul = pre[a[l]][1];
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, ls(k));
    build(mid + 1, r, rs(k));
    push_up(l, r, k);
}
inline void update(int nl, int nr, int l, int r, int k, int x) {
    if (nl <= l && r <= nr) {
        t[k].mul = pre[x][r - l + 1];
        t[k].tag = x;
        return;
    }
    push_down(l, r, k);
    int mid = (l + r) >> 1;
    if (nl <= mid) update(nl, nr, l, mid, ls(k), x);
    if (nr > mid) update(nl, nr, mid + 1, r, rs(k), x);
    push_up(l, r, k);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    for (int k = 0; k <= 5; k++) {
        for(int i = 0; i <= 5; i++)
            pre[k][0].a[i][i] = pre[k][1].a[i][i] = 1;
        if (k) pre[k][1].a[k-1][k] = 1;
    }
    for(int k = 0;k <= 5; k++)
        for(int i = 2;i < maxn; i++)
            pre[k][i] = pre[k][i - 1] * pre[k][1];
    m['F'] = 1, m['e'] = 2,m['i'] = 3,m['M'] = 4,m['a'] = 5;
    cin >> T;
    while (T--) {
        cin >> n >> q;
        for (int i = 1; i <= n; i++) {
            cin >> x;
            a[i] = m[x];
        }
        build(1, n, 1);
        int l, r;
        while (q--) {
            cin >> l >> r >> x;
            update(l, r, 1, n, 1, m[x]);
            cout << t[1].mul.a[0][5] << endl;
        }
    }
    return 0;
}

C++ - 2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 1e5+100;
int T,n,m,q;
char ss[maxn];
char pp[] = "FeiMa";
ll pro[maxn<<2][5][5];
char lazy[maxn<<2];
void build(int rt,int l,int r)
{
    lazy[rt] = 0;
    if(l==r)
    {
        for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt][i][j] = 0;
        for(int i=0; i<5; i++) pro[rt][i][i] = ss[l]==pp[i];
        return;
    }
    int mid = (l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    for(int i=0; i<5; i++)
    {
        for(int j=i; j<5; j++)
        {
            pro[rt][i][j] = (pro[rt<<1][i][j]+pro[rt<<1|1][i][j])%mod;
            for(int k=i; k<j; k++) pro[rt][i][j] = (pro[rt][i][j]+pro[rt<<1][i][k]*pro[rt<<1|1][k+1][j])%mod;
        }
    }
}
void push_down(int rt,int l,int r)
{
    if(!lazy[rt]) return;
    lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
    int mid = (l+r)>>1;
    int sz = mid-l+1;
    for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt<<1][i][j] = 0;
    for(int i=0; i<5; i++) pro[rt<<1][i][i] = (lazy[rt]==pp[i])*sz;
    sz = r-mid;
    for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt<<1|1][i][j] = 0;
    for(int i=0; i<5; i++) pro[rt<<1|1][i][i] = (lazy[rt]==pp[i])*sz;
    lazy[rt] = 0;
}
void update(int rt,int l,int r,int fr,int to,char ch)
{
    if(fr<=l && r<=to)
    {
        for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt][i][j] = 0;
        lazy[rt] = ch;
        int sz = r-l+1;
        for(int i=0; i<5; i++) pro[rt][i][i] = (ch==pp[i])*sz;
        return;
    }
    push_down(rt,l,r);
    int mid = (l+r)>>1;
    if(mid>=fr) update(rt<<1,l,mid,fr,to,ch);
    if(mid+1<=to) update(rt<<1|1,mid+1,r,fr,to,ch);
    for(int i=0; i<5; i++)
    {
        for(int j=i; j<5; j++)
        {
            pro[rt][i][j] = (pro[rt<<1][i][j]+pro[rt<<1|1][i][j])%mod;
            for(int k=i; k<j; k++) pro[rt][i][j] = (pro[rt][i][j]+pro[rt<<1][i][k]*pro[rt<<1|1][k+1][j])%mod;
        }
    }
}
char ch[2];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%s",&n,&q,ss + 1);
        build(1,1,n);
        int l,r;
        while(q--)
        {
            scanf("%d%d%s",&l,&r,ch);
            update(1,1,n,l,r,ch[0]);
            printf("%lld\n",pro[1][0][4]);
        }
    }
    return 0;
}

E - ★★序列大团结★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★★★★
☆☆☆☆☆

标签:<label>哈希Hash</label>

题解

先考虑序列中无法被 整除的数,设 为序列前 项中数值 出现的次数模 的余数,当 完全相同时(即每个数值 出现的次数模 的余数都相同),子序列 就是“团结”的。

考虑使用滚动数组,将 优化至 ,从首项开始遍历并维护 ,同时对 的内容进行 处理,将处理后的结果保存,记 为到目前为止 出现的次数,那么以当前项为右端点的团结的子序列的数量为 可用 存储。

而能够被 整除的数,不影响 值,但也同样需要计算答案。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
#define p 998244353
ull T, n, k, x, ans, now, hs;
map<ull, int>num;
map<int, int>s;
inline ull ksm(ull x, ull k) {
    ull res = 1;
    for(; k; k >>= 1, x = x * x)
        if (k & 1) res = res * x;
    return res;
}
int main(){
    cin >> T;
    while (T--) {
        cin >> n >> k;
        num.clear();
        s.clear();
        ans = now = 0;
        num[0] = 1;
        for (int i = 1; i <= n; i++) {
            cin >> x;
            if (x % k) {
                hs = ksm(p, x);
                now -= hs * s[x];
                s[x] = (s[x] + 1) % k;
                now += hs * s[x];
            }
            ans += num[now];
            num[now]++;
        }
        cout << ans << endl;
    }
    return 0;
}

F - ★★飞马分隔符★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★☆☆☆
☆☆☆☆☆

标签:<label>模拟</label><label>字符串</label><label>贪心</label>

题解

从头到尾遍历,每次遇到字符 a,判断前面是否顺次出现过一组 FeiM,若出现过,则在此处分隔,更新答案,并清空记录。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
int T, n, ans, flag;
string s;
int main()
{
    cin >> T;
    while (T--) {
        cin >> n >> s;
        ans = flag = 0;
        for (int i = 0; i < n; i++) {
            if (s[i] == 'F' && flag == 0) flag++;
            else if (s[i] == 'e' && flag == 1) flag++;
            else if (s[i] == 'i' && flag == 2) flag++;
            else if (s[i] == 'M' && flag == 3) flag++;
            else if (s[i] == 'a' && flag == 4) {
                ans++;
                flag = 0;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

G - ★★糖果魔法阵★★

时间限制: 2000ms
空间限制: 256MB
难度: ★★★★★
★★★★☆

标签:<label>数学</label><label>数论</label><label>递推</label><label>二次剩余</label><label>光速幂</label><label>扩展欧拉定理</label><label>逆元</label>

题解

根据题意,定义 ,求出两个实数不动点为

构造

推出通项为

由于 是模 的二次剩余,将 代入得:

考虑指数 ,根据欧拉定理,可对其取模,即

考虑指数 ,根据扩展欧拉定理,当 时可对其取模,即

使用快速幂求出每天结束时的 ,进而求出下一天的魔力值,时间复杂度 ,无法通过。

考虑对 进行模 的光速幂处理,对 进行模 的光速幂处理。

在每天结束时,用光速幂求出相应的 ,便可得知下一天的情况。

直到最后一天结束时,用光速幂和费马小定理求出

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 31625
#define two 116195171
#define ka 116195172
#define kb 882049183
#define phi 402653184
#define p 998244353
ll s, d, q, n, a[maxn + 5][2], b[maxn + 5][2], c[maxn + 5][2], k, t, sum, ans;
bool flag;
inline void Init() {
    a[0][0] = a[0][1] = b[0][0] = b[0][1] = c[0][0] = c[0][1] = 1;
    for (ll i = 1; i <= maxn; i++) {
        a[i][0] = a[i - 1][0] * ka % p;
        b[i][0] = b[i - 1][0] * kb % p;
        c[i][0] = c[i - 1][0] * 4 % (p - 1);
    }
    for (ll i = 1; i <= maxn; i++) {
        a[i][1] = a[i - 1][1] * a[maxn][0] % p;
        b[i][1] = b[i - 1][1] * b[maxn][0] % p;
        c[i][1] = c[i - 1][1] * c[maxn][0] % (p - 1);
    }
}
inline ll ksm(ll x, ll k) {
    ll res = 1 % p; x %= p;
    for (; k; k >>= 1, x = x * x % p)
        if (k & 1) res = res * x % p;
    return res;
}
int main()
{
    Init();
    cin >> s >> d >> q;
    while (q--) {
        cin >> n;
        sum = s;
        k = flag = 0;
        for (ll i = 1; i <= n; i++) {
            k += sum / d;
            if (k >= phi) flag = 1;
            k %= phi;
            if(flag) k += phi;
            t = c[k % maxn][0] * c[k / maxn][1] % (p - 1);
            sum = (((a[t % maxn][0] * a[t / maxn][1] % p) + (b[t % maxn][0] * b[t / maxn][1] % p)) * two % p) * i % p;
        }
        ans = (sum * ksm(n, p - 2) % p) * ksm((((a[t % maxn][0] * a[t / maxn][1] % p) - (b[t % maxn][0] * b[t / maxn][1] % p) + p) % p) % p, p - 2)%p;
        cout << ans << endl;
    }
    return 0;
}

H - ★★温暖的力量★★

时间限制: 1000ms
空间限制: 256MB
难度: ★☆☆☆☆
☆☆☆☆☆

标签:<label>数学</label>

题解

要求将 分为尽可能多的质数的和。

为大于 的奇数时,可以分为若干个 和一个 ;当 为大于 的偶数时,可以分为若干个

不难得出答案:
$$

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
int T, n;
int main()
{
    cin >> T;
    while (T--) {
        cin >> n;
        if (n <= 3) cout << -1 << endl;
        else cout << n / 2 << endl;
    }
    return 0;
}

I - ★★平形四边行★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★★★★
☆☆☆☆☆

标签:<label>暴力</label><label style="background&#45;color&#58;&#35;f39c11">Special Judge</label>

题解

四个点能形成“平形四边行”的充要条件是,存在一种方案,将四个点均分为两组,每组的两个点形成一条线段,这两条线段的中点重合。

注意到桶的大小只有 ,即第 个点落下时,一定存在重合的中点,从而构成一组解。另外,桶不可能被完全填满,因此复杂度远远小于预期。

于是 暴力枚举每组点,存储所形成线段的中点,直到存在重合的中点,同时应注意过滤掉存在交集的两组点。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
int n, x[maxn], y[maxn];
pair<int, int>v[4005][4005];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x[i] >> y[i];
        for (int j = 1; j < i; j++) {
            int xx = x[i] + x[j] + 2000;
            int yy = y[i] + y[j] + 2000;
            if (i == v[xx][yy].first || j == v[xx][yy].first) continue;
            if (i == v[xx][yy].second || j == v[xx][yy].second) continue;
            if (v[xx][yy].first) {
                cout << "YES" << endl << i << ' ' << j << ' ' << v[xx][yy].first << ' ' << v[xx][yy].second << endl;
                return 0;
            }
            v[xx][yy] = make_pair(i,j);
        }
    }
    cout << "NO" << endl;
    return 0;
}

J - ★★翻滚吧硬币★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★★★☆
☆☆☆☆☆

标签:<label>数学</label><label>计算几何</label><label style="background&#45;color&#58;&#35;f39c11">Special Judge</label>

题解

硬币悖论问题。

如图,三枚硬币 ,半径分别为

图片说明

现在,让硬币 沿着硬币 的边界翻滚,硬币 的圆心的运动轨迹为曲线 ,记其长度为 ,那么翻滚的圈数即为

由弧长公式可得 ,其中 可由余弦定理推出:$$。

要想使得圈数最小,只需固定半径较小的两个硬币,让半径最大的硬币沿其边界翻滚即可。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
const long double pi=acosl(-1);
int T, r[5];
long double ans, a, b, c, x, y;
int main()
{
    cin >> T;
    while (T--) {
        cin >> r[1] >> r[2] >> r[3];
        sort(r + 1, r + 3 + 1);
        a = r[1] + r[2], b = r[1] + r[3], c = r[2] + r[3];
        x = acosl((a * a + b * b - c * c) / (2.0l * a * b));
        y = acosl((a * a + c * c - b * b) / (2.0l * a * c));
        ans = ((pi - x) * b + (pi - y) * c) / (pi * r[3]);
        cout << fixed << setprecision(15) << ans << endl;
    }
    return 0;
}

K - ★★快乐苹果树★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★★★★
★★★★☆

标签:<label>数学</label><label>逆元</label><label>概率论</label><label>数学期望</label><label>树链剖分</label><label>树状数组</label><label>线段树</label>

题解

定义 表示以结点 为根的子树的大小, 表示结点 的 所有直接子结点(距离为 )组成的集合, 表示以结点 为根的子树内所有节点组成的集合。

对于操作一,可分为以下两种情况:

  • 当选取结点 ,那么点集 表示的是 ,此时该操作与 无关,只与 有关。结点 的概率为 ,于是点集 中所有结点的快乐值增加

  • 当选取结点 时,概率为 ,点集 表示的是 ,于是点集 中所有结点的快乐值增加

预处理出各子树大小以及对应逆元,暴力求解,总时间复杂度 ,无法通过。

考虑使点集 中所有结点的快乐值都加上该值,然后再使点集 中所有结点的快乐值减去该值。

如此一来,对于每次操作一,使点集 中所有结点的快乐值增加

随后,遍历 ,使点集 中所有结点的快乐值减去

总时间复杂度 ,无法通过。

考虑将子树按大小分块,在每次进行子树更新时使用线段树或树状数组维护,总时间复杂度 ,可以通过,但不稳定且代码实现较为困难。

考虑进行树链剖分,将轻儿子 懒标记的值放置在 结点即 结点,重儿子 更新。查询时,遍历 节点,减去对应的懒标记的值。

时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 100005
#define maxm 200005
#define p 998244353
ll T, n, q, inv[maxn], t[maxn], tag[maxn], fa[maxn], se[maxn], son[maxn], dfn[maxn], top[maxn], id, cnt, head[maxn], s1[maxn], s2[maxn], s3[maxn];
struct Edge {ll u, v, next;} edge[maxm];
inline void add(ll u, ll v) {
    edge[++cnt].u = u;
    edge[cnt].v = v;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline void Get_Inv() {
    inv[1] = 1;
    for (ll i = 2; i < maxn; i++)
        inv[i] = (p - p / i) * inv[p % i] % p;
}
inline ll lowbit(ll x) {return x & (-x);}
inline void change(ll x, ll val)
{
    for (ll i = x; i <= n; i += lowbit(i))
        t[i] = (t[i] + val) % p;
}
inline ll query(ll x) {
    ll res = 0;
    for (ll i = x; i; i -= lowbit(i))
        res = (res + t[i]) % p;
    return res;
}
inline void update(ll l, ll r, ll val) {
    change(l, val);
    change(r + 1, p - val);
}
inline void Dfs1(int u, int f) {
    fa[u] = f;
    se[u] = 1;
    son[u] = s3[u] = 0;
    for (ll i = head[u]; i; i = edge[i].next) {
        ll v = edge[i].v;
        if (v == f) continue;
        Dfs1(v, u);
        se[u] += se[v];
        if (se[v] > se[son[u]]) son[u] = v;
    }
    s1[u] = inv[se[u]];
    for (ll i = head[u]; i; i = edge[i].next) {
        ll v = edge[i].v;
        if (v == f) continue;
        s2[v] = (se[v] * s1[u] % p) * inv[se[u] - se[v]] % p;
        s3[u] = (s3[u] + s2[v]) % p;
    }
}
inline void Dfs2(int u, int t) {
    top[u] = t;
    dfn[u] = ++id;
    if (!son[u]) return;
    Dfs2(son[u], t);
    for (ll i = head[u]; i; i = edge[i].next) {
        ll v = edge[i].v;
        if (v == son[u] || v == fa[u]) continue;
        Dfs2(v, v);
    }
}
int main()
{
    Get_Inv();
    cin >> T;
    while (T--) {
        cin >> n >> q;
        cnt = id = 0;
        for (ll i = 0; i <= n; i++)
            head[i] = t[i] = tag[i] = 0;
        ll u, v;
        for (ll i = 1; i < n; i++) {
            cin >> u >> v;
            add(u, v);
            add(v, u);
        }
        Dfs1(1, 0);
        Dfs2(1, 1);
        ll op, f, k;
        while (q--) {
            cin >> op;
            if (op == 1) {
                cin >> f >> k;
                update(dfn[f], dfn[f] + se[f] - 1, (s3[f] * k % p + (s1[f] * s1[f] % p) * k % p) % p);
                if (son[f])
                    update(dfn[son[f]], dfn[son[f]] + se[son[f]] - 1, (p - s2[son[f]] * k % p) % p);
                tag[f] = (tag[f] + k) % p;
            }
            else {
                cin >> f;
                ll res = query(dfn[f]);
                for(ll i = top[f]; fa[i]; i = top[fa[i]])
                    res = (res + (p - s2[i] * tag[fa[i]] % p) % p) % p;
                cout << res << endl;
            }
        }
    }
    return 0;
}

L - ★★星星收集者★★

时间限制: 1000ms
空间限制: 256MB
难度: ★★★★★
★☆☆☆☆

标签:<label>博弈论</label><label>动态规划</label>

题解

令星星的总和为
首先,因为双方的星星总和一定是 ,如果 ,那么当 就获胜了。
反之,如果 ,当 获胜。
其余情况 获胜。
那么我们从 的角度考虑,他的策略根据 的关系,只有两种:

  • 抓尽可能少的星星

  • 抓尽可能多的星星(也就是全部抓取)

需要让 在这两种策略下都不能获胜。
令添加星星数为 ,首先可以发现,当时, 必胜(全部抓取),所以可以得到
时,考虑 在添加星星时的策略。

我们发现,在此情况下,双方都想最小化自己的星星数。那么 会尽可能的将 分配给 ,只要将 全部分配给第一个盒子就能做到。
我们用动态规划可以求出在原先状态下,a最少可以抓取多少星星。
令这个值为 ,则能得到
综合两种情况就能得到 的最小值。
时间复杂度

参考代码

C++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 200005
ll n, x, a[maxn], sum[maxn], dp[maxn], last;
int main()
{
    cin >> n >> x;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    last = n;
    dp[n] = a[n];
    for (ll i = n - 1; i; i--) {
        dp[i] = sum[n] - sum[i - 1] - dp[last];
        if (dp[i] > dp[last]) last = i;
    }
    cout << max(max(0ll, sum[n] - dp[1] - dp[1] + 1), max(0ll, 2 * x + 1 - sum[n])) << endl;
    return 0;
}