CF1028 ABCD

A. Gellyfish and Tricolor Pansy


原题链接

题意简述

\(A\)\(B\) 玩游戏,\(A\) 有一个守卫 \(C\) ,\(B\) 有一个守卫 \(D\) , \(A\) ,\(B\) ,\(C\) ,\(D\) 的血量 为 \(a\) , \(b\), \(c\), \(d\),当玩家死亡时玩家失败,当守卫死亡时,玩家丧失攻击能力。当护卫存在时,玩家可以选择对对手护卫或玩家攻击使其血量 -1 .A先进行攻击,求谁获胜

解题思路

显然,雨露均沾式的攻击一定是不优的,优先攻击敌方软肋,使得他的守卫丧失攻击或者敌方直接死亡。那只要比较双方单位血量较小的谁更大即可。

AC code

void solve(){
    int a,b,c,d;
    cin>>a>>b>>c>>d;
    int x=min(a,c),y=min(b,d);
    cout<<(x>=y?"Gellyfish":"Flower")<<endl;
}

B.Gellyfish and Baby's Breath


原题链接

题意简述

给你两长度均为 \(n\) 的排列 \(p\)\(q\),给定一个 \(r\)数组,
对于每一个 \(i\) (\(0 \leq i \leq n-1\)), \(r_i = \max\limits_{j=0}^{i} \left(2^{p_j} + 2^{q_{i-j}} \right)\)
由于 \(r\) 的元素非常多,所以你只需要输出 \(r\) modulo \(998\\,244\\,353\) 的元素。
要求计算每一位 r 的值输出

解题思路

考虑 \(r_i = \max\limits_{j=0}^{i} \left(2^{p_j} + 2^{q_{i-j}} \right)\),对于每一个 \(r_i\) 的值只有最大的一个 $2^{p_j} + 2^{q_{i-j}} $ 决定,而对于每一对$2^{p_j} + 2^{q_{i-j}} $,显然有函数的值大小由幂次较大的一项决定,当函数幂次相等时再比较余项的大小。利用单变量或前缀最值数组快速统计前缀最大值,然后我们可以预处理出排列中每一个值所在的位置,之后简单计算 \(p\) 枚举前 n 项 最值时 \(q\) 取的值以及 \(q\) 枚举前 n 项 最值时 \(p\) 取的值,比价统计最值即可。

AC code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll inf=1e18;
const ll mod=998244353;
ll q_pow(ll a,ll b){
    ll s=1;
    while(b){
        if(b&1){
            s=s*a%mod;
        }
        a=a*a%mod;
        b>>=1;
    }
    return s;
}
void solve(){
    int n;cin>>n;
    vector<ll>a(n+1),b(n+1);
    map<ll,int>mpA,mpB;
    for(int i=1;i<=n;i++) cin>>a[i],mpA[a[i]]=i;
    for(int i=1;i<=n;i++) cin>>b[i],mpB[b[i]]=i;
    vector<ll>ans(n+1);
    ll preA_max=0;
    ll preB_max=0;
    for(int i=1;i<=n;i++){
        preA_max=max(preA_max,a[i]);
        preB_max=max(preB_max,b[i]);
        if(preA_max>preB_max) 
            ans[i]=(q_pow(2LL,preA_max)+q_pow(2LL,b[i-mpA[preA_max]+1]))%mod;
        else if(preB_max>preA_max) 
            ans[i]=(q_pow(2LL,preB_max)+q_pow(2LL,a[i-mpB[preB_max]+1]))%mod;
        else if(b[i-mpA[preA_max]+1]>a[i-mpB[preB_max]+1]) 
            ans[i]=(q_pow(2LL,preA_max)+q_pow(2LL,b[i-mpA[preA_max]+1]))%mod;
        else 
            ans[i]=(q_pow(2LL,preB_max)+q_pow(2LL,a[i-mpB[preB_max]+1]))%mod;
    }
    for(int i=1;i<=n;i++) cout<<ans[i]<<" \n"[i==n];
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}

C.Gellyfish and Flaming Peony


原题链接

题意简述

给你一个长为 \(n\) 的正整数数组 \(a_1, a_2, \ldots, a_n\).
现在进行以下操作直到数组中每个数都相等 ,在数组中任意选择两个位置 让 \(a_i =gcd(a_i,a_j)\)求最小操作次数

解题思路1

首先考虑在进行操作到最后数组可能的状态是什么,一定是所有数都变成原数组的 \(gcd\),即\(gcd(a_1, a_2, \ldots, a_n)\)。令 \(g=gcd(a_1, a_2, \ldots, a_n)\). 然后考虑要至少进行多少次操作,可以发现对一个存在 \(g\) 的数组我们仅需要 \(n-cnt_g\) 次操作就可以使得每一个数都变成 \(g\),然后对于不存在 \(g\) 的数组我们需要考虑一种方法计算出通过当前数组所有数计算出现 \(g\) 的最小次数。考虑动态规划,规划 \(dp_{i,j}\) 枚举仅使用前 \(i\) 个数组元素,出现 \(j\) 值的最小操作次数 ,则有状态转移方程 \(dp_{i,j}=dp_{i-1,j},dp_{i,gcd(a[i],j)}=dp_{i-1,j}+1\) .
\(ps:std::gcd(a,b)\) 可以近似认为是O(1)的函数,优化的比 \(\_\_gcd(a,b)\) 好很多 ,本题也可以预处理 \(gcd[a][b]\) 数组表示 \(gcd(a,b)\) 的值避免大量重复的调用

