The 2022 ICPC Asia Regionals Online Contest (I) (2022ICPC网络赛第一场)题解A,C,G,H,J,K,L

A.01 Sequence

题意:对于一个长度为 3 3 3 的倍数的,元素只有01的环,你每次可以选择一个 1 1 1 删除以这个 1 1 1 为中心的相邻三个元素。你可以选择将环当中的部分 0 0 0 变成 1 1 1 ,求最少的选择数字数量使得你能够将这个环删除完毕。
给定一个长度为 n n n 的01序列, q q q 次询问,每次询问一个区间。每次询问一个区间,表示询问的环。 ( 3 ≤ n ≤ 1 e 6 , 1 ≤ q ≤ 1 e 6 ) (3 \leq n \leq 1e6, 1 \leq q \leq 1e6) 3n1e6,1q1e6

题解:题意显然可以转换成 l e n / 3 − 环上选择至多的互不相邻的 1 的数量 len/3 - 环上选择至多的互不相邻的1的数量 len/3环上选择至多的互不相邻的1的数量。通过线段树维护这个值,直接区间查询即可。
注意,题目询问是个环 ,最后得到的答案应该满足,不同时选左右端点的1,且上述式子可能求出来负数,要和 0 0 0 m a x max max
复杂度: O ( n + q l o g n ) 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

给一个树,你可以选择两种操作

删除:删除一个点以及所有与他相邻的边
收缩:将一个有两条边的结点与他有的边删除,并连接两个本来与他相连的结点。

求最少的删除操作删除整棵树。

题解:签到题,对于每个度数大于等于 2 2 2 的结点在删除过程中都可能使得度数等于 2 2 2 ,所以答案就是度数小于等于 1 1 1 的节点数目。

#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

题意:给定一个数字 n n n 表示按照顺序有 n n n个物品,每个物品有一个权值 w i w_i wi,有费用 t t t
对于选择物品要求有

不能选择连续的五个物品
作为连续的第 i i i 个物品需要花费 x i x_i xi 的费用

求获得的权值最大是多少。 ( 1 ≤ n ≤ 100 , 1 ≤ q ≤ 1 e 9 ) (1 \leq n \leq 100,1 \leq q \leq 1e9) (1n100,1q1e9)
题解:简单背包。
d p [ i ] [ j ] [ k ] [ l ] [ m ] dp[i][j][k][l][m] dp[i][j][k][l][m] 表示,前 i i i 个数, j j j 1 1 1 , k k k 2 2 2 , l l l 3 3 3 , m m m 4 4 4 的方案的最大值
满足 j ∗ 2 + k ∗ 3 + l ∗ 4 + m ∗ 5 < = i + 1 & & s u m 1 ∗ j + s u m 2 ∗ k + s u m 3 ∗ l + s u m 4 ∗ m ≤ t j*2+k*3+l*4+m*5<=i+1 \&\& sum_1*j+sum_2*k+sum_3*l+sum_4*m \leq t j2+k3+l4+m5<=i+1&&sum1j+sum2k+sum3l+sum4mt
暴力转移即可(转移式子可见代码)。
复杂度 O ( n 5 120 ) 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 , Y X,Y XY 表示进行 X X X 轮抽卡,结束游戏的轮数期望是 Y Y Y 。你需要构造一组包含 X X X 个数的数组 p p p 。其中 p i p_i pi 表示在第 i i i 轮游戏抽中卡的概率,满足 p X = = 1 p_X==1 pX==1 ∀ 1 ≤ i < x , 0 < p i < 1 \forall{1 \leq i < x},0< p_i < 1 1i<x,0<pi<1 。对于所有的 p i p_i pi 以最简分数形式输出,保证输出的最简分数 a / b a/b a/b 满足 a , b ≤ 10000 a,b \leq 10000 a,b10000

q i q_i qi 表示进行 i i i 轮游戏结束的概率,考虑将题意用公式写出则有

