CUGBACM2022校赛总结

2022.10.23 9:00~13:00 AC 5/14 总排名20 大一排名4 金奖

梦开始的地方

传送门

A-Carry爱排序

题目描述

Carry刚上大一,最近在学各种排序,他学习“冒泡排序”之后,研究出一种叫“泡冒排序”方法。

Carry有一个长度为n的正整数数列,它可以通过一个操作来改变这个数列:

  • 选择数列中相邻的两个数字并且交换位置,并且把它们都乘以−1。也就是选择一个下标i(1≤i<n),使得a[i]和a[i+1]进行交换,然后乘以−1,也就是a[i]=−a[i+1],a[i+1]=−a[i]

Carry想知道通过若干次(可能0次)这种操作是否能够将数列从小到大排序,也就是最后数列是不递减数列,当然最后的数列所有数都应该是正数。但是蠢Carry他不会,所以他来问问你是否可以。

注意:可能会有重复元素,例如最后序列是[1,2,2]则也是符合条件(从小到大/不递减)的。

输入格式

每个输入一共有5组数据.

对于每一组数据,输入有两行.

第一行为一个正整数n(1≤n≤2×10^5),表示数列的长度.

第二行有n个正整数ai(1≤ai≤10^6),表示这个数列.

输出格式

对于每一组数据,输出一行内容:如果最后能完成“泡冒排序”则输出Yes,否则输出No


tag

思维

解题思路

进行若干次操作后可以发现,保证所有数都为正数的情况下,只能让距离为偶数的数进行相互调换,因此我们将数按照1,3,5,7,.../2,4,6,8,...分为两组,两组分别排序,再看看按顺序拼到一起是否有序即可

另一种方法则是按照权值对所有数分组,每个权值记录其下标之和,排序之后再记录一次,两种记录方法做差,如果差均为偶数则可行,否则不可行

代码一

#include<bits/stdc++.h>
using namespace std;
int n, x;
void solve() {
    cin>>n;
    vector<int> a, b;
    for (int i=1;i<=n;i++){
        cin>>x;
        if (i%2==1) 
            a.push_back(x);
        else 
            b.push_back(x);
    }
    sort(a.begin(), a.end());
    sort(b.begin(), b.end());
    if (n==1) {
        cout<<"Yes"<<endl;
        return;
    }
    vector<int> arr;
    int pa = 0, pb = 0;
    for (int i=1;i<=n;i++){
        if (i%2==1)
            arr.push_back(a[pa]), pa++;
        else
            arr.push_back(b[pb]), pb++;
    }
    for (int i=0;i<n-1;i++){
        if (arr[i]<=arr[i+1])
            continue;
        cout<<"No"<<endl;
        return;
    }
    cout<<"Yes"<<endl;
}

int main() {
    int _ = 5;
    while (_--){
        solve();
    }
}

代码二

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
    int f = 1; char ch = getchar();
    for (; ch < '0' || ch>'9'; ch = getchar())    if (ch == '-')    f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar())    x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
const int N = 1e5;
int A[N + 10];
int main() {
    for (int Times = 0; Times < 5; Times++) {
        map<int, int>Mp;
        int n = read(0);
        for (int i = 1; i <= n; i++)
            Mp[A[i] = read(0)] += i;
        sort(A + 1, A + 1 + n);
        for (int i = 1; i <= n; i++)
            Mp[A[i]] -= i;
        bool Flag = 0;
        for (auto p : Mp)
            Flag |= p.second & 1;
        printf(Flag ? "No\n" : "Yes\n");
    }
    return 0;
}

B-明明爱摸鱼

题目描述

众所周知,明明很喜欢摸鱼,所以他养了n条各不相同的鱼。

某天,明明想摸一下其中m条鱼,他想知道他有多少种不同的摸鱼方案。

我们认为两种方案不同,当且仅当存在至少一条鱼,在方案一种被摸过,在方案二中没被摸过。

由于答案可能过大,请输出答案取模 998244353 后的数值。

输入格式

在第一行中有一个正整数 t(1≤t≤100),表示测试用例的组数。

随后有 t 行,每行有两个以空格相间隔的正整数 n,m(1≤n≤10^6 ,0≤m≤n)。题目保证每个测试用例中 n 的总和不超过 10^6 。

输出格式

对于每一组 n,m,输出一行,为方案数取模 998244353 后的结果数值。


tag

费马小定理求逆元

解题思路

时空复杂度

时间复杂度:O(nlogn),如果预处理阶乘及阶乘逆元,可达到O(1) 。

空间复杂度:O(1)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353;
const ll maxn=1e6,M=1e6;
ll qmksm(ll a,ll b){
    ll sc=1,zc=a%mod;
    while(b){
        if(b&1) sc=(sc*zc)%mod;
        b>>=1,zc=(zc*zc)%mod;
    }
    return sc;
}
ll ny(ll x){
    return qmksm(x,mod-2);
}
ll inv[maxn+5];
void pre_ny(ll n,ll p){
    ll i,j,k;inv[1]=1;
    for(i=2;i<=n;i++) inv[i]=(p-p/i)*(inv[p%i])%p;
}
ll jc[maxn+5],jcn[maxn+5];
ll C(ll n,ll m){
    return jc[n]*jcn[m]%mod*jcn[n-m]%mod;
}
int main(){
    ll t,n,m,i,j,k;
    pre_ny(M,mod);
    for(i=jc[0]=jcn[0]=1;i<=M;i++) jc[i]=(jc[i-1]*i)%mod,jcn[i]=(jcn[i-1]*inv[i])%mod;
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",C(n,m));
    }
    return 0;
}

C-饮料店

题目描述

Wolfycz开了一家饮料店,这家店会卖n种饮料。我们知道,店家往往喜欢推出第二杯半价的促销活动,Wolfycz也不例外,他的店里面总共推出了m种第二杯半价的搭配。不过他的第二杯半价活动有些特殊,如果饮料x和饮料k1,k2,...,kt均有第二杯半价的搭配关系,那么只需要购买一杯饮料x,那么无论再购买任意多杯饮料k1,k2,...,kt,均可以享受半价优惠

Pigeon是饮料店的常客(也非常有钱),她每次来点饮料,都会被第二杯半价的活动给吸引。具体而言,Pigeon如果点了y杯饮料x,那么所有和x有半价搭配的饮料她都会同时买y杯

有时候,Pigeon会心血来潮,她想问问Wolfycz她已经买过多少杯饮料x了(Pigeon肯定不会记自己买了多少杯,Wolfycz作为店家可以查询营业额)。不过Wolfycz忙着做饮料(毕竟Pigeon每天都买很多杯),所以他把这个问题抛给了在店里打杂的你,希望你帮忙从营业额里面统计Pigeon买了多少杯(算对了就给你加薪)

输入格式

第一行输入三个正整数n,m,q(1≤n≤10^5 , 1≤m,q≤3×10^5),代表饮料种数,第二杯半价的搭配数,Pigeon购买与询问的总次数

