Soratosorato

ARC175 A~C 题解

Sorato·2024-04-07 12:58·43 次阅读

ARC175 A~C 题解

ARC175A Spoon Taking Problem#

题目大意#

n 个人围成一个环,第 i 个人左手边是第 i 个勺子,右手边是第 i%n+1 个勺子。每个人的惯用手用一个字符 ai= L/R/? 表示,即左手/右手/未知。

给定 1n 的一个排列 P1,,Pn 表示这 n 个人行动的顺序。第 i 个人行动时,若他两边的勺子都没被拿走,他将拿走惯用手那边的,否则拿走有勺子那边的。

问存在多少种惯用手组合,使得每个人恰好拿到一个勺子。

Solve#

有一个性质:所有人要么都拿左手边,要么都拿右手边的。因为对于相邻的两人,如果左边的选左手,右边的选右手,那么他们之间的勺子就没人拿了,显然是非法的。

接下来考虑什么情况会对答案产生贡献。

当全部选左手时,如果一个人的右边已经被选过,此时不管他的惯用手是什么,他都只能选左手。所以有:

记答案为 ans,初值为 1。用 visi 表示第 i 个勺子是否被选过。若 ai= ?visimodn+1=1,则 ans=ans×2

什么情况会无解呢?显然,如果一个人惯用手为右手且到他行动时右手的勺子未被拿走,那么他会选择右手边的,此时不符合全部选左手。即:

ai= Rvisimodn+1=0,则 ans=0

全部右手时同理。

Code#

Copy
#include<bits/stdc++.h> #pragma GCC optimize(1,2,3,"Ofast","inline") using namespace std; #define int long long #define mod 998244353 inline int read() { short f=1; int x=0; char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } inline int readc() { char c=getchar(); while(c==' '||c=='\n') c=getchar(); return c; } int n,p[200010],ans; char a[200010]; bool vis[200010]; inline int calc(int op/*全部左(0)/右(1)手*/) { int sum=1; for(int i=1;i<=n;i=-~i) vis[i]=0; vis[p[1]+op]=1; for(int i=2;i<=n;i=-~i) { if((!vis[p[i]%n+1]&&!op&&a[p[i]]=='R') ||(!vis[p[i]]&&op&&a[p[i]]=='L')) return 0; if((vis[p[i]%n+1]&&!op&&a[p[i]]=='?') ||(vis[p[i]]&&op&&a[p[i]]=='?')) sum=(sum<<1)%mod; if(!op) vis[p[i]]=1; else vis[p[i]%n+1]=1; } return sum; } signed main() { n=read(); for(int i=1;i<=n;i=-~i) p[i]=read(); for(int i=1;i<=n;i=-~i) a[i]=readc(); if(a[p[1]]=='L'||a[p[1]]=='?') ans+=calc(0); if(a[p[1]]=='R'||a[p[1]]=='?') ans=(ans+calc(1))%mod; return printf("%lld",ans),0; }

ARC175B Parenthesis Arrangement#

题目大意#

给定一个长度为 2n 括号序列,对这个括号序列进行若干次以下操作,使得括号匹配。

  • 交换序列中的任意两个元素,代价为 a
  • 修改序列中的任意一个元素,代价为 b

Solve#

小贪一波。

首先记序列中左括号个数为 cnt,则:

  • cnt<n 即左括号不够,则我们从左端开始填,将前 ncnt 个右括号改为左括号是最优的。
  • cnt>n 即右括号不够,则我们从右端开始填,将后 cntn 个左括号改为右括号是最优的。

显然这些操作二是必要的,代价为 |ncnt|×b

接下来,对于处理出的左右括号数量相等的序列,将 a2bmin 后,执行交换操作一定比执行修改操作要优,这是显然的,因为修改一个元素一定要修改另一个元素使得左右括号数量相等,而这就相当于一次交换操作。

考虑将左括号设为 1,右括号设为 1。遍历序列,记录一个前缀和 sum。若遍历至第 i 位时 sum>0,则说明需要进行操作,那么考虑贪心:

将这个右括号与最后一个左括号交换一定是最优的。因为交换前后序列的所有括号匹配数一定不会减少且会加 1。而与任何一个其他位置的左括号交换,括号匹配数有可能减少/不变/加 1

Code#

Copy
// LUOGU_RID: 153163957 #include<bits/stdc++.h> #pragma GCC optimize(1,2,3,"Ofast","inline") using namespace std; #define int long long inline int read() { short f=1; int x=0; char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,a,b,cnt,ans,sum=0,res=0; string s; signed main() { n=read();a=read();b=read(); a=min(a,b<<1);cin>>s; for(int i=0;i<s.size();i=-~i) if(s[i]=='(') cnt=-~cnt; if(cnt<n) for(int i=0,j=0;i<s.size()&&j<n-cnt;i=-~i) if(s[i]==')') s[i]='(',j=-~j; if(cnt>n) for(int i=s.size()-1,j=0;i&&j<cnt-n;i--) if(s[i]=='(') j=-~j,s[i]=')'; for(int l=0,r=s.size()-1;l<s.size()&&l<r;l=-~l) { if(s[l]=='(') sum--; else sum=-~sum; if(sum>0) { while(s[r]==')'&&r>l) r--; if(r>l) res=-~res,sum-=2; } } return printf("%lld",abs(n-cnt)*b+res*a),0; }

