zoukankan      html  css  js  c++  java
  • 【AtCoder】AtCoder Grand Contest 029 解题报告($Asim E$)

    点此进入比赛

    (A):Irreversible operation(点此看题面

    大致题意: 给定一个"B"和"W"组成的序列,每次操作将一对相邻的"BW"改成"WB",问最多操作几次。

    考虑操作本质就是将一个"B"移到一个"W"右边。

    因此我们只要求出每个"B"的右边有几个"W"即可。

    直接从后往前扫一遍。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    using namespace std;
    int n;char s[N+5];
    int main()
    {
    	RI i,t=0;long long ans=0;scanf("%s",s+1),n=strlen(s+1);//读入
    	for(i=n;i;--i) s[i]=='W'?++t:(ans+=t);return printf("%lld
    ",ans),0;//统计W个数,对于B更新答案
    }
    

    (B):Powers of two(点此看题面

    大致题意: 给定(n)个数,问能选出多少对数(每个数只能被选一次),使得每对的两数之和都是(2)的幂。

    如果直接瞎做,每个数可能与(log)种数匹配,不太好搞。

    但我们考虑,如果只去找比当前数小且能匹配的数,那么最多只有一种符合要求的数。

    也就是说,其实呈现出一个树状结构,那么就可以贪心,能取就取。

    直接从后往前扫一遍。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    using namespace std;
    int n,a[N+5];map<int,int> s,g;
    int main()
    {
    	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),++s[a[i]];//读入+初始化
    	RI ans=0,t=1<<30;for(sort(a+1,a+n+1),i=n;i;--i)//排序,从后往前扫
    	{
    		if(g[a[i]]) {--g[a[i]];continue;}--s[a[i]];//如果被选过就跳过
    		W((a[i]<<1)<t) t>>=1;s[t-a[i]]&&(--s[t-a[i]],++g[t-a[i]],++ans);//贪心
    	}return printf("%d
    ",ans),0;
    }
    

    (C):Lexicographic constraints(点此看题面

    大致题意: 已知(n)个字符串(s_{1sim n})的长度(a_{1sim n}),问最少用多少种字符,使得(s_i)的字典序严格递增。

    显然先去二分答案(x),然后就是个模拟的过程:

    • (a_i>a_{i-1}):直接在末尾加上(a_i-a_{i-1})(0)
    • (a_ile a_{i-1}):先将末尾(a_{i-1}-a_i)个数删去,然后加(1)(视作一个(x)进制数)。

    考虑将数全部相同的区间合并起来,容易发现每次操作最多新增一个区间,复杂度可以保证。

    具体模拟的时候,可以用栈来存储。(一开始看到区间赋值就想无脑(ODT),后来发现这里每次只会操作最后若干个区间,完全没必要)

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    using namespace std;
    int n,a[N+5];struct Data {int l,r,v;I Data(CI x=0,CI y=0,CI t=0):l(x),r(y),v(t){}}S[N+5];
    I bool Check(CI x)//验证
    {
    	RI i,T=0;for(i=1;i<=n;++i)
    	{
    		if(a[i]>a[i-1]) {S[++T]=Data(a[i-1]+1,a[i],0);continue;}//若a[i]>a[i-1],在末尾加0
    		W(a[i]<S[T].l) --T;S[T].r=a[i];//若a[i]≤a[i-1],先删去多余的数
    		W(T&&S[T].v==x-1) --T;if(!T) return 0;//找到最右的加1不进位的位置,找不到说明答案偏小
    		S[++T]=Data(S[T].r,S[T].r,S[T].v+1),--S[T-1].r<S[T-1].l&&(S[T-1]=S[T],--T),//提取出区间的最后一个元素加1
    		S[T].r^a[i]&&(S[++T]=Data(S[T].r+1,a[i],0),0);//将进位的那些位修改为0放回来
    	}return 1;
    }
    int main()
    {
    	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
    	RI l=1,r=n,mid;W(l<r) Check(mid=l+r-1>>1)?r=mid:l=mid+1;return printf("%d
    ",r),0;//二分答案
    }
    

    (D):Grid game(点此看题面

    大致题意: 给定一个(H imes W)的网格图,上面有(n)个障碍格。一开始棋子在((1,1)),先手每次可以选择将棋子向下移一格,后手每次可以选择将棋子向右移一格,先手连续不操作两次就输。先手希望最大化先手移动次数,后手希望最小化先手移动次数,问先手最终的移动次数。

    考虑每一行肯定只需考虑最左边的障碍格,而后手的目的就是把棋子移到某个障碍格上方。

    如果先手某次不走,那么后手可以随便走或不走,情况一定不会利于先手,因此先手肯定每次能走就走。

    那我们只要枚举每一行,求出走到这一行时后手最多能走多少步(t),显然他一定能走(le t)步。

    于是只要找到第一个符合条件的行就可以了。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    using namespace std;
    int h,w,n,s[N+5];map<int,map<int,int> > p;
    int main()
    {
    	RI i,t,x,y;for(scanf("%d%d%d",&h,&w,&n),i=1;i<=h;++i) s[i]=w+1;
    	for(i=1;i<=n;++i) scanf("%d%d",&x,&y),s[x]=min(s[x],y);//记录每一行最左边的障碍格
    	for(t=1,i=2;i<=h;++i) if(s[i]<=t) return printf("%d
    ",i-1),0;else s[i]>t+1&&++t;//判断是否合法;能向右走就走
    	return printf("%d
    ",h),0;//输出总行数
    }
    

    (E):Wandering TKHS(点此看题面

    大致题意: 给定一棵树,要求你从(k)号点出发,每次走到与已访问节点相邻的未访问节点中编号最小的节点上,直至走到(1)号点。对于(k=2sim n),各自求出走过的总点数减(1)的值。

    神仙题。

    首先,我们设(Mx_x)表示从(fa_x)(1)号点路径上的最大编号,并用(Q(x,v))表示在(x)的子树内,只走编号小于(v)的点经过的边数。

    然后考虑从(fa_x)(x)答案(c)发生的变化,显然要根据(x)(Mx_{fa_x})的大小关系分类讨论:

    • (x>Mx_{fa_x})(x)会造成新的贡献,因此(c_x-c_{fa_x}=Q(x,Mx_x)+1)
    • (x<Mx_{fa_x})(x)原本就有贡献,因此只需在(fa_x>Mx_{fa_x})时考虑(x)子树内所有编号(in(Mx_{fa_x},fa_x))的点数,即(c_x-c_{fa_x}=Q(x,Mx_x)-Q(x,Mx_{fa_x}))

    至于如何计算(Q(x,v)),实际上我们可以暴力做。由于我们询问的(v)只可能是(Mx_x)或者(Mx_{fa_x}),因此一个点被访问到的次数可以证明是常数级别的。

    然后只要树上差分即可求出答案。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,c[N+5],sz[N+5],Mx[N+5],s[N+5],d[N+5];int ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
    I void Walk(CI x,CI v,CI lst=0)//暴力
    {
    	s[x]=1;for(RI i=lnk[x];i;i=e[i].nxt)
    		e[i].to^lst&&e[i].to<v&&(Walk(e[i].to,v,x),s[x]+=s[e[i].to]);//只走编号小于v的点
    }
    I void dfs1(CI x,CI lst=0)//第一遍dfs
    {
    	RI i;for(sz[x]=1,Mx[x]=max(Mx[lst],lst),i=lnk[x];i;i=e[i].nxt)//从父节点继承信息
    		e[i].to^lst&&(dfs1(e[i].to,x),sz[x]+=sz[e[i].to],d[x]+=d[e[i].to]);//从子节点上传信息
    	Mx[lst]<x&&(Walk(x,Mx[x],lst),d[x]=-sz[x]),Mx[lst]<lst&&(d[x]+=sz[x]);//特殊处理
    }
    I void dfs2(CI x,CI lst=0)//第二遍dfs
    {
    	lst&&(Mx[lst]<x?c[x]+=s[x]:Mx[lst]<lst&&(c[x]+=d[x]-s[x]),c[x]+=c[lst]);//注意求出的是c[x]-c[lst],因此要加上c[lst]
    	for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs2(e[i].to,x),0);
    }
    int main()
    {
    	RI i,x,y;for(scanf("%d",&n),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    	for(dfs1(1),dfs2(1),i=2;i<=n;++i) printf("%d%c",c[i]," 
    "[i==n]);return 0;
    }
    
  • 相关阅读:
    VSCode插件开发全攻略(一)概览
    如何使用JavaScript实现纯前端读取和导出excel文件
    ReactNative学习笔记(七)Navigator的使用
    ReactNative学习笔记(六)集成视频播放
    ReactNative学习笔记(五)踩坑总结
    ReactNative学习笔记(四)热更新和增量更新
    ReactNative学习笔记(三)打包、调试、运行等相关介绍
    ReactNative学习笔记(二)基础进阶
    ReactNative学习笔记(一)环境搭建
    彻底禁用Chrome的“请停用以开发者模式运行的扩展程序”提示
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC029.html
Copyright © 2011-2022 走看看