之后m行,每行两个整数xi,yi(1≤ xi,yi ≤n , xi≠yi),第二杯半价的搭配关系(数据保证每对x,y只出现一次)

之后q行,每行两个或三个整数——

  • 三个整数:1 x y (1≤x≤n , 1≤y≤10^9),表示Pigeon点了y杯饮料x

  • 两个整数:2 x (1≤x≤n),表示Pigeon想知道已经买了多少杯饮料x

输出格式

对于Pigeon的每一次询问,输出一个整数,表示该次询问的答案


tag

分块

解题思路

我们记Di表示能与饮料i构成第二杯半价的饮料种数,记的饮料为一类饮料,否则为二类饮料

对于每次买饮料操作,如果饮料x为一类饮料,则暴力更新所有可更新的饮料,复杂度为 ;如果饮料x为二类饮料,则对其打上一个标记,复杂度为O(1)

对于每次询问操作,暴力统计与其连边的所有二类饮料,累加标记。因为二类饮料的总个数最多不会超过,因此该操作复杂度为

故总时间复杂度为

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
    int f = 1; char ch = getchar();
    for (; ch < '0' || ch>'9'; ch = getchar())    if (ch == '-')    f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar())    x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
inline void print(int x) {
    if (x < 0)    putchar('-'), x = -x;
    if (x > 9)    print(x / 10);
    putchar(x % 10 + '0');
}
const int N = 1e5, M = 3e5;
int pre[(M << 1) + 10], now[N + 10], child[(M << 1) + 10], tot;
ll Deg[N + 10], Delta[N + 10], V[N + 10];
vector<int>Big[N + 10];
void link(int x, int y) { pre[++tot] = now[x], now[x] = tot, child[tot] = y; }
void connect(int x, int y) { link(x, y), link(y, x); }
int main() {
    int n = read(0), m = read(0), q = read(0);
    int limit = (int)sqrt(m);
    for (int i = 1; i <= m; i++) {
        int x = read(0), y = read(0);
        connect(x, y);
        Deg[x]++, Deg[y]++;
    }
    for (int i = 1; i <= n; i++) {
        if (Deg[i] > limit) {
            for (int p = now[i]; p; p = pre[p]) {
                int son = child[p];
                Big[son].emplace_back(i);
            }
        }
    }
    for (int i = 1; i <= q; i++) {
        int type = read(0);
        if (type == 1) {
            int x = read(0), y = read(0);
            if (Deg[x] <= limit) {
                for (int p = now[x]; p; p = pre[p]) {
                    int son = child[p];
                    V[son] += y;
                }
                V[x] += y;
            }
            else   Delta[x] += y;
        }
        else {
            int x = read(0);
            ll Ans = V[x] + Delta[x];
            for (auto p : Big[x])
                Ans += Delta[p];
            printf("%lld\n", Ans);
        }
    }
    return 0;
}

D-摘星星

题目描述

小 K漫步于星空之下,想起海子的诗,“摘下星星送给你,你就是我的全世界”“今夜,我不关心人类,我只想你”。

他不能摘下天上的星星,所以他打算从字符串中收集星星。

给出一个包含若干星号 * 的字符串 s 。

在一步操作中,你可以:

​ ● 选中 s 中的一个星号。

​ ● 移除星号 左侧 最近的那个 非星号 字符(保证存在),并移除该星号自身。

返回移除 所有 星号之后的字符串。

可以证明结果字符串是唯一的。

输入格式

第一行包含一个由小写字母和星号 * 组成的字符串,表示输入字符串s (1≤丨s丨≤10^5)

​ ● 输入保证总是可以执行题面中描述的操作。

输出格式

输出最后生成的字符串


tag

解题思路

用栈压入每个字符,碰到 * 就弹出栈顶,否则压入栈,最后输出栈即可

用 string 类,碰到 * 就执行 pop_back() 操作,否则加入字符串尾

用 vector<char> 模拟 string 亦可

代码

#include <bits/stdc++.h>
using namespace std;
int main(){
    string s, str;
    cin >> s;
    for (int i = 0; i < s.length(); i++){
        if (s[i] != '*') str += s[i];
        else str.pop_back();
    }
    cout << str;
}

E-圣诞树

题目描述

圣诞节到了,Wolfycz给Pigeon送了一棵圣诞树,这棵树有n个节点,n−1条边,节点上挂着一个个小礼物,每个礼物有一个价值,我们记i号节点上礼物的价值为Vi

Pigeon收到礼物之后非常开心,于是给Wolfycz扔了好几个问题:她每次指定两个节点x,y,并且问Wolfycz,x到y的路径上,价值第k小的礼物价值是多少

当然,Pigeon为了增加问题的难度,她会对每次询问的信息进行加密。具体而言,记上次的答案为LastAns(特别的,我们认为第一次询问的LastAns=0),那么Pigeon会给出x⊗LastAns , y⊗LastAns , k⊗LastAns的值(⊗为异或操作)

输入格式

第一行输入两个正整数n,m(1≤n,m≤2×10^5),表示树的节点数和Pigeon的询问次数

第二行输入n个正整数Vi(1≤Vi≤10^9),表示各个节点礼物的价值,保证各不相同

之后n−1行,每行两个正整数x,y(1≤x,y≤n),表示树的连边情况,即x,y之间有边相连,保证数据是一棵树

之后m行,每行三个正整数x,y,k,表示Pigeon给出的信息,记x′,y′,k′为解密后的信息,保证1≤x′,y′≤n,且k′≤C,C表示x,y路径上的节点数(包含x,y两点)

输出格式

输出m行,每一行一个正整数,代表Pigeon询问的答案


tag

主席树

解题思路

裸的树上路径第k大,强制在线

首先要学一下主席树,然后每个节点从父节点延续建树即可(讲道理会了主席树就是板子题)

代码

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
    int f = 1; char ch = getchar();
    for (; ch < '0' || ch>'9'; ch = getchar())    if (ch == '-')    f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar())    x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
