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;
}