隐藏页面特效

AtCoder Regular Contest 180

1|0Preface


一年没现场打Atcoder了,结果手速不够经典赛后过题,丢了个上大分的好机会

这场主要是B卡了挺久了,拖到快1h的时候才过,然后后面策略也有点问题,对着不擅长的Counting C题看了30min没啥想法

最后30min去看D发现是个很经典的DS拼凑题,结果写完之后比赛已经结束20min了,不过交上去也过了

感觉状态啥的还是没调整过来,今晚还有场Div1+2希望能别掉大分的说


2|0A - ABA and BAB


刚开始感觉有点无从下手,但观察到核心性质后就很简单了

需要注意到,两个相邻的AA或者两个相邻的BB是无论如何都不能一起操作的

因此可以根据这个把序列分成若干段,每一段内部都是形如ABABABABA这样的交错段

设这一段的长度为 l,手玩一下会发现不同的变化方案有 l+12种,最后把每一段的方案数乘起来即可

#include<cstdio> #include<iostream> #include<utility> #include<vector> #include<cstring> #include<cmath> #include<cstdlib> #include<algorithm> #include<queue> #include<set> #include<map> #include<set> #include<array> #include<random> #include<bitset> #include<ctime> #include<limits.h> #include<assert.h> #include<unordered_set> #include<unordered_map> #define RI register int #define CI const int& #define mp make_pair #define fi first #define se second #define Tp template <typename T> using namespace std; typedef long long LL; typedef long double LDB; typedef unsigned long long u64; typedef pair <int,int> pi; typedef vector <int> VI; typedef array <int,3> tri; const int N=250005,mod=1e9+7; int n; char s[N]; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i; scanf("%d%s",&n,s+1); int len=1,ans=1; for (i=2;i<=n;++i) if (s[i]!=s[i-1]) ++len; else ans=1LL*(len+1)/2*ans%mod,len=1; ans=1LL*(len+1)/2*ans%mod; return printf("%d",ans),0; }

3|0B - Improve Inversions


首先考虑当 k=1 时怎么处理,不难发现交换次数的上界就是排列的逆序对数,则考虑是否存在一种方案使得交换次数总能达到该上界

考虑按照从小到大的顺序考虑每个数 x,设其在序列中初始所在的位置为 posx,则我们对于所有 j>posxpj<x的数均可以进行一次交换

具体地,把后面的比 x 小的数按照从大到小的顺序依次与位置 posx 上的数进行交换,不难发现这样的交换总能进行

由于我们是按照从小到大的顺序进行这个操作的,因此在处理后面更大的数时比它小的数的相对位置可能会发生变化,但对答案没有影响

因此就得到了一种达到上界的构造方案,推广到 k>1 的情形也是类似的,直接模拟地构造即可

#include<cstdio> #include<iostream> #include<utility> #include<vector> #include<cstring> #include<cmath> #include<cstdlib> #include<algorithm> #include<queue> #include<set> #include<map> #include<set> #include<array> #include<random> #include<bitset> #include<ctime> #include<limits.h> #include<assert.h> #include<unordered_set> #include<unordered_map> #define RI register int #define CI const int& #define mp make_pair #define fi first #define se second #define Tp template <typename T> using namespace std; typedef long long LL; typedef long double LDB; typedef unsigned long long u64; typedef pair <int,int> pi; typedef vector <int> VI; typedef array <int,3> tri; const int N=505; int n,k,a[N],pos[N]; vector <pi> ans; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]),pos[a[i]]=i; for (i=1;i<=n;++i) { for (int l=pos[i];;) { int best=-1,num=-1; for (j=l+k;j<=n;++j) if (a[j]<a[l]&&a[j]>best) best=a[j],num=j; if (best!=-1) ans.push_back(pi(l,num)),swap(a[l],a[num]); else break; } } printf("%d\n",ans.size()); for (auto [l,r]:ans) printf("%d %d\n",l,r); return 0; }

4|0C - Subsequence and Prefix Sum


刚开始看错题了以为是替换成所有选中位置的和,感觉不太可做;后面玩了会样例后才发现正确题意,然而还是不会做,鉴定为Counting战俘没什么好说的

首先这题做法一眼DP,但状态的设计比较巧妙,定义 fi,j 表示处理了前 i 个数,并且此时 ai=j 的方案数