AC code1

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll inf=1e18;
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    int base=a[1];
    for(int i=1;i<=n;i++) base=std::gcd(base,a[i]);
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(a[i]==base) ++cnt;
    }
    if(cnt) cout<<n-cnt<<endl;
    else{
        int maxl=*max_element(a.begin(),a.end());
        vector<vector<ll> >dp(n+1,vector<ll>(maxl+1,INT_MAX));
        //前i个数构造出base所需要的最小次数
        dp[0][0]=0;
        for(int i=1;i<=n;i++) dp[i][a[i]]=1;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=maxl;j++) dp[i][j]=dp[i-1][j];
            for(int j=0;j<=maxl;j++) dp[i][std::gcd(j,a[i])]=min(dp[i-1][j]+1,dp[i][std::gcd(j,a[i])]);
        }
        cout<<dp[n][base]+n-2<<endl;
    }
    
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}

解题思路2

观察 \(dp\) 状态转移式,发现第二维度 \(j\) 有很多根本不可能出现的值也设计在了状态内,这样很浪费时间,考虑优化模拟实现。这里使用 \(BFS\) 模拟枚举,对于每一次广搜贪心的选择 原序列 \(a_1,a_2,...,a_n\) 每一个值与广搜到的值 \(x\)\(gcd(x,a_i)\),同时采用 \(vis\) 数组统计当前广搜产生的\(gcd(x,a_i)\) 的时间是否比之前产生 \(gcd(x,a_i)\)要早,如果是才进行添加,这样可以优化大量重复枚举的状态。这样枚举的次数是严格小于 \(dp\) 做法的。

AC code2

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    int base=a[1];
    for(int i=1;i<=n;i++) base=std::gcd(base,a[i]);
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(a[i]==base) ++cnt;
    }
    if(cnt) cout<<n-cnt<<endl;
    else{
        queue<pair<ll,int> >Q;
        sort(a.begin()+1,a.end());
        a.erase(unique(a.begin()+1,a.end()),a.end());
        vector<unsigned int>vis(*max_element(a.begin(),a.end())+1,n+1);
        for(int i=1;i<a.size();i++) vis[a[i]]=1,Q.push({a[i],1});
        while(!Q.empty()){
            auto [x,t]=Q.front();
            Q.pop();
            if(x==base) {cout<<n-2+t<<endl;return;}
            for(int i=1;i<a.size();i++) {
                ll s=std::gcd(x,a[i]);
                if(t+1<vis[s]) vis[s]=t+1,Q.push({s,t+1});
            }
        }
    }
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}

D.Gellyfish and Camellia Japonica


原题链接

题意简述

有一个由 \(n\) 个整数 \(c_1, c_2, \ldots, c_n\)组成的数组. 开始, \(c = [a_1, a_2, \ldots, a_n]\).
对它进行 \(q\) 次操作,每次操作选择三个位置 \(x_i,y_i,z_i\),并使得 \(c_{z_i} = \min(c_{x_i}, c_{y_i})\).
\(q\)次操作后 \(c = [b_1, b_2, \ldots, b_n]\).
现询问可能的 \(a_1, a_2, \ldots, a_n\),如果不存在任何可能,直接输出 \(-1\) 即可

解题思路

题目要求构造还原 \(a\) 数组的可能性输出任意,那我们肯定考虑把操作倒过来一层一层的回到上一个状态, 最后是不是 可行数组把还原的数组正着进行一次操作看是不是 可能到 \(b\) 数组就行了。再考虑每个操作对原序列的影响 \(c_{z_i} = \min(c_{x_i}, c_{y_i})\),那么操作后 操作前 \(c_{x_i}\) 一定是 \(\max(c_{x_i},c_{z_i})\),同理 \(c_{y_i}\) 一定是 \(\max(c_{y_i},c_{z_i})\),再考虑 \(c_{z_i}\) 的可能性,如果 \(z_i=x_i\)\(z_i=y_i\),则 \(z\) 可以由 \(x\)\(y\) 唯一决定,但如果 \(z_i \neq x_i\)\(z_i \neq y_i\),此时上一个状态 \(z\) 的值我们无法直接确定,思考 \(z_i\) 如果成为上上个状态的 \(x_{i-1}\)\(y_{i-1}\) ,我们一定希望 \(c_{z_i}=\max(c_{x_{i-1}},c_{z_{i-1}})\)是尽可能有效的 ,那我们只要让 \(c_{z_i}\) 尽可能小即可,设 $c_{z_i} =0 $.按这个尝试逐层转移还原 \(a\) 数组判定。
ps:好像还有看成依赖关系的图的做法,不会,有人会可以浇浇我

AC code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll inf=1e18;
struct node{
    int x,y,z;
};
void solve(){
    int n,q;cin>>n>>q;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    auto b=a;
    vector<node>op(q+1);
    for(int i=1;i<=q;i++) cin>>op[i].x>>op[i].y>>op[i].z;
    reverse(op.begin()+1,op.end());
    for(int i=1;i<=q;i++){
        int x=op[i].x,y=op[i].y,z=op[i].z;
        int A=a[x],B=a[y],C=a[z];
        a[x]=max(A,C);
        a[y]=max(B,C);
        if(x!=z&&y!=z) a[z]=0;
    }
    reverse(op.begin()+1,op.end());
    auto c=a;
    for(int i=1;i<=q;i++){
        int x=op[i].x,y=op[i].y,z=op[i].z;
        c[z]=min(c[x],c[y]);
    }
    if(c!=b) {cout<<-1<<endl;return;}
    for(int i=1;i<=n;i++) cout<<a[i]<<" \n"[i==n];
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}
posted @ 2025-06-02 00:43  usedchang  阅读(19)  评论(0)    收藏  举报