inline void print(int x) {
    if (x < 0)    putchar('-'), x = -x;
    if (x > 9)    print(x / 10);
    putchar(x % 10 + '0');
}
const int N = 2e5, M = 5e6;
int root[N + 10], V[N + 10], list[N + 10], T;
struct CT {    //主席树
    int ls[M + 10], rs[M + 10], cnt[M + 10], tot;
    void insert(int k, int& p, int l, int r, int v) {
        cnt[p = ++tot] = cnt[k] + 1;
        ls[p] = ls[k], rs[p] = rs[k];
        if (l == r)    return;
        int mid = (l + r) >> 1;
        if (v <= mid)    insert(ls[k], ls[p], l, mid, v);
        else    insert(rs[k], rs[p], mid + 1, r, v);
    }
    int query(int x, int y, int lca, int lca_f, int l, int r, int k) {
        if (l == r)    return l;
        int mid = (l + r) >> 1, Cnt = cnt[ls[x]] + cnt[ls[y]] - cnt[ls[lca]] - cnt[ls[lca_f]];
        if (k <= Cnt)    return query(ls[x], ls[y], ls[lca], ls[lca_f], l, mid, k);
        else    return query(rs[x], rs[y], rs[lca], rs[lca_f], mid + 1, r, k - Cnt);
    }
}ct;
struct HLD {    //重链剖分
    int pre[(N << 1) + 10], now[N + 10], child[(N << 1) + 10], tot;
    int Fa[N + 10], Heavy[N + 10], Size[N + 10], Deep[N + 10], Top[N + 10];
    void link(int x, int y) { pre[++tot] = now[x], now[x] = tot, child[tot] = y; }
    void connect(int x, int y) { link(x, y), link(y, x); }
    void Dfs(int x, int fa) {
        Deep[x] = Deep[Fa[x] = fa] + (Size[x] = 1);
        ct.insert(root[Fa[x]], root[x], 1, T, V[x]);
        for (int p = now[x]; p; p = pre[p]) {
            int son = child[p];
            if (son == Fa[x])    continue;
            Dfs(son, x), Size[x] += Size[son];
            if (!Heavy[x] || Size[x] > Size[Heavy[x]])
                Heavy[x] = son;
        }
    }
    void Chain(int x) {
        Top[x] = Heavy[Fa[x]] == x ? Top[Fa[x]] : x;
        if (Heavy[x])    Chain(Heavy[x]);
        for (int p = now[x]; p; p = pre[p]) {
            int son = child[p];
            if (son == Fa[x] || son == Heavy[x])    continue;
            Chain(son);
        }

    }
    int LCA(int x, int y) {
        while (Top[x] != Top[y]) {
            if (Deep[Top[x]] < Deep[Top[y]])
                swap(x, y);
            x = Fa[Top[x]];
        }
        return Deep[x] < Deep[y] ? x : y;
    }
}hld;
int main() {
    int n = read(0), m = read(0);
    for (int i = 1; i <= n; i++)    V[i] = list[i] = read(0);
    sort(list + 1, list + 1 + n);
    T = unique(list + 1, list + 1 + n) - list - 1;
    for (int i = 1; i <= n; i++)    V[i] = lower_bound(list + 1, list + 1 + T, V[i]) - list;
    for (int i = 1; i < n; i++) {
        int x = read(0), y = read(0);
        hld.connect(x, y);
    }
    hld.Dfs(1, 0), hld.Chain(1);
    int LastAns = 0;
    for (int i = 1; i <= m; i++) {
        int x = read(0) ^ LastAns, y = read(0) ^ LastAns, k = read(0) ^ LastAns;
        int LCA = hld.LCA(x, y);
        printf("%d\n", LastAns = list[ct.query(root[x], root[y], root[LCA], root[hld.Fa[LCA]], 1, T, k)]);
    }
    return 0;
}

F-数数

题目描述

Wolfycz正在和Pigeon探讨一个数学问题,Wolfycz写下了n个数,Pigeon给定了一个操作:每次选择任意两个数x,y,记两者乘积为a,两者和为b,然后在这n个数中抹去x,y,同时写下一个新的数a+b

Wolfycz和Pigeon想知道,按何种顺序对这n个数进行操作,可以使最后留下的数最大?Wolfycz只要花点时间就可以做出来了,但是他花时间干别的事去了……临走前他找到了你,想让你给出这个问题的答案(答案可能会很大,请输出答案对998244353取模后的结果)

输入格式

第一行输入一个整数n(2≤n≤100),代表数的个数

第二行n个整数xi(1≤xi≤100),代表这n个数

输出格式

输出一个整数,代表最后留下的数的最大值


tag

思维

解题思路

实际上操作顺序对于结果是没有任何影响的,所以可以直接模拟

至于为什么,每次操作都是选取x,y,将其合并为xy+(x+y)=(x+1)(y+1)-1,在将其和z合并,得到的结果就是(x+1)(y+1)(z+1)-1,可以发现顺序是不会影响答案的

所以最后答案为,注意取模,别爆int就行

代码

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define pci pair<char,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
    int f = 1; char ch = getchar();
    for (; ch < '0' || ch>'9'; ch = getchar())    if (ch == '-')    f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar())    x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
inline void print(int x) {
    if (x < 0)    putchar('-'), x = -x;
    if (x > 9)    print(x / 10);
    putchar(x % 10 + '0');
}
const int P = 998244353;
int main() {
    int n = read(0), Ans = 1;
    for (int i = 1; i <= n; i++)
        Ans = 1ll * Ans * (read(0) + 1) % P;
    Ans = (Ans - 1 + P) % P;
    printf("%d\n", Ans);
    return 0;
}

G-明明的金币

题目描述

在 233233 年的魔法镇之旅中,明明收集了很多(大于 234234个)枚金币。为了保护这些硬币,明明对存放这些金币的宝箱施展了一种奇怪的魔法,只有能解开法阵谜题的人,才有资格取走这些金币。

法阵的谜题是一个由 2 名玩家组成的游戏。在游戏中,有 n 枚硬币排成一行,初始时分别为正面或者背面。探险家与法阵镜像轮流进行如下操作(探险家先行):

  • 在法阵中找到一个正面朝上的金币,记该硬币从左至右从 1 开始编号的位置为 x。

  • 执行该步骤的玩家可以选择是否在该硬币的左边选择一个位置的金币(该金币无需满足正面朝上的条件),记从左至右从 1 开始编号的该位置为 y(y<x),将这两枚金币同时翻面。

  • 如果玩家不选择第二枚硬币,那么只将第一枚金币翻面。

如果在一名玩家的回合开始时所有金币都为背面,那么该名玩家将直接输掉游戏。

可以证明,在任意一种给定初始金币条件的情况下,游戏在 233233 回合内必然会结束。为了取走这些金币,请问对于法阵给出的初始金币情况下,是否存在先手必胜的策略,使得无论法阵如何操作,都能获得胜利?

输入格式

在第一行中有一个正整数 t(1≤t≤10^5),表示测试用例的组数。

随后有 t 组测试用例,对于每一个测试用例有 2 行,第一行为一个正整数 n(1≤n≤5×10^5),表示金币的数量,第二行有一个长度为 n 的字符串,表示这 n 枚金币的具体正反面情况。

我们用 'F'(front) 表示初始时该金币为正面,'B'(back) 表示初始时改金币为反面。

数据保证测试用例中 n 的总和不超过 10^6。

输出格式

对于每一组测试用例,如果不存在先手必胜策略,输出一行一个字符串 'NO'(不含引号,下同)。

否则,输出两行,第一行为 'YES'。下面请继续输出一种第一回合的获胜方式:

  • 如果第一回合需要翻两枚硬币,第二行输出一种先手的第一步操作 x,y(1≤y<x≤n)

  • 如果第一回合只翻一枚硬币,输出 x,y(1≤x≤n,y=−1)。

表示第一回合的操作,使得在这之后无论法阵如何操作,法阵将必输。如果答案不唯一,你可以输出任意一种。