∑ q i = 1 ∑ i ∗ q i = Y \begin{aligned} \sum q_i &=1 \\ \sum i*q_i&=Y \end{aligned} qiiqi=1=Y
我们需要构造序列 q q q 满足以上两个方程。假设所有 q i q_i qi 的最简分数。令分母的最小公倍数为 n n n , a i a_i ai 表示分母为 n n n 时候的 q i q_i qi 的分子,则有, s u m i sum_i sumi a a a 数组前 i i i 项和。对于任何一个合法的 q q q 序列,对应有 p i = a i n − s u m i − 1 p_i = \frac{a_i}{n-sum_{i-1}} pi=nsumi1ai,发现每一轮的 p i p_i pi 的分母小于等于 n n n,为满足题意要求可令 n = 10000 n=10000 n=10000,随后即有
∑ a i = 10000 ∑ i ∗ a i = 10000 ∗ Y \begin{aligned} \sum a_i &=10000 \\ \sum i*a_i&=10000*Y \end{aligned} aiiai=10000=10000Y
考虑构造一个满足题意的解序列 a a a ,首先找到满足上式,对应期望最小值,则为 a 1 = 10001 − x a_1=10001-x a1=10001x, 其余为 1 1 1 ,然后在每次 a i − 1 − 1 , a i + 1 a_{i-1}-1,a_{i}+1 ai11,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

题意:玩家起始攻击力为 1 1 1 ,训练可增长 1 1 1 攻击力(后续称之训练效果)。每天玩家可以选择两种操作

使训练效果+1
训练一次

每天做完选择之后会面临一个血量为 x i x_i xi 的怪物,当且仅当你的攻击力大于等于怪物血量的时候才能杀死他。怪物共 n n n 个,求最多杀死多少个怪物。 ( 1 ≤ n , x i ≤ 5000 , ) (1 \leq n,x_i \leq 5000,) (1n,xi5000,)

题解:太晚了明天写详细的。先放个代码。
读完题目感觉无从下手,先列出题目相关的变量 :怪物的数量,玩家的攻击力,训练效果,胜利的次数,而且攻击力显然至多和血量为一个数量级。因为每天可以增加攻击力或让训练效果增强很容易想到一个 O ( n 3 ) O(n^3) O(n3) 的 dp ,即令 d p i , j , k dp_{i,j,k} dpi,j,k 表示经过 i i i 个怪物 ,攻击力为 j j j ,训练效果为 k k k 的最大胜利数量。这个递推可以 O ( 1 ) O(1) O(1) 转移。
考虑优化。一个较为显然的优化是训练效果至多增强到 n \sqrt n n 级别所以该维可以只用前 n \sqrt n n 部分。
简略证明:这里本来有个证明但是我一下子不知道怎么写,大致思路是这里是个凸函数。后面想明白了再补。
考虑第二个优化。容易发现当前的 dp 式子的维度无法继续优化。但是根据上一个优化结论,我们可以知道,假设我们不考虑一开始的怪物,只需要最少只需要 142 142 142 天即可将攻击力超过 5000 5000 5000 ,所以我们发现失败的轮数至多是 142 142 142
考虑将失败轮数作为 dp 的转移,令 d p i , j , k dp_{i,j,k} dpi,j,k 表示经过 i i i 个怪物 ,失败次数为 j j j ,训练效果为 k k k 的最大攻击力。然后枚举每一天选择两种方案即可 O ( 1 ) O(1) O(1) 转移。由于可能存在部分无效状态所以可以使用刷表法进行转移。最后对所有有效的状态求 M A X ( i − j ) MAX(i-j) MAX(ij) 即可。

#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 , t s,t s,t 求从 s s s 的最长子序列满足该子序列和 t t t 的最长公共子序列小于等于 1 1 1

题解:DP,对 s s s 中字母分出现在 t t t 中和没出现在 t t t 中考虑。对于没出现过的字母,贪心选择即可。对于出现在 t t t 中的字母,我们可以建立一个矩阵 c k i , j ck_{i,j} cki,j 表示字母 i i i 后面能否添加 j j j。然后直接 26 n 26n 26n 求最大值即可。

#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();
}
  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值