ARC175C Jumping Through Intervals#

题目大意#

给定 n 个区间 [li,ri]。构造一组 Ai[li,ri],使得邻的 Ai 的差的和,即 i=1n1|Ai+Ai+1| 最小。若有多组解,输出字典序最小的一组。

Solve#

fi(x) 表示前 i 个数,第 Ai=x 时的最小的相邻两项之差的和。那么 f1(x),x[l1,r1]=0。即 f1(x) 的图象是一条在 y 轴上的线段。

fi(x),i[2,n] 的图象显然是这样的图象的一部分:

f2(x) 为例,其图象为:(此时 [l1,r1]=[4,9],[l2,r2]=[1,12]

对于 x[l2,r2][l1,r1]=[4,9],显然可以直接从 f1(x) 上转移过来,代价为 0;否则,从[l1,r1] 上最靠近 x 的点,即 l1r1 上转移过来最优,代价为 l1xxr1(同无交时)。如果 [l2,r2][1,12],那也不影响图象的总体形状,只是在上边截取一部分或延长一端。

而对于这么个图象,我们只需要记录住它的拐点(即上图中 (4,0)(9,0))就可以知道这个图象长什么样子了。记 [li,ri] 的拐点为 aibi,则 [ai,bi]fi(x) 最小,考虑怎么状态转移。

首先,我们有 [a1,b1]=[l1,r1]

对于 i[2,n]

  • ri<ai1,即 [li,ri][ai1,bi1] 无交且在其左侧,则此时当且仅当 x=rifi(x)min=fi1(li1)+li1ri,故 ai=bi=ri
  • li>bi1,即 [li,ri][ai1,bi1] 无交且在其右侧,同理有 ai=bi=li
  • 否则 [li,ri][ai1,bi1] 有交,则对于 x[li,ri][ai1,bi1]fi(x)min=fi1(x)。故 [ai,bi]=[li,ri][ai1,bi1],即 ai=max(li,ai1)bi=min(ri,bi1)

处理完拐点之后,我们来确定这个字典序最小的序列,但这时候就有一个问题:[ai,bi] 是由 [ai1,bi1] 转移来的,但时我们确定字典序是按照从 1n 的顺序确定的,这样就会有后效性。所以我们在处理 [ai,bi] 时令 [an,bn]=[ln,rn],倒着转移即可。

倒着转移完成后,显然 A1=a1。那么在确定 A2 的时候,就变成了这样一个问题:

如图,在 f0 上给定一点 P{A,B,C,D,E},在 f2(x) 上找一点 (x,f2(x)),使得 g2(x)=f2(x)+|xxP| 最小,若有多个合法的 x,取最小的。

  • P=A(,l2],对于 x[l2,a2]g2(x) 为定值。因为 x 每减小 1f2(x) 就增大 1|xxA| 减小 1,和不变。所以应取 x=l2
  • P=B[l2,a2],对于 x[xB,a2]g2(x) 为定值,原因同上。故应取 x=xB
  • P=C[a2,b2],显然当 x=xCg2(x) 最小,应取 x=xC
  • P=D[b2,r2],对于 x[b2,xD]g2(x) 为定值,原因与第一种情况类似。应取 x=b2
  • P=E[r2,),对于 x[b2,r2]g2(x) 为定值。应取 x=b2

整理一下并从 A2 推广到所有 Ai,i[2,n],有:Ai={li(Ai1<li)Ai1(liAi1bi)bi(Ai1>bi)

Code#

(代码中 [ai,bi][li,ri] 意义相反。)

Copy
// LUOGU_RID: 153163957 #include<bits/stdc++.h> #pragma GCC optimize(1,2,3,"Ofast","inline") using namespace std; #define int long long inline int read() { short f=1; int x=0; char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int a[500010],b[500010],l[500010],r[500010],n,ans[500010]; signed main() { n=read(); for(int i=1;i<=n;i=-~i) a[i]=read(),b[i]=read(); l[n]=a[n];r[n]=b[n]; for(int i=n-1;i;i--) { if(b[i]<l[-~i]) l[i]=r[i]=b[i]; else if(a[i]>r[-~i]) l[i]=r[i]=a[i]; else l[i]=max(l[-~i],a[i]),r[i]=min(r[-~i],b[i]); } printf("%lld ",ans[1]=l[1]); for(int i=2;i<=n;i=-~i) printf("%lld ",ans[i]=clamp(ans[i-1],a[i],r[i])); return 0; }

注:clamp(a,b,c):若 a[b,c] 则返回 a,否则返回 b,c 中与 a 的差较小的一个。

posted @   Sorato  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示
目录