The 2022 ICPC Asia Regionals Online Contest (I) (2022ICPC网络赛第一场)题解A,C,G,H,J,K,L
2022 ICPC网络赛第一场题解 更新中更新至A,C,G,H,J,K,L
A.01 Sequence
题意:对于一个长度为 333 的倍数的,元素只有01的环,你每次可以选择一个 111 删除以这个 111 为中心的相邻三个元素。你可以选择将环当中的部分 000 变成 111 ,求最少的选择数字数量使得你能够将这个环删除完毕。
给定一个长度为 nnn 的01序列,qqq 次询问,每次询问一个区间。每次询问一个区间,表示询问的环。(3≤n≤1e6,1≤q≤1e6)(3 \leq n \leq 1e6, 1 \leq q \leq 1e6)(3≤n≤1e6,1≤q≤1e6)
题解:题意显然可以转换成 len/3−环上选择至多的互不相邻的1的数量len/3 - 环上选择至多的互不相邻的1的数量len/3−环上选择至多的互不相邻的1的数量。通过线段树维护这个值,直接区间查询即可。
注意,题目询问是个环 ,最后得到的答案应该满足,不同时选左右端点的1,且上述式子可能求出来负数,要和 000 取 maxmaxmax。
复杂度: O(n+qlogn)O(n+qlogn)O(n+qlogn)
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N =1000005;
string str;
namespace SegTree{
#define ls rt << 1
#define rs rt << 1 | 1
#define lson ls, l, mid
#define rson rs, mid + 1, r
struct Info{
int lmax, rmax, lrin, lrout;
friend Info operator+ (Info a, Info b){
return {
max(a.lmax+max(b.lmax,b.lrout),a.lrin+b.lrout),
max(max(a.rmax,a.lrout)+b.rmax,a.lrout+b.lrin),
max({a.lmax+b.rmax,a.lrin+b.rmax,a.lmax+b.lrin}),
max({a.rmax+b.lrout,a.lrout+b.lrout,a.lrout+b.lmax})
};
}
}tr[N << 2];
void push_up(int rt){ tr[rt] = tr[ls] + tr[rs]; }
void build(int rt, int l, int r){
if(l == r){
if(str[l-1]=='1'){
tr[rt]={0,0,1,0};
}
return;
}
int mid = l + r >> 1;
build(lson), build(rson);
push_up(rt);
}
Info query(int rt, int l, int r, int L, int R){
if(l >= L && r <= R) return tr[rt];
int mid = l + r >> 1;
if(mid >= R) return query(lson, L, R);
if(mid < L) return query(rson, L, R);
return query(lson, L, R) + query(rson, L, R);
}
}
using SegTree::build;
using SegTree::query;
inline void solve(){
int n, q; cin >> n >> q >> str;
build(1, 1, n);
for(int i = 1; i <= q; i++){
int l, r; cin >> l >> r;
auto res = query(1, 1, n, l, r);
cout << max((r-l+1)/3-max({res.lmax, res.rmax,res.lrout}),0ll)<< endl;
}
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 1; // cin >> t;
while(t--) solve();
return 0;
}
C.Delete the Tree
给一个树,你可以选择两种操作
删除:删除一个点以及所有与他相邻的边
收缩:将一个有两条边的结点与他有的边删除,并连接两个本来与他相连的结点。
求最少的删除操作删除整棵树。
题解:签到题,对于每个度数大于等于 222 的结点在删除过程中都可能使得度数等于 222 ,所以答案就是度数小于等于 111 的节点数目。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 1e6 + 10;
int d[N],ans;
inline void solve(){
int n; cin >> n; ans = 0;
memset(d, 0, (n + 2) * sizeof(int));
for(int i = 1; i < n; i++){
int u, v; cin >> u >> v;
d[u]++;d[v]++;
}
for(int i = 1; i <= n; i++){
if(d[i] <= 1) ans++;
}
cout << ans << endl;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 1; cin >> t;
while(t--) solve();
return 0;
}
G.Read the Documentation
题意:给定一个数字 nnn 表示按照顺序有 nnn个物品,每个物品有一个权值 wiw_iwi,有费用 ttt。
对于选择物品要求有
不能选择连续的五个物品
作为连续的第 iii 个物品需要花费 xix_ixi 的费用
求获得的权值最大是多少。(1≤n≤100,1≤q≤1e9)(1 \leq n \leq 100,1 \leq q \leq 1e9)(1≤n≤100,1≤q≤1e9)
题解:简单背包。
令 dp[i][j][k][l][m]dp[i][j][k][l][m]dp[i][j][k][l][m] 表示,前 iii 个数,jjj 个 111 , kkk 个222 ,lll 个333 ,mmm 个 444 的方案的最大值
满足 j∗2+k∗3+l∗4+m∗5<=i+1&&sum1∗j+sum2∗k+sum3∗l+sum4∗m≤tj*2+k*3+l*4+m*5<=i+1 \&\& sum_1*j+sum_2*k+sum_3*l+sum_4*m \leq tj∗2+k∗3+l∗4+m∗5<=i+1&&sum1∗j+sum2∗k+sum3∗l+sum4∗m≤t
暴力转移即可(转移式子可见代码)。
复杂度O(n5120)O(\frac{n^5}{120})O(120n5)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,t,ans;
int x[10],a[105];
int dp[102][55][35][27][22];
void solve(){
cin>>n>>t;
for(int i=1;i<=4;i++){
cin>>x[i];
x[i]+=x[i-1];
}
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]+=a[i-1];
}
for(int i=1;i<=n;i++){
for(int j=0,sum1=0,res1=0;res1<=t&&sum1<=i+1&&j<=50;j++,sum1+=2,res1+=x[1]){
for(int k=0,sum2=sum1,res2=res1;res2<=t&&sum2<=i+1&&k<=33;k++,sum2+=3,res2+=x[2]){
for(int l=0,sum3=sum2,res3=res2;res3<=t&&sum3<=i+1&&l<=25;l++,sum3+=4,res3+=x[3]){
for(int m=0,sum4=sum3,res4=res3;res4<=t&&sum4<=i+1&&m<=20;m++,sum4+=5,res4+=x[4]){
dp[i][j][k][l][m]=dp[i-1][j][k][l][m];
if(j){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-2)][j-1][k][l][m]+a[i]-a[i-1]);
}
if(k){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-3)][j][k-1][l][m]+a[i]-a[i-2]);
}
if(l){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-4)][j][k][l-1][m]+a[i]-a[i-3]);
}
if(m){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-5)][j][k][l][m-1]+a[i]-a[i-4]);
}
ans=max(ans,dp[i][j][k][l][m]);
}
}
}
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
H.Step Debugging
题意:忘光了
题解:按照题意模拟dfs过程即可,我是被队友嘴巴操纵写的代码。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=20220911;
string s;
int dfs(){
int res=0,w;
string s;
while(cin>>s){
if(s=="for")return res;
else if(s=="repeat"){
int now=dfs();
cin>>w>>s;
(res+=now*w)%=mod;
}
else if(s=="library")res=(res+1)%mod;
else if(s=="fin")return res;
}
return res;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
cout<<dfs()%mod;
}
J.Gachapon
题意:一个抽卡游戏,玩家想要抽中某张卡,一旦抽中立刻停止抽奖。
给定两个整数 X,YX,YX,Y 表示进行 XXX 轮抽卡,结束游戏的轮数期望是 YYY 。你需要构造一组包含 XXX 个数的数组 ppp 。其中 pip_ipi 表示在第 iii 轮游戏抽中卡的概率,满足 pX==1p_X==1pX==1 且 ∀1≤i<x,0<pi<1\forall{1 \leq i < x},0< p_i < 1∀1≤i<x,0<pi<1 。对于所有的 pip_ipi 以最简分数形式输出,保证输出的最简分数 a/ba/ba/b 满足 a,b≤10000a,b \leq 10000a,b≤10000。
令 qiq_iqi 表示进行 iii 轮游戏结束的概率,考虑将题意用公式写出则有
∑qi=1∑i∗qi=Y \begin{aligned} \sum q_i &=1 \\ \sum i*q_i&=Y \end{aligned} ∑qi∑i∗qi=1=Y
我们需要构造序列 qqq 满足以上两个方程。假设所有 qiq_iqi 的最简分数。令分母的最小公倍数为 nnn , aia_iai 表示分母为 nnn 时候的 qiq_iqi 的分子,则有,sumisum_isumi 为 aaa 数组前 iii 项和。对于任何一个合法的 qqq 序列,对应有 pi=ain−sumi−1p_i = \frac{a_i}{n-sum_{i-1}}pi=n−sumi−1ai,发现每一轮的 pip_ipi 的分母小于等于 nnn,为满足题意要求可令 n=10000n=10000n=10000,随后即有
∑ai=10000∑i∗ai=10000∗Y \begin{aligned} \sum a_i &=10000 \\ \sum i*a_i&=10000*Y \end{aligned} ∑ai∑i∗ai=10000=10000∗Y
考虑构造一个满足题意的解序列 aaa ,首先找到满足上式,对应期望最小值,则为 a1=10001−xa_1=10001-xa1=10001−x, 其余为 111 ,然后在每次 ai−1−1,ai+1a_{i-1}-1,a_{i}+1ai−1−1,ai+1 过程中,期望增加一,循环即可找到一个解,随后输出即可。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int x,y,ex,p[1005];
void solve(){
cin>>x>>y;
ex=p[1]=10001-x;
for(int i=2;i<=x;i++){
p[i]=1;
ex+=i;
}
for(;ex<y*10000;){
for(int i=1;ex<y*10000&&i<x;i++){
p[i]--,p[i+1]++;
ex++;
}
}
for(int i=1,fenmu=10000;i<=x;i++){
int d=__gcd(p[i],fenmu);
cout<<p[i]/d<<" "<<fenmu/d<<endl;
fenmu-=p[i];
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
K.Pyramad
题意:玩家起始攻击力为 111 ,训练可增长 111 攻击力(后续称之训练效果)。每天玩家可以选择两种操作
使训练效果+1
训练一次
每天做完选择之后会面临一个血量为 xix_ixi 的怪物,当且仅当你的攻击力大于等于怪物血量的时候才能杀死他。怪物共 nnn 个,求最多杀死多少个怪物。(1≤n,xi≤5000,)(1 \leq n,x_i \leq 5000,)(1≤n,xi≤5000,)
题解:太晚了明天写详细的。先放个代码。
读完题目感觉无从下手,先列出题目相关的变量 :怪物的数量,玩家的攻击力,训练效果,胜利的次数,而且攻击力显然至多和血量为一个数量级。因为每天可以增加攻击力或让训练效果增强很容易想到一个 O(n3)O(n^3)O(n3) 的 dp ,即令 dpi,j,kdp_{i,j,k}dpi,j,k 表示经过 iii 个怪物 ,攻击力为 jjj ,训练效果为 kkk 的最大胜利数量。这个递推可以 O(1)O(1)O(1) 转移。
考虑优化。一个较为显然的优化是训练效果至多增强到 n\sqrt nn 级别所以该维可以只用前 n\sqrt nn 部分。
简略证明:这里本来有个证明但是我一下子不知道怎么写,大致思路是这里是个凸函数。后面想明白了再补。
考虑第二个优化。容易发现当前的 dp 式子的维度无法继续优化。但是根据上一个优化结论,我们可以知道,假设我们不考虑一开始的怪物,只需要最少只需要 142142142 天即可将攻击力超过 500050005000 ,所以我们发现失败的轮数至多是 142142142 。
考虑将失败轮数作为 dp 的转移,令 dpi,j,kdp_{i,j,k}dpi,j,k 表示经过 iii 个怪物 ,失败次数为 jjj ,训练效果为 kkk 的最大攻击力。然后枚举每一天选择两种方案即可 O(1)O(1)O(1) 转移。由于可能存在部分无效状态所以可以使用刷表法进行转移。最后对所有有效的状态求 MAX(i−j)MAX(i-j)MAX(i−j) 即可。
#include <bits/stdc++.h>
using namespace std;
int n,k,ans;
int x[5005];
int dp[5005][150][75];//前i个输j把,w到k的最大攻击力
void solve(){
cin>>n;
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;i++){cin>>x[i];}
dp[0][0][1]=1;
for(int i=0;i<=n;i++){
for(int j=0;j<=142;j++){
for(int k=1;k<=73;k++){
if(dp[i][j][k]!=-1){
ans=max(ans,i-j);
if(dp[i][j][k]>=x[i+1]){
dp[i+1][j][k+1]=max(dp[i+1][j][k+1],dp[i][j][k]);
}
else{
dp[i+1][j+1][k+1]=max(dp[i+1][j+1][k+1],dp[i][j][k]);
}
if(dp[i][j][k]+k>=x[i+1]){
dp[i+1][j][k]=max(dp[i][j][k]+k,dp[i+1][j][k]);
}
else{
dp[i+1][j+1][k]=max(dp[i][j][k]+k,dp[i+1][j+1][k]);
}
}
}
}
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
L.LCS-like Problem
题意:给定两个字符串 s,ts,ts,t 求从 sss 的最长子序列满足该子序列和 ttt 的最长公共子序列小于等于 111 。
题解:DP,对 sss 中字母分出现在 ttt 中和没出现在 ttt 中考虑。对于没出现过的字母,贪心选择即可。对于出现在 ttt 中的字母,我们可以建立一个矩阵 cki,jck_{i,j}cki,j 表示字母 iii 后面能否添加 jjj。然后直接 26n26n26n 求最大值即可。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
string s,t;
int n,m;
int ck[30][30],tot[26];
int dp[30];
void solve(){
cin>>s>>t;
n=s.size();m=t.size();
s=" "+s;t=" "+t;
for(int i=m;i;i--){
int k=t[i]-'a';
for(int j=0;j<26;j++){
ck[k][j]=tot[j];
}
tot[k]++;
}
int res=0,ans=0;
for(int i=1;i<=n;i++){
int k=s[i]-'a';
if(tot[k]==0){
res++;continue;
}
if(!ck[k][k])dp[k]=dp[k]+1;
for(int j=0;j<26;j++){
if(j!=k&&ck[j][k]==0){
dp[k]=max(dp[k],dp[j]+1);
}
}
}
for(int i=0;i<26;i++){
ans=max(ans,dp[i]);
}
cout<<ans+res;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
更多推荐
所有评论(0)