根据操作的性质,当 j0 时我们可以直接枚举下一个选中的位置 k,转移为 fk,ak+jfi,j,此时 k 位置上的数一定会发生变化,因此是一种不同的方案

但当 j=0 时情况就有点特殊了,此时直接枚举到的下一个位置 k 是不会变化的,因此不能记为一种方案

因此我们需要在 [i+1,k1] 中找一个数 v0 作为中转点将其贡献转给 ak,即 fk,ak+vfi,j

不过要注意中间的这段数需要去重,因为选它们中值相同的位置不会改变序列本身的形态,是同质的操作

总复杂度 O(n3×|ai|)

#include<cstdio> #include<iostream> #include<utility> #include<vector> #include<cstring> #include<cmath> #include<cstdlib> #include<algorithm> #include<queue> #include<set> #include<map> #include<set> #include<array> #include<random> #include<bitset> #include<ctime> #include<limits.h> #include<assert.h> #include<unordered_set> #include<unordered_map> #define RI register int #define CI const int& #define mp make_pair #define fi first #define se second #define Tp template <typename T> using namespace std; typedef long long LL; typedef long double LDB; typedef unsigned long long u64; typedef pair <int,int> pi; typedef vector <int> VI; typedef array <int,3> tri; const int N=105,M=100*20+5,mod=1e9+7; int n,a[N],mn,mx,f[N][M],ans; inline void inc(int& x,CI y) { if ((x+=y)>=mod) x-=mod; } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i,j,k; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),mn+=min(a[i],0),mx+=max(a[i],0); for (f[0][0-mn]=1,i=0;i<n;++i) for (j=mn;j<=mx;++j) { if (!f[i][j-mn]) continue; if (j==0) { set <int> s; s.insert(a[i+1]); for (k=i+2;k<=n;++k) { for (auto x:s) if (x) inc(f[k][a[k]+x-mn],f[i][j-mn]); s.insert(a[k]); } } else { for (k=i+1;k<=n;++k) inc(f[k][a[k]+j-mn],f[i][j-mn]); } } for (i=0;i<=n;++i) for (j=mn;j<=mx;++j) inc(ans,f[i][j-mn]); return printf("%d",ans),0; }

5|0D - Division into 3


很典的一个题,属于是看一眼就知道怎么写了,但因为挺久没写代码了写的磨磨蹭蹭而且时间也不够,没能在比赛中写出来

首先不妨设 mx=maxlirai,不难发现三段中至少有一段的贡献为 mx,因此根据 mx 所在的位置讨论一下:

  • mx 在中间一段时,此时简单分析后会发现最优策略就是 al,ar 单独成一段,剩下中间的全部成一段
  • mx 在最左边的一段时,此时中间一段最优一定是选一个数 ap,剩下最右边的一段的贡献为 maxp<jraj
  • mx 在最右边的一段时,此时中间一段最优一定是选一个数 aq,剩下最左边的一段的贡献为 maxlj<qaj

不难发现这样统计虽然没有考虑 mx 所在的具体位置的限制,但由于只会把贡献算大并且正确的答案一定会被统计到,因此是完备的

其中第一种情况很简单,而第三种情况只需要把原序列reverse一下就可以规约为第二种情况,因此仅考虑第二种情况如何处理

考虑当右端点 r 固定时,选择 p 这个位置的贡献由两部分构成,一部分是 ap;另一部分为 maxp<jraj

不难发现后面那个式子的值是一段一段变化的,即我们只需要关心从位置 r 向左的一段极长的单调增子序列即可

这个只需要用单调栈维护下所有关键的分界点,每部分内部用ST表查询出前面部分式子的最小值即可算出整体的贡献

最后对于询问就用扫描线的思路从左到右处理右端点,然后在单调栈上二分找到左端点对应的位置

单独讨论掉最边上的一段贡献后,剩下的部分就是一个单点修改,区间求最小值了,直接用线段树维护即可

总复杂度 O(nlogn),代码其实很好写