大致题意

有一行硬币,每次只能翻一枚背面朝上的硬币并选择是否额外翻一枚它左边的硬币,询问是否先手必胜

并给出策略。

tag

nim博弈

解题思路

nim板子题。

考虑每一枚正面朝上的硬币,均可看成一堆个数为其位置的硬币。每次只翻一个等价于取走这一堆的所有硬币,翻两个等价于将这一堆硬币变成更少的一堆,因此符合Nim博弈的模型,考虑亦或和即可判断是否有解。

设x为所有正面朝上硬币位置的亦或和,易证存在一个位置i(1 ≤ i ≤ n),使得ai ⊕ x ≤ ai,因此枚举即可。

时间复杂度:O(n)

空间复杂度:O(1)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    ll t,n,i,j,k,x,zc,a,b;
    string sr;
    scanf("%lld",&t);
    while(t--){
        scanf("%lld",&n),sr.resize(n),scanf("%s",&sr[0]);
        for(i=x=0;i<=n-1;i++) if(sr[i]=='F') x=(x^(i+1));
        if(x==0) puts("NO");
        else{
            puts("YES");
            for(i=0;i<=n-1;i++){
                if(sr[i]=='F'){
                    zc=i+1;
                    if((zc^x)<=zc){
                        a=zc,b=zc^x;
                        break;        
                    }
                }
            }
            if(b==0) printf("%lld -1\n",a);
            else printf("%lld %lld\n",a,b);
        }
    }
    return 0;
}

H-Carry爱喝药

题目描述

Carry玩游戏一股脑的买了一大堆不知名药水共n瓶,第i(1≤i≤n)瓶药水有一个功效a[i],为一个正整数,有以下两种情况:

  • 如果a[i]≥0,那么Carry的血量会增加a[i]

  • 如果a[i]<0,那么Carry的血量会减少a[i]

现在他决定从第一瓶开始,依次决定每一瓶药水喝还是不喝,最开始Carry的血量是0,他希望喝的药水瓶数最多并且不会中途挂掉(喝完任意一瓶药水的时候血量都大于等于0)

可惜比较蠢的Carry面对这么多连续的药水,他不知道哪些该喝还是不喝,请你帮他计算一下最后最多能喝多少瓶,并且途中与最后没有挂掉(血量小于0)。

输入格式

第一行一个正整数n(1≤n≤10^5),表示Carry面前的连续n瓶药水。

第二行n个整数,表示第i瓶药水的功效a[i](∣a[i]∣≤2×10^6).

输出格式

一个整数,表示Carry最多能够喝多少药水并且没有挂掉。不要求输出具体喝了哪些药水。


tag

贪心,优先队列

解题思路

按顺序考虑每瓶药,如果能喝就喝下去,同时记录一个已喝药的序列,如果对于药水i,Carry喝了它之后会挂掉,那么可以考虑从已喝药的序列中找到一个最小的(同时满足这个最小的药水值比当前药水更小),进行替换操作,这样可以使Carry在之后有更多的操作空间

至于怎么找最小的,用优先队列即可

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
typedef long long ll;
int a[maxn];
priority_queue<int,vector<int>,greater<int> >pq;
int main(){
    int n;
    cin>>n;
    ll cur=0,ans=0;
    for(int i = 1; i <= n; i++){
        int x;
        cin>>x;
        if(cur+x<0){
            if(pq.size()&&pq.top()<x&&cur-pq.top()+x>=0){
                cur=cur-pq.top()+x;
                pq.pop();pq.push(x);
            }
        }
        else{
            ans++;cur+=x;pq.push(x);
        }
    }
    cout<< ans <<'\n';
    return 0;
}

I-数汪汪墙

题目描述

来自喵喵国的猫猫Snoopy 以及来自汪汪国的小狗 Kitty 在某个地方抓到了很多木块。它们开始通过将立方体一个放在另一个上来制作立方体塔。它们将排成一排的多座塔定义为一堵墙,一堵墙可以由不同高度的塔组成。

Kitty是第一个完成它的墙的人。它称它的墙为汪汪墙,这堵墙由 m 座塔组成。不久后 Snoopy 也完成了他的墙,它的墙由 n 座塔组成,但他没有给这座墙命名。Kitty 看着 Snoppy 搭好的墙,想知道:它可以在后者墙上的多少个部分中看到汪汪墙?

如果Snoopy墙中任一长度为 m 的塔的高度能与汪汪墙中塔的高度匹配,那么Kitty就可以在 m 连续塔的一段上看到一堵汪汪墙。为了尽可能多地看到汪汪墙,贺拉斯可以升降它的墙。它甚至可以将墙降低到地面以下(参考样例解释)。

你的任务是计算 Kitty 在Snoopy 搭好的墙中可以看到汪汪墙的个数

输入格式

第一行包含两个整数 n 和 m(1≤n,m≤10^6) ,对应Snoppy和Kitty各自搭建墙的长度。

第二行包含 n 个整数 ai(1≤ai≤10^9) ——Snoppy搭建好的墙中每座塔的高度

第三行包含 m 个整数 bi(1≤bi≤10^9) ——Kitty搭建好的墙中每座塔的高度

输出格式

在一行中输出一个整数,表示Snoopy搭好的墙中能看到汪汪墙的个数。


大致题意

给出a序列和b序列,求a序列中b序列的个数

tag

KMP算法

字符串哈希

解题思路

对山峰来说,当山脉中有一段与山峰形状相似时,那么连续一段长度为 m 的山脉中前后两个数之差与山峰中前后两个数之差相等

  1. m = 1时输出n ,m > n时输出0

  1. 对于每个bi(i < m)和ai(i < n),令

  1. 用KMP算法求出处理后b序列的next数组,在处理后a序列中计算出处理后b序列的个数并输出即可

时间复杂度:O(n + m)

空间复杂度:O(n + m)

代码

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n, m;
    cin >> n >> m;
    vector<int> a(n), b(m);
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int i = 0; i < m; i++) cin >> b[i];
    //特判 m == 1 和 m > n
    if(m == 1){
        cout << n << endl;
        return 0;
    }
    if(m > n){
        cout << 0 << endl;
        return 0;
    }
    //对a,b序列进行处理
    for(int i = 0; i < n - 1; i++) a[i] = a[i + 1] - a[i];
    for(int i = 0; i < m; i++) b[i] = b[i + 1] - b[i];
    //求出处理后b序列的next数组
    vector<int> nxt(m - 1);
    for(int i = 1, j = 0; i < m - 1; i++){
        while(j > 0 && b[i] != b[j]) j = nxt[j - 1];
        nxt[i] = (b[i] == b[j] ? ++ j : j);
    }
    int ans = 0;
    //在a序列中对b序列进行匹配,计算出个数
    for(int i = 0, j = 0; i < n - 1; i++){
        while(j > 0 && a[i] != b[j]) j = nxt[j - 1];
        if(a[i] == b[j]) j++;
        if(j == m - 1) ans++;
    }
    cout << ans << endl;
    return 0;
}

