ARC175 A~C 題解

Sorato發表於2024-04-07

ARC175A Spoon Taking Problem

題目大意

\(n\) 個人圍成一個環,第 \(i\) 個人左手邊是第 \(i\) 個勺子,右手邊是第 \(i\%n+1\) 個勺子。每個人的慣用手用一個字元 \(a_i=\) L/R/? 表示,即左手/右手/未知。

給定 \(1\sim n\) 的一個排列 \(P_1,\dots,P_n\) 表示這 \(n\) 個人行動的順序。第 \(i\) 個人行動時,若他兩邊的勺子都沒被拿走,他將拿走慣用手那邊的,否則拿走有勺子那邊的。

問存在多少種慣用手組合,使得每個人恰好拿到一個勺子。

Solve

有一個性質:所有人要麼都拿左手邊,要麼都拿右手邊的。因為對於相鄰的兩人,如果左邊的選左手,右邊的選右手,那麼他們之間的勺子就沒人拿了,顯然是非法的。

接下來考慮什麼情況會對答案產生貢獻。

當全部選左手時,如果一個人的右邊已經被選過,此時不管他的慣用手是什麼,他都只能選左手。所以有:

記答案為 \(ans\),初值為 \(1\)。用 \(vis_i\) 表示第 \(i\) 個勺子是否被選過。若 \(a_i=\) ?\(vis_{i\%+1}=1\),則 \(ans=ans\times2\)

什麼情況會無解呢?顯然,如果一個人慣用手為右手且到他行動時右手的勺子未被拿走,那麼他會選擇右手邊的,此時不符合全部選左手。即:

\(a_i=\) R\(vis_{i\%n+1}=0\),則 \(ans=0\)

全部右手時同理。

Code

#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\) 即左括號不夠,則我們從左端開始填,將前 \(n-cnt\) 個右括號改為左括號是最優的。
  • \(cnt>n\) 即右括號不夠,則我們從右端開始填,將後 \(cnt-n\) 個左括號改為右括號是最優的。

顯然這些操作二是必要的,代價為 \(|n-cnt|\times b\)

接下來,對於處理出的左右括號數量相等的序列,將 \(a\)\(2b\)\(\min\) 後,執行交換操作一定比執行修改操作要優,這是顯然的,因為修改一個元素一定要修改另一個元素使得左右括號數量相等,而這就相當於一次交換操作。

考慮將左括號設為 \(-1\),右括號設為 \(1\)。遍歷序列,記錄一個字首和 \(sum\)。若遍歷至第 \(i\) 位時 \(sum>0\),則說明需要進行操作,那麼考慮貪心:

將這個右括號與最後一個左括號交換一定是最優的。因為交換前後序列的所有括號匹配數一定不會減少且會加 \(1\)。而與任何一個其他位置的左括號交換,括號匹配數有可能減少/不變/加 \(1\)

Code

// 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\) 個區間 \([l_i,r_i]\)。構造一組 \(A_i\in[l_i,r_i]\),使得鄰的 \(A_i\) 的差的和,即 \(\sum\limits_{i=1}^{n-1}|A_i+A_{i+1}|\) 最小。若有多組解,輸出字典序最小的一組。

Solve

\(f_i(x)\) 表示前 \(i\) 個數,第 \(A_i=x\) 時的最小的相鄰兩項之差的和。那麼 \(f_1(x),x\in[l_1,r_1]=0\)。即 \(f_1(x)\) 的圖象是一條在 \(y\) 軸上的線段。

\(f_i(x),i\in[2,n]\) 的圖象顯然是這樣的圖象的一部分:

