【AtCoder】AtCoder Grand Contest 029 解题报告(A~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\n",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\n",ans),0;
}
\(C\):Lexicographic constraints(点此看题面)
大致题意: 已知\(n\)个字符串\(s_{1\sim n}\)的长度\(a_{1\sim n}\),问最少用多少种字符,使得\(s_i\)的字典序严格递增。
显然先去二分答案\(x\),然后就是个模拟的过程:
- \(a_i>a_{i-1}\):直接在末尾加上\(a_i-a_{i-1}\)个\(0\)。
- \(a_i\le 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\n",r),0;//二分答案
}
\(D\):Grid game(点此看题面)
大致题意: 给定一个\(H\times 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\n",i-1),0;else s[i]>t+1&&++t;//判断是否合法;能向右走就走
return printf("%d\n",h),0;//输出总行数
}
\(E\):Wandering TKHS(点此看题面)
大致题意: 给定一棵树,要求你从\(k\)号点出发,每次走到与已访问节点相邻的未访问节点中编号最小的节点上,直至走到\(1\)号点。对于\(k=2\sim 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]," \n"[i==n]);return 0;
}