J 明明的苹果树

题目描述

在 233233 年的魔法镇之旅中,明明收集了很多(大于 234234个)金苹果银苹果和红苹果。为了保护这些苹果,明明对存放这些苹果的宝箱施展了一种奇怪的魔法,只有能解开法阵谜题的人,才有资格取走这些苹果。

这个法阵具有特殊的规则,形式上,可以看作由 n 个玩家组成的游戏,游戏有 m 回合,在每一回合中,每一个玩家可以秘密向裁判声明一种类型的苹果(金苹果,银苹果,红苹果中之一),在每一个玩家独立做出选择后,裁判公布每一位玩家的选择,并决定选取某一种苹果的玩家获胜。具体的获胜规则如下:

  • 如果这是第 1 回合:

  • 如果存在唯一一种选取人数最小的苹果种类(请注意,最小选取人数可以为 0),那么声明本回合的获胜苹果为该苹果(该苹果种类也可无人选择)。

  • 如果不存在唯一一种选取人数最小的苹果种类(请注意,最小选取人数可以为 0),那么此时所有选取金苹果的玩家获胜(该苹果种类也可无人选择)。

  • 否则:

  • 如果所有在上一回合选择红苹果的玩家,在这一回合依旧全部选择红苹果(如果上一回合无人选择红苹果也算),那么这一轮选取红苹果的玩家直接获胜。

  • 在上一条不成立的情况下,如果选取银苹果的玩家数量(可以为 0)严格小于选取金苹果的玩家数量,那么这一轮选取银苹果的玩家直接获胜。

  • 在上两条均不成立的情况下,那么这一轮选取金苹果的玩家直接获胜。

例如:

  • 在第一回合中,假设有 10 名玩家,有 5 人选取红苹果,3 人选取金苹果,2 人选取银苹果,那么此时所有选取银苹果的玩家获胜(共 2人)。

  • 在第一回合中,假设有 10 名玩家,有 6 人选取红苹果,无人选取金苹果,有 4 人选取银苹果,那么此时所有选取金苹果的玩家获胜(共 0 人)。

  • 在第一回合中,假设有 10 名玩家,有 10 人选取红苹果,无人选取金苹果,无人选取银苹果,那么此时所有选取金苹果的玩家获胜(共 0 人)。

这个游戏对于玩家选取金苹果的次数具有限制,每名玩家在整局游戏中至多选取 k 次金苹果,但银苹果和红苹果的选取次数没有限制。

为了破解这个法阵,由 n−1 个人所组成的探险家团队(没错,就是你们)和法阵所组成的虚拟镜像一共 n 个玩家来到了法阵的游戏中,已知你们可以预先商量对策,请问你们是否能使得法阵无论如何选择,让法阵永远无法在一局游戏的任一回合中获胜?

形式上,对于上述游戏,请问你是否能分别构造出 n−1 个玩家的选择,使得在上述规则下:

  • 无论玩家 1 如何选择,玩家 2−n 的预先确定的一种选择方式可以使得玩家 1 无论如何都无法获得任一回合的胜利。

输入格式

在一行中有三个以空格相间隔的正整数 n,m,k(2≤n≤10^5 , 5≤m≤50 , 1≤k≤m−1),分别表示题目中所描述的数值。

输出格式

如果符合条件的选择存在,在一行中输出 "YES"(不含引号),否则,输出 "NO"(不含引号)。

如果答案存在,我们记金苹果为字母 'G',银苹果为字母 'S',红苹果为字母 'R',请你输出对应的一组 n−1 行每行长度为 m 的字符序列,使得上述条件成立。请注意,每一个序列中 'G' 的数量应当不大于 k。

如果答案不唯一,你可以输出任意一种满足上述格式要求的字符序列,你可以阅读样例输出以获得对此格式的更好理解。


大致题意

如果在第一回合存在最小选择人数的苹果种类,那么对应选择苹果种类的人胜出,否则金苹果胜。

如果上一轮选红苹果这一轮仍选红苹果,则红苹果胜;若银苹果人数小于金苹果,则银苹果胜;否则金苹果胜。

求玩家2-n的一种选择方式使得玩家1无论如何都无法在任一回合胜利。

tag

构造

解题思路

若n = 2或k(n − 1) + 1 < m,则无解。

简单证明: n = 2无解显然。若k(n − 1) + 1 < m,则必然存在2 − m回合中某一回合玩家2 − n均未 选择金苹果。那么: 若此时2 − n玩家中上一轮选取红苹果的玩家中选取银苹果玩家的数量为0,那么此时玩家1 在这一回合和上一回合全选红苹果即可获得胜利。 若此时2 − n玩家中上一轮选取红苹果的玩家中选取银苹果玩家的数量不为0,那么此时玩家1在这一回合选取金苹果即可获得胜利。

具体构造方式(方式不唯一):

第一回合全R即可。

在之后的每一回合,只要银苹果数量为金苹果数量-1,或者正好等于金苹果数量即可,同时需要满足上一轮某个选择红苹果的玩家这一轮没有选择红苹果。

其实可以不用选银苹果,后者每一轮均挑一个上一轮选取红苹果的玩家选取金苹果即可。

时间复杂度:O(nm)

空间复杂度:O(1)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    ll n,m,k,ii,i,j,T,dq;
    scanf("%lld%lld%lld",&n,&m,&k);
    if(n==2 or k*(n-1)+1<m) puts("NO");
    else{
        puts("YES");
        for(i=0;i<=n-2;i++){
            printf("R");
            for(j=0;j<=m-2;j++) printf("%c",(j-i+(n-1))%(n-1)==0?'G':'R');
            puts("");
        }    
    }
    return 0;
}

K-国奖爷降临到你身边

题目描述

作为一名见义忘利的国奖爷,在你想不出题目时,只需要在心里默念国奖爷,他就会迅速地降临到你身边,和你一起想不出这一题。然而,由于论文怪的阻挠,国奖爷手里还有一堆论文要写,他需要先写完这些论文才能来。

国奖爷手里有n份待写的论文,第i份论文耗时wi分钟。由于国奖爷有两只手,所以他可以左右开弓,同时写两份论文。在国奖爷写完所有的论文后,他仅需一分钟就能降临到你身边。

现在国奖爷正享受着双倍的水论文乐趣,他不想耽误任何一点时间。那他需要多久以后才能帮帮你呢?

输入格式

第一行一个整数n,表示待写的论文的数量。

接下来n行,每行一个整数w,第i行的w表示写完这份论文需要耗时wi分钟。

保证n≤1000,w的总和不超过5000。

输出格式

输出一个整数,表示国奖爷最快多久以后就可以降临到你身边。


tag

背包

解题思路

对所有任务进行01背包,记F[i]表示能否使用某些任务凑出时间i,最后记总任务时长为W,那么我们只需要找到一个最大的x,满足,并且F[x]=1,答案即为W-x+1。