\(f_2(x)\) 為例,其圖象為:(此時 \([l_1,r_1]=[4,9],[l_2,r_2]=[1,12]\)

對於 \(x\in[l_2,r_2]\cap[l_1,r_1]=[4,9]\),顯然可以直接從 \(f_1(x)\) 上轉移過來,代價為 \(0\);否則,從\([l_1,r_1]\) 上最靠近 \(x\) 的點,即 \(l_1\)\(r_1\) 上轉移過來最優,代價為 \(l_1-x\)\(x-r_1\)(同無交時)。如果 \([l_2,r_2]\neq[1,12]\),那也不影響圖象的總體形狀,只是在上邊擷取一部分或延長一端。

而對於這麼個圖象,我們只需要記錄住它的拐點(即上圖中 \((4,0)\)\((9,0)\))就可以知道這個圖象長什麼樣子了。記 \([l_i,r_i]\) 的拐點為 \(a_i\)\(b_i\),則 \([a_i,b_i]\)\(f_i(x)\) 最小,考慮怎麼狀態轉移。

首先,我們有 \([a_1,b_1]=[l_1,r_1]\)

對於 \(i\in[2,n]\)

  • \(r_i<a_{i-1}\),即 \([l_i,r_i]\)\([a_{i-1},b_{i-1}]\) 無交且在其左側,則此時當且僅當 \(x=r_i\)\(f_i(x)_\min=f_{i-1}(l_{i-1})+l_{i-1}-r_i\),故 \(a_i=b_i=r_i\)
  • \(l_i>b_{i-1}\),即 \([l_i,r_i]\)\([a_{i-1},b_{i-1}]\) 無交且在其右側,同理有 \(a_i=b_i=l_i\)
  • 否則 \([l_i,r_i]\)\([a_{i-1},b_{i-1}]\) 有交,則對於 \(x\in[l_i,r_i]\cap[a_{i-1},b_{i-1}]\)\(f_i(x)_\min=f_{i-1}(x)\)。故 \([a_i,b_i]=[l_i,r_i]\cap[a_{i-1},b_{i-1}]\),即 \(a_i=\max(l_i,a_{i-1})\)\(b_i=\min(r_i,b_{i-1})\)

處理完拐點之後,我們來確定這個字典序最小的序列,但這時候就有一個問題:\([a_i,b_i]\) 是由 \([a_{i-1},b_{i-1}]\) 轉移來的,但時我們確定字典序是按照從 \(1\)\(n\) 的順序確定的,這樣就會有後效性。所以我們在處理 \([a_i,b_i]\) 時令 \([a_n,b_n]=[l_n,r_n]\),倒著轉移即可。

倒著轉移完成後,顯然 \(A_1=a_1\)。那麼在確定 \(A_2\) 的時候,就變成了這樣一個問題:

如圖,在 \(f_0\) 上給定一點 \(P\in\{A,B,C,D,E\}\),在 \(f_2(x)\) 上找一點 \((x,f_2(x))\),使得 \(g_2(x)=f_2(x)+|x-x_P|\) 最小,若有多個合法的 \(x\),取最小的。

  • \(P=A\in{(-\infty,l_2]}\),對於 \(x\in[l_2,a_2]\)\(g_2(x)\) 為定值。因為 \(x\) 每減小 \(1\)\(f_2(x)\) 就增大 \(1\)\(|x-x_A|\) 減小 \(1\),和不變。所以應取 \(x=l_2\)
  • \(P=B\in[l2,a_2]\),對於 \(x\in[x_B,a_2]\)\(g_2(x)\) 為定值,原因同上。故應取 \(x=x_B\)
  • \(P=C\in[a_2,b_2]\),顯然當 \(x=x_C\)\(g_2(x)\) 最小,應取 \(x=x_C\)
  • \(P=D\in[b_2,r_2]\),對於 \(x\in[b_2,x_D]\)\(g_2(x)\) 為定值,原因與第一種情況類似。應取 \(x=b_2\)
  • \(P=E\in[r_2,\infty)\),對於 \(x\in[b_2,r_2]\)\(g_2(x)\) 為定值。應取 \(x=b_2\)

整理一下並從 \(A_2\) 推廣到所有 \(A_i,i\in[2,n]\),有:\(A_i=\begin{cases}l_i&(A_{i-1}<l_i)\\A_{i-1}&(l_i\leq A_{i-1}\leq b_i)\\b_i&(A_{i-1}>b_i)\end{cases}\)

Code

(程式碼中 \([a_i,b_i]\)\([l_i,r_i]\) 意義相反。)

// 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\in[b,c]\) 則返回 \(a\),否則返回 \(b,c\) 中與 \(a\) 的差較小的一個。