AtCoder Regular Contest 174

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专题的搬题吧

posted @ 2024-03-19 16:21  空気力学の詩  阅读(78)  评论(0编辑  收藏  举报