代码

#include <iostream>
#include<cstring>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
using namespace std;
#define LL long long 
bool f[5010];
void solve() {
    int n,sum=0;
    cin >> n;
    f[0] = 1;
    for (int i = 1; i <= n; ++i) {
        int w;
        cin >> w;
        sum += w;
        for (int j = 5000; j >=w; --j) {
            f[j] = f[j] | f[j - w];
        }
    }
    int k = sum / 2;
    for (; !f[k]; --k);
    cout << sum - k + 1;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t;
    _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

L-集合!动物森友会!

题目描述

小K在玩动物森友会,作为一个钓鱼爱好者,他想获得钓鱼比赛的冠军!在他面前是一片湖面,湖面以大小为

n x m 的二进制矩阵 grid进行表示。grid中0表示湖水,1表示该位置有 1 条鱼。

恰好,他有一把神奇的鱼竿,这把鱼竿可以在任意位置开始抛竿,并且一次性钓到与该位置连通的整个鱼群,这里的「连通」要求鱼群中的 鱼 必须在 水平或者竖直的四个方向上 相邻。

他想问问你,在单次垂钓中,最多能钓到的鱼的数量。如果图上没有任何鱼,请输出0.

输入格式

第一行有两个正整数 n和 m(1≤n,m≤1000),代表该湖面是一个 n x m 的矩阵。

后面的 n 行每行有 m 个整数,每个数字为 0 或1,1 代表这里有1条鱼,0 代表是湖水。

输出格式

一个正整数,表示单次垂钓中能获得的最多的鱼的数目。


大致题意

求出grid中的最大1联通块,如果没有,则返回0。

tag

dfs

bfs

解题思路

1. 开二维vector数组对grid进行存储。

2. 对grid数组进行深度优先搜索或者广度优先搜索并更新答案。

时间复杂度:O(nm)

空间复杂度:O(nm)

代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
bool g[1000][1000],vis[1000][1000];
int lt[1000001],num=0;
 int n,m;
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
void dfs(int x,int y){
    vis[x][y]=1;
    for(int i=0;i<4;++i){
        if(x+dx[i]>=0&&x+dx[i]<n&&y+dy[i]>=0&&y+dy[i]<m&&!vis[x+dx[i]][y+dy[i]]&&g[x+dx[i]][y+dy[i]]){
            dfs(x+dx[i],y+dy[i]);
            ++lt[num];
        } 
    }
}
int main(){
   
    cin>>n>>m;
    memset(vis,0,sizeof(vis));
    memset(lt,0,sizeof(lt));
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            cin>>g[i][j];
        }
    }
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            if(!vis[i][j]&&g[i][j]==1) {
                ++num;
                lt[num]++;
                dfs(i,j);
                
            }
        }
    };
    if(lt[1]==0) cout<<"0";
   else cout<<*max_element(lt+1,lt+num);
    return 0;
}

M-并砖

题目描述

工地上有n堆砖,每堆砖的块数分别是mi,m2,...,mn,每块砖的重量都为1,现要将这些砖通过n-1次的合并(每次把两堆砖并到一起),最终合成一堆。若将两堆砖合并到一起消耗的体力等于两堆砖的重量之和,请设计最优的合并次序方案,使消耗的体力最小。

输入格式:

测试数据有多组,处理到文件尾。每组测试先输入一个整数n(1≤n≤100),表示砖的堆数;然后输入n个整数,分别表示各堆砖的块数。

输出格式:

对于每组测试,在一行上输出采用最优的合并次序方案后体力消耗的最小值。


tag

贪心

解题思路

每次贪心的选取两堆最小的合并即可,可以每次暴力排序,也可以用优先队列

代码

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<sstream>
using namespace std;
int a[101];
int main(){
    string s;
    while(getline(cin,s)){
        long long sum=0;
        memset(a,0,sizeof(a));
   stringstream ss(s);
        int n;
        ss>>n;
        for(int i=1;i<=n;++i){
            ss>>a[i];
        }
        sort(a+1,a+n+1);
        for(int i=1;i<n;++i){
            a[i+1]=a[i]+a[i+1];
            sum+=a[i+1];
            sort(a+i+1,a+n+1);
        }
        
         cout<<sum<<endl;
    }
    return 0;
}

网络赛

传送门

F-Carry爱操作数字

题目描述

给定一个正整数 n,我们希望你可以通过一系列的操作,将其变为另一个正整数 m。操作共分两种:

  1. 将当前的数乘以 2。

  1. 将当前的数减去 1。

要求,在变换过程中,数字始终为正。请你计算所需要的最少操作次数。

输入格式

第一行一个正整数T(1≤T≤5),代表有T组测试数据

接下来T行,每一行有两个不同的正整数 n,m(1≤n,m≤10000)。

输出格式

一个整数,表示所需的最少操作次数。


tag

  • 方法一:BFS(广度优先搜索)

  • 方法二:贪心

解题思路

  • 方法一(BFS)

用广搜的思想,首先将(n,0)放入队列,队列中每个元素有两个值,第一个表示当前数字,第二个表示从开始到达当前数字所需要的次数,例如(n,0)就是就是从n到达n需要0步。同时控制边界即可,需要保证每个数字只进队列一次,相同数字最早进入队列一定是最优的,不需要重复进入浪费时间。最后只要某一个数字达到m即最优。

  • 方法二(贪心)

贪心的思想,从 开始往m靠近n,如果是m<n那么需要一个个减(等于操作二,一个个加)。如果是奇数,那么需要减一(操作二);如果是偶数,那么需要除以二(操作一)

代码

#include <bits/stdc++.h>
#pragma GCC optimize(2)
#pragma G++ optimize(2)
#define endl "\n"
#define all(x) x.begin(), x.end()
#define rep(i, x, y) for (auto i = x; i <= y; ++i)
#define dep(i, x, y) for (auto i = x; i >= y; --i)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
void test_case() {
    int n, m;
    cin >> n >> m;
    int res = 0;
    while (m != n) {
        if (m > n && m % 2 == 0) m /= 2;
        else ++m;
        ++res;
    }
    cout << res << endl;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int _;
    cin >> _;
    for (int i = 1; i <= _; ++i) test_case();
    return 0;
}

G-十一旅游规划

题目描述

众所周知,国庆有7天假期,小K想乘着这段时间出校旅游。小K的目标清单上有N个景区,景区之间有一些双向的路来连接,现在小K想找到一条旅游路线,这个路线从地大出发并且最后回到地大(标记为1)。除了出发点(地大)以外必须至少经过2个景区,而且不能重复经过同一景区(地大必须开头、结束各经过一次),即路线可以表示为V1,Vi,……,Vj,V1 。

小K不太擅长路线规划,现在需要你帮他找到这样的路线,并且花费越少越好。

输入格式

第一行是2个整数N和M(1≤n≤100,1≤m≤1000),代表景区的个数和道路的条数。 接下来的M行里,每行包括3个整数a,b,c.代表a和b之间有一条通路,并且需要花费c元(1≤c≤100)。

