AtCoder Regular Contest 174

空気力学の詩發表於2024-03-19

Preface

這場當時在和同學開黑打LOL都不知道有這回事,後面看徐神和祁神在討論才知道有ARC

賽後補了下發現中規中矩,D題在已知是個打表題的前提下很好做,但如果真在比賽的時候是否能用有限的時間找到規律也是存疑


A - A Multiply

找出原序列的最大/最小子段和,討論下操作哪個得到的結果最優即可

#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 int long long
#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=300005;
int n,c,a[N],sum,mx,mn,cur1,cur2;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%lld%lld",&n,&c),i=1;i<=n;++i)
	scanf("%lld",&a[i]),sum+=a[i];
	for (RI i=1;i<=n;++i)
	{
		cur1=max(0LL,cur1+a[i]); mx=max(mx,cur1);
		cur2=min(0LL,cur2+a[i]); mn=min(mn,cur2);
	}
	return printf("%lld",max(sum+(c-1)*mx,sum+(c-1)*mn)),0;
}

B - Bought Review

將題意稍做轉化,令\(S=2\times A_1+A_2-A_4-2\times A_5\),我們現在的目標就是要將\(S\)變為\(\le 0\)

操作為花費\(P_4\)的代價使\(S\)\(1\);或者花費\(P_5\)的代價使\(S\)\(2\)

不難發現最優方案要麼全部用其中一種操作,要麼用一次減\(1\)用若干次減\(2\)(當\(S\)為奇數時)

#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 int long long
#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=300005;
int t,a[10],p[10];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; int cnt=0,sum=0;
		for (i=1;i<=5;++i) scanf("%lld",&a[i]);
		for (i=1;i<=5;++i) scanf("%lld",&p[i]);
		int tmp=a[1]*2+a[2]-a[4]-a[5]*2;
		if (tmp<=0) { puts("0"); continue; }
		printf("%lld\n",min({tmp*p[4],(tmp+1)/2*p[5],tmp/2*p[5]+(tmp%2)*p[4]}));
	}
	return 0;
}

C - Catastrophic Roulette

經典ARC數數題,但這個還行閃總都會做

注意到\(n\)的範圍可以列舉,因此一眼考慮DP,設\(f_{i,0/1}\)表示第一次出現\(i\)個數時,當前操作者是先手/後手的機率

對於狀態\(f_{i,0}\)\(f_{i,1}\)同理),直接來看它有\(P=\frac{i}{n}\)的機率轉移到\(f_{i,1}\),並對先手產生\(1\)的花費;同時有\(1-P\)的機率轉移到\(f_{i+1,1}\)

注意到這個DP轉移的時候會在\(i\)相同的兩個狀態間迴圈轉移,乍一看感覺不好處理,但仔細一想會發現貢獻其實都是等比數列形式

\(f_{i,0}\)的轉移為例,在經過了無限次操作後顯然它必然會到達\(f_{i+1,0/1}\)中的一個,手玩一下機率會發現有:

  • \(f_{i+1,1}\leftarrow f_{i,0}\times \frac{1}{1+P}\),同時對於先手的花費期望貢獻為\(f_{i,0}\times \frac{P}{1-P^2}\)
  • \(f_{i+1,0}\leftarrow f_{i,0}\times \frac{P}{1+P}\),同時對於後手的花費期望貢獻為\(f_{i,0}\times \frac{P^2}{1-P^2}\)

對於這類狀態可能重複多次的問題,不妨稍微修改定義,只統計第一次遇到某個狀態時的機率/期望,問題就迎刃而解了