#include<cstdio> #include<iostream> #include<utility> #include<vector> #include<cstring> #include<cmath> #include<cstdlib> #include<algorithm> #include<queue> #include<set> #include<map> #include<set> #include<array> #include<random> #include<bitset> #include<ctime> #include<limits.h> #include<assert.h> #include<unordered_set> #include<unordered_map> #define RI register int #define CI const int& #define mp make_pair #define fi first #define se second #define Tp template <typename T> using namespace std; typedef long long LL; typedef long double LDB; typedef unsigned long long u64; typedef pair <int,int> pi; typedef vector <int> VI; typedef array <int,3> tri; const int N=250005,INF=1e9; int n,q,a[N],mn[N][20],mx[N][20],lg[N],ans[N],stk[N],top; vector <tri> Q1[N],Q2[N]; inline int query_min(CI l,CI r) { if (l==0) return INF; int k=lg[r-l+1]; return min(mn[l][k],mn[r-(1<<k)+1][k]); } inline int query_max(CI l,CI r) { if (l==0) return -INF; int k=lg[r-l+1]; return max(mx[l][k],mx[r-(1<<k)+1][k]); } class Segment_Tree { private: int mn[N<<2]; public: #define TN CI now=1,CI l=1,CI r=n #define LS now<<1,l,mid #define RS now<<1|1,mid+1,r inline void modify(CI pos,CI mv,TN) { if (l==r) return (void)(mn[now]=mv); int mid=l+r>>1; if (pos<=mid) modify(pos,mv,LS); else modify(pos,mv,RS); mn[now]=min(mn[now<<1],mn[now<<1|1]); } inline int query(CI beg,CI end,TN) { if (beg>end) return INF; if (beg<=l&&r<=end) return mn[now]; int mid=l+r>>1,ret=INF; if (beg<=mid) ret=min(ret,query(beg,end,LS)); if (end>mid) ret=min(ret,query(beg,end,RS)); return ret; } #undef TN #undef LS #undef RS }SEG; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i,j; for (scanf("%d%d",&n,&q),i=1;i<=n;++i) scanf("%d",&a[i]),mn[i][0]=mx[i][0]=a[i]; for (lg[0]=-1,i=1;i<=n;++i) lg[i]=lg[i>>1]+1; for (j=1;(1<<j)<=n;++j) for (i=1;i+(1<<j)-1<=n;++i) mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]), mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]); for (i=1;i<=q;++i) { int l,r; scanf("%d%d",&l,&r); int mx=query_max(l,r); ans[i]=mx+a[l]+a[r]; Q1[r].push_back({mx,l,i}); Q2[n-l+1].push_back({mx,n-r+1,i}); } for (i=1;i<=n;++i) { while (top>0&&a[stk[top]]<=a[i]) --top; stk[++top]=i; SEG.modify(top,query_min(stk[top-1],i-1)+a[i]); for (auto [mx,l,id]:Q1[i]) { int pos=lower_bound(stk+1,stk+top+1,l+1)-stk,ret; if (l+1<=stk[pos]-1) ret=query_min(l+1,stk[pos]-1)+a[stk[pos]]; else ret=INF; ret=min(ret,SEG.query(pos+1,top)); ans[id]=min(ans[id],mx+ret); } } reverse(a+1,a+n+1); top=0; for (i=1;i<=n;++i) mn[i][0]=mx[i][0]=a[i]; for (j=1;(1<<j)<=n;++j) for (i=1;i+(1<<j)-1<=n;++i) mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]), mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]); for (i=1;i<=n;++i) { while (top>0&&a[stk[top]]<=a[i]) --top; stk[++top]=i; SEG.modify(top,query_min(stk[top-1],i-1)+a[i]); for (auto [mx,l,id]:Q2[i]) { int pos=lower_bound(stk+1,stk+top+1,l+1)-stk,ret; if (l+1<=stk[pos]-1) ret=query_min(l+1,stk[pos]-1)+a[stk[pos]]; else ret=INF; ret=min(ret,SEG.query(pos+1,top)); ans[id]=min(ans[id],mx+ret); } } for (i=1;i<=q;++i) printf("%d\n",ans[i]); return 0; }

6|0Postscript


最近总是倒反天罡想要挑战Counting,每次都落得大败而归,还是老老实实去写那些不用动脑的题吧


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/18276353.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(225)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2019-06-30 Luogu P3265 [JLOI2015]装备购买
点击右上角即可分享
微信分享提示