输出格式

对于每个测试实例,如果能找到这样一条路线的话,输出花费的最小值。如果找不到的话,输出"No solution.".


tag

floyd

解题思路

删去点1,跑Floyd获取图上任意两点间的距离

枚举两点i,j,求min(dist[1][i]+dist[1][j]+dist[i][j])即为答案。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll dist[110][110];
ll n, m;
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++){
            if (i == j) dist[i][j] = 0;
            else dist[i][j] = 0x3f3f3f3f;
        }
    }
    for (int i = 1; i <= m; i++){
        ll x, y, z;
        cin >> x >> y >> z;
        dist[x][y] = min(dist[x][y], z);
        dist[y][x] = dist[x][y];
    }
    for (int k = 2; k <= n; k++){
        for (int i = 2; i <= n; i++){
            for (int j = 2; j <= n; j++){
                dist[i][j] = min(dist[i][k] + dist[k][j], dist[i][j]);
                dist[j][i] = dist[i][j];
            }
        }
    }
    ll minn = 0x3f3f3f3f;
    for (int i = 2; i <= n; i++){
        for (int j = 2; j <= n; j++){
            if (i == j) continue;
            minn = min(dist[1][i] + dist[1][j] + dist[i][j], minn);
        }
    }
    if (minn == 0x3f3f3f3f){
        cout << "No solution." << endl;
        return 0;
    }
    cout << minn << endl;
    return 0;
}

L-明明的宝石

题目描述

在 233233 年的魔法镇之旅中,明明屯下了很多宝石,为了将这些宝石组成美轮美奂的艺术品,明明打算将这些宝石中挑出一部分。不过由于明明的奇怪的要求,在挑选宝石时,他提出了如下要求:

  • 钻石:由于明明只有 1 个,因此要么拿出一个,要么不拿。

  • 绿宝石:由于明明只有 1 个,因此要么拿出一个,要么不拿。

  • 紫水晶:由于明明只有 2 个,因此明明至多只能拿出两个(也可以不拿)。

  • 青金石:由于明明只有 3 个,因此明明至多只能拿出三个(也可以不拿)。

  • 红石:由于明明只有 3 个,因此明明至多只能拿出三个(也可以不拿)。

  • 金锭:明明只打算挑出偶数个(包括 0 个)。

  • 石英:明明只打算挑出奇数个(不包括 0 个)。

  • 铁锭:明明只打算挑出模 3 余 1 数个(不包括 0 个)。

  • 铜锭:明明只打算挑出模 4 余 1 数个(不包括 0 个)。

  • 苹果(众所周知,苹果是一种宝石):明明只打算挑出模 4 余 3 数个(不包括 0 个)。

现在这些东西(如果没有说明,假装明明有无限个)都杂乱的放在明明的背包里,明明的问题是,如果明明现在想从中取出 n 个,请问一共有多少种不同的方案?

在本题中,两种方案被视为不相同的,当且仅当他们存在至少一种物品,其取出的数量不相同。由于答案可能过大,明明只想知道答案取模 998244353 的结果数值。

输入格式

在一行中输入一个正整数 n(10≤n≤10^18),表示需要取出的物品数目。

输出格式

输出一个正整数,为所求的方案数。


tag

生成函数

解题思路

考虑构造生成函数,我们记多项式函数的次数表示某种宝石取若干个,系数表示某种宝石取若干个的方案数,有:

钻石:由于明明只有1个,因此要么拿出一个,要么不拿。

绿宝石:由于明明只有1个,因此要么拿出一个,要么不拿。

上述生成函数为:

紫水晶:由于明明只有2个,因此明明至多只能拿出两个(也可以不拿)。

上述生成函数为:

青金石:由于明明只有3个,因此明明至多只能拿出三个(也可以不拿)。

红石:由于明明只有3个,因此明明至多只能拿出三个(也可以不拿)。

上述生成函数为:

金锭:明明只打算挑出偶数个(包括0个)。

上述生成函数为:

石英:明明只打算挑出奇数个(不包括0个)。

上述生成函数为:

铁锭:明明只打算挑出模3余1数个(不包括0个)。

上述生成函数为:

铜锭:明明只打算挑出模4余1数个(不包括0个)。

上述生成函数为:

苹果(众所周知,苹果是一种宝石):明明只打算挑出模4余3数个(不包括0个)。

上述生成函数为:

将上述式子相乘后化简,由二项展开可得

故答案即为:

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353;
ll qmksm(ll a,ll b,ll p){
    ll sc=1,zc=a%p;
    while(b>0){
        if(b%2==1) sc=(sc*zc)%p;
        b/=2,zc=(zc*zc)%p;
    }
    return sc;
}
ll ny(ll x){
    return qmksm(x,mod-2,mod);
}
ll mul(ll a,ll b){
    return (a%mod)*(b%mod)%mod;
}
int main(){
    ll n,ii,i,j,k,ans;
    string srwj,scwj;
    cin>>n;
    printf("%lld\n",mul(n-2,mul(n-3,mul(n-4,mul(n-5,mul(n-6,ny(24)))))));
    return 0;
}

热身赛

传送门

C-亚索学E

题目描述

众所周知,踏前斩是亚索的灵魂技能,对一名敌人使用踏前斩可以让亚索位移到该敌人的位置并对该敌人造成伤害

在某一局游戏中,YoungChigga正在使用他的亚索大杀四方

正当YoungChigga在lemon的高地上得瑟时,突然,他发现他的鼠标被自己摁坏了,无法进行攻击,也无法进行移动

幸运的是,他的E键没有坏,他仍旧可以利用他的踏前斩进行位移

如果一名敌人在他的攻击范围内,那么他可以使用他的踏前斩位移至该敌人的位置,但不能对同一个单位使用两次踏前斩

地图上仍有n个敌方小兵幸存,YoungChigga已经知道了这些小兵的坐标,并准备利用这些小兵进行移动

只要回到防御塔的攻击范围内,YoungChigga就能活下来,否则他将被lemon使用他的大宝剑击杀,并被无情嘲讽

我们假设亚索的施法范围和防御塔的攻击范围是一个

现在YoungChigga想知道他能否抵达防御塔的攻击范围内,如果不能,他想知道在位移过程中距离防御塔最近的距离是多少?

输入格式

第一行输入三个正整数sx,sy,r1 (1≤sx,sy≤10^4,1≤r1≤10^4) ,表示防御塔的位置sx,sy以及防御塔的攻击范围r1

第二行输入三个正整数fx,fy,r2 (1≤fx,fy≤10^4,1≤r2≤10^4) ,表示防御塔的位置fx,fy以及防御塔的攻击范围r2

第三行输入一个正整数n (1≤n≤10^3) ,表示有n个敌方小兵

接下来的n行输入两个数xi,yi (1≤xi,yi≤10^4) ,表示第i个小兵的坐标xi,yi

输出格式