#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=1e6+5,mod=998244353;
int n,f[N][2],ans[2];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; scanf("%d",&n); int inv_n=quick_pow(n);
	for (f[1][1]=i=1;i<n;++i) for (j=0;j<2;++j)
	{
		int p=1LL*i*inv_n%mod;
		(ans[j]+=1LL*f[i][j]*p%mod*quick_pow((1LL-1LL*p*p%mod+mod)%mod)%mod)%=mod;
		(ans[j^1]+=1LL*f[i][j]*p%mod*p%mod*quick_pow((1LL-1LL*p*p%mod+mod)%mod)%mod)%=mod;
		(f[i+1][j^1]+=1LL*f[i][j]*quick_pow((1+p)%mod)%mod)%=mod;
		(f[i+1][j]+=1LL*f[i][j]*p%mod*quick_pow((1+p)%mod)%mod)%=mod;
	}
	return printf("%d %d",ans[0],ans[1]),0;
}

D - Digit vs Square Root

看到題目直覺告訴我們符合條件的\(x\)感覺不會很多,手玩一下會發現確實如此

因此考慮打表來找合法的\(x\)是否存在某些規律,不過直接暴枚\(x\)的話複雜度會有點炸(雖然感覺來了一樣能看出規律),故考慮列舉\(y\)來判斷其合法的區間

首先\(x\in[y^2,(y+1)^2-1]\)是trivial的,考慮要滿足\(y\)\(x\)的字首的性質,這個只要找出\(\le y^2\)且以\(y\)為字首的最小數以及\(\le (y+1)^2-1\)且以\(y\)為字首的最大數即可

前者很好處理,我們在\(y\)後面補\(0\)直到其值\(\ge y^2\)即可,後者的話需要一點小討論,具體可以看打表程式碼

#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 int long long
#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=1e6;
signed main()
{
	freopen("BF.txt","w",stdout);
	printf("{%lld,%lld},",1LL,1LL);
	for (RI y=4;y<=N;++y)
	{
		int l=y*y,r=(y+1)*(y+1)-1;
		int tl=y,tr=r; while (tl<l) tl*=10;
		auto calc_len=[&](int x)
		{
			int len=0; while (x>0) x/=10,++len; return len;
		};
		int len_y=calc_len(y),len_r=calc_len(r);
		for (RI i=1;i<=len_r-len_y;++i) tr/=10;
		if (tr>y)
		{
			tr=y; for (RI i=1;i<=len_r-len_y;++i) tr=tr*10+9;
		} else if (tr<y)
		{
			tr=y; for (RI i=1;i<len_r-len_y;++i) tr=tr*10+9;
		} else tr=r;
		if (tl<=tr) printf("{%lld,%lld},",tl,tr);
	}
	return 0;
}

把表打出來後就會發現顯而易見的規律,即除了\([1,1]\)合法的區間就兩種形式:

  • 單個數:末尾\(i\)\(0\),開頭是\(i-1\)\(9\)再接上一個\(8\),如\(\{998000\},\{99980000\}\)
  • 一段區間:左端點為\(i\)\(9\)接上\(i\)\(0\);右端點為\(10^{2i}+10^{i}-1\),如\([999000,1000999],[99990000,100009999]\)

不難發現合法的區間個數很少,預先處理出來後詢問的時候直接列舉每個區間即可

#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 int long long
#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;
int t,n,pw[20];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (pw[0]=i=1;i<=18;++i) pw[i]=pw[i-1]*10;
	vector <pi> table={{1,1}};
	for (i=1;i<=9;++i)
	{
		int x=0; for (j=1;j<i;++j) x=x*10+9; x=x*10+8;
		for (j=1;j<=i;++j) x*=10; table.push_back({x,x});
		x=0; for (j=1;j<=i;++j) x=x*10+9;
		for (j=1;j<=i;++j) x*=10;
		if (i==9) table.push_back({x,pw[18]}); else table.push_back({x,pw[i]+pw[2*i]-1});
	}
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld",&n); int ans=0;
		for (auto [l,r]:table) if (n>=l) ans+=min(n,r)-l+1; else break;
		printf("%lld\n",ans);
	}
	return 0;
}

Postscript

後面E題雖然過的人挺多,但看到Counting一眼寄,還是去準備準備DS專題的搬題吧