若YoungChigga能够安全抵达防御塔的攻击范围内,则输出YES

否则输出位移过程中防御塔的最近距离(保留两位小数)


tag

并查集

解题思路

用并查集处理出所有与起点连通的节点并标记,然后遍历这些节点,用一个变量minn记录这些距离的最 小值,判断这些节点与防御塔的距离,如果小于r2说明可以到达,否则更新最小值minn,如果没有一个 节点可以到达防御塔内,则输出minn即可

代码

#include<bits/stdc++.h>
#define Inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef pair<int,int> P;
const int MAXX=100005;
struct player{
    int hp,tz,bx,pbx;
    player(){hp=tz=bx=pbx=0;}
}a[3];
int n,m,now;
inline void solve(){
    scanf("%d%d%d",&n,&m,&now);
    a[1].hp=a[2].hp=n;
    while(m--){
        int op;scanf("%d",&op);
        int bob=3-now;
        if(op==1){
            int damage=(a[bob].tz?1:6);
            if(a[bob].hp-damage<=0){
                if(a[bob].bx) a[bob].pbx=1;
                else a[bob].hp-=damage;
            }
            else a[bob].hp-=damage;
        }
        else if(op==2) a[now].tz=1;
        else if(op==3) a[now].bx=1;
        else{
            now=3-now;
            a[now].tz=0;
            if(a[now].pbx) a[now].bx=a[now].pbx=0;
        }
    }  
    if(a[1].hp<=0) printf("shaun cb!\n");
    else if(a[2].hp<=0) printf("shaun nb!\n");
    else printf("shaun fb!\n");
}
int main(){
    solve();
    return 0;
}

D-喵喵国的狂欢节

题目描述

喵喵国是一个喵喵的国度,这里有许多可爱的喵喵,还有许多城市。最近,喵喵国是一个享乐的国度,这个国家每年有 D 天,每天不同的城市都在举办不同主题的狂欢节,来自全国各个城市的喵喵都可以乘坐对应时空隧道从他们所在的城市到达当天举办狂欢节的城市进行玩耍。

现在喵喵国有 n 个城市,编号为 1−n ,城市之间一共有 m 条双向时空隧道,每条隧道能够让喵喵从编号为 u 的城市传送到编号为 v 的城市,也能让喵喵从编号为 v 的城市传送到编号为 u 的城市,在城市 s 的喵喵如果想参加城市 t 举办的狂欢节,可以从城市 s 开始出发通过多次乘坐不同的隧道进行中转最后到达城市 t ,最后到达城市。天有不测之风云,喵喵们的天敌——炼金战士每天都会从虚空中出现,并随机占领一个未被占领的城市,炼金战士在占领了一个城市后,会封锁与当前城市相连的所有时空隧道,时空隧道被封锁后,喵喵们将无法使用隧道通行。

喵喵国是一个娱乐至死的国度,即使有炼金战士的威胁,也无法阻挡喵喵们参加狂欢节的热情,他们早在每年到来之前就规划好了自己下一年参加狂欢节的行程。但炼金战士带来的影响却不能忽视,于是喵喵找到了你,想请你帮忙计算在炼金战士的影响下他们每天还能正常完成的行程有多少。

输入格式

第一行包含三个整数 n(1≤n≤5×10^4),m(1≤m≤4×10^5),D(1≤D≤10^3),分别表示城市数量,时空隧道的数量,以及每年有 D 天。

接下来有 M 行,每行给出由空格分隔的两个数字 ui,vi(1≤ui,vi≤n , ui≠vi) ,表示编号为 ui 和 vi 的城市之间有一条双向时空隧道可供通行(保证无自环和重边)。

然后是 D 块输入,每块的第一行为由空格分隔的两个整数 C 和 Q(1≤Q≤10^3) ,表示当天炼金战士新占领的城市 C 和今天喵喵们的行程有 Q 条,保证炼金战士新占领的城市为之前未被占领的城市。

接下来是 Q 行,每行有由空格分隔的两个数字 X 和 Y ,表示当天有一个喵喵原本要从城市 X 去参加城市 Y 的狂欢节,X,Y 中有可能包含之前就被炼金战士占领的城市。

输出格式

对于每天的询问,你需要在一行中输出在当天城市 C 被炼金战士占领后,仍有多少喵喵的行程能够正常完成。


tag

并查集

解题思路

这是一个查询图中任意两点连通性的问题,我们很容易就能想到可以用并查集的方法来解决,但是如果 对于每一天,我们都对当前未被标记的点之间的边使用并查集的话,时间复杂度是O((m+q)*d), 这种做法的数量级显然不满足题目的复杂度要求。 在这里我们可以考虑将所有标记点、删边的操作离线,逆序处理,即先将所有要删除的点删除,再倒着 往里加点,顺便将询问处理好,最后再顺着输出答案即可,这里我们可以使用并查集来维护这些操作。

代码

#include<bits/stdc++.h>
using namespace std;
// 并查集
struct DSU{
    vector<int> fa, siz;
    DSU(int n): fa(n), siz(n, 1) { iota(fa.begin(), fa.end(), 0); }
    int find(int x){
        while(x != fa[x]) x = fa[x] = fa[fa[x]];
        return x;
    }
    bool same(int x, int y){
        return find(x) == find(y);
    }
    bool merge(int x, int y){
        x = find(x);
        y = find(y);
        if(x == y) return false;
        siz[x] += siz[y];
        fa[y] = x;
        return true;
    }
    int size(int x){
        return siz[find(x)];
    }
};
int main(){
    int n, m, d;
    cin >> n >> m >> d;
    vector<vector<int>> g(n + 1);
    for(int i = 0; i < m; i++){
        int u, v;
        cin >> u >> v;
        g[u].emplace_back(v);
        g[v].emplace_back(u);
    }
    vector<int> vis(n + 1, 0), c(d), q(d);
    vector<vector<pair<int,int> > > plan(d);
    for(int i = 0; i < d; i++){
        cin >> c[i] >> q[i];
        vis[c[i]] = 1;
        for(int j = 0; j < q[i]; j++){
            int u, v;
            cin >> u >> v;
            plan[i].emplace_back(u, v);
        }
    }
    //处理没有被标记的点,加入到并查集中
    DSU dsu(n + 1);
    for(int i = 1; i <= n; i++){
        if(!vis[i])
            for(int x : g[i])
                if(!vis[x])
                    dsu.merge(i, x);
    }
    //反向遍历每个操作,在处理好当前询问后将点c加入及其有效边加入到并查集中
    vector<int> ans(d);
    for(int i = d - 1; i >= 0; i--){
        int cnt = 0;
        for(auto &[u, v] : plan[i]){
            if(dsu.same(u, v)) cnt++;
        }
        ans[i] = cnt;
        vis[c[i]] = 0;
        for(int x : g[c[i]]){
            if(!vis[x]) dsu.merge(x, c[i]);
        }
    }
    //顺着输出答案
    for(int i = 0; i < d; i++) cout << ans[i] << "\n";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值