Codeforces1043 A至F 题解

22:13:登录进 Codeforces。

22:15:登录进洛谷,发现今日忌睡觉:

22:35:比赛开始,打开了 A 题。

22:37:发现 Google 翻译太难用了,于是换成了 AI 翻译。

22:38:发现 A 题是一道水题,题面如下:

很明显,直接暴力你就可以得到一个 O(tm)O(tm)O(tm) 的"高效"做法。

22:41:写完并调试完代码,直接 AC。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,m;
string s1,s2,s3;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		cin>>s1;
		cin>>m;
		cin>>s2>>s3;
		string ans="";
		for(int i=m-1;i>=0;i--)
		{
			if(s3[i]=='V')
			{
				ans+=s2[i];
			}
		}
		ans+=s1;
		for(int i=0;i<m;i++)
		{
			if(s3[i]=='D')
			{
				ans+=s2[i];
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}

22:42:打开 B 题,发现又是一道水题。

题面:

因为往一个数后面加 000 实际上就是给这个数乘了一个 10k10^k10k,所以新得到的这个数就是 x+x×10k=x×(10k+1)x+x\times10^k=x\times(10^k+1)x+x×10k=x×(10k+1),那明显 kkk 是可以枚举的(因为顶天了也就枚举 lg⁡(n)\lg(n)lg(n) 次),然后看看 nnn 能不能整除 10k+110^k+110k+1,如果能,就整除后把得到的值记录下来,最后排个序输出一下就好了(在这里为了方便,我用的 set 排序,主要是正常的 sort 是 O(nlog⁡2(n))O(n\log_2(n))O(nlog2(n)),而 set 则是 O(log⁡2(n))O(\log_2(n))O(log2(n)) 的,这样可以防止 TLE)。

22:46:写完并调试完 B 题代码,直接提交 AC。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		int k=1;
		set<int>s;
		while(k+1<=n)
		{
			k*=10;
			if(n%(k+1)==0)
			{
				s.insert(n/(k+1));
			}
		}
		cout<<s.size()<<'\n';
		for(auto i:s)
		{
			cout<<i<<" ";
		}
		if(s.size()!=0)
		{
			cout<<'\n';
		}
	}
	return 0;
}

22:47:打开 C1 题,猜出用三进制解。

题面:

22:50:纠结是否用三进制写。

22:54:决定直接写代码,懒得证明。

因为小贩每次只能卖出 3x3^x3x 西瓜,所以很明显:这题是让我们把 nnn 分解成 a1×3x1+a2×3a2⋯+ak×3xka_1\times3^{x_1}+a_2\times3^{a_2}\dots+a_k\times3^{x_k}a1×3x1+a2×3a2⋯+ak×3xk,并且要求 ∑i=1kai\sum_{i=1}^ka_i∑i=1kai 要最小。那很容易想到把 nnn 三进制分解了,再按每一位的值(aia_iai)计算花的金币个数(当然这是我猜的,具体原因我也没证出来为什么这样 ∑i=1kai\sum_{i=1}^ka_i∑i=1kai 最小)。

我想起了我们同学的一句话:大胆猜想,不用证明!(不值得借鉴)于是我就凭直觉写了份代码,然后 AC 了。

23:02:C1 题 AC 了。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,a[26],b[26];
signed main()
{
	b[0]=1;
	for(int i=1;i<=20;i++)
	{
		b[i]=b[i-1]*3;
	}
	a[0]=b[1];
	for(int i=1;i<20;i++)
	{
		a[i]=b[i+1]+i*b[i-1];
	}
	cin>>t;
	while(t--)
	{
		int ans=0,pos=0;
		cin>>n;
		while(n)
		{
			ans+=(n%3)*a[pos];
			n/=3;
			pos++;
		}
		cout<<ans<<'\n';
	}
	return 0;
}

23:03:打开 C2 题,发现就是把 C1 题改变了一下。

题面:

对于这题,我们首先要从收金币的式子入手:

3x+1+x⋅3x−13^{x+1}+x\cdot3^{x-1}3x+1+x⋅3x−1

我们看看进行三次买 3x−13^{x-1}3x−1 个西瓜的操作和进行一次买 3x3^x3x 个西瓜的操作所花的金币有什么区别:

  • 买了三次 3x−13^{x-1}3x−1 的西瓜:

3×(3x+(x−1)⋅3x−2)=3x+1+(x−1)⋅3x−13\times(3^{x}+(x-1)\cdot3^{x-2})=3^{x+1}+(x-1)\cdot3^{x-1}3×(3x+(x−1)⋅3x−2)=3x+1+(x−1)⋅3x−1

  • 买了一次 3x3^x3x 个西瓜:

3x+1+x⋅3x−13^{x+1}+x\cdot3^{x-1}3x+1+x⋅3x−1

仔细一对比就不难发现:买三次 3x−13^{x-1}3x−1 个西瓜比买一次 3x3^x3x 个西瓜要便宜 3x−13^{x-1}3x−1 个金币 (生活小妙招,你值得拥有),那我们现在要让价格最低,就是要让我们能便宜的钱最多,很明显,3x−13^{x-1}3x−1 这个指数型函数是单调递增的,所以 xxx 越大,3x−13^{x-1}3x−1 也就越大,所以综上:我们就是要尽量的用小的西瓜的个数代替大的西瓜的个数,并且大的个数越大越好。

那么我们在 C1 题的代码上改变一下:用一个数组记录一下三进制分解完后的数,然后从高位到低位尽量的把数往下传,最后算答案。

23:22:AC 了 C2 题。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,k,a[26],b[26],c[26];
signed main()
{
	b[0]=1;
	for(int i=1;i<=20;i++)
	{
		b[i]=b[i-1]*3;
	}
	a[0]=b[1];
	for(int i=1;i<20;i++)
	{
		a[i]=b[i+1]+i*b[i-1];
	}
	cin>>t;
	while(t--)
	{
		int ans=0,pos=0,cnt=0;
		cin>>n>>k;
		while(n)
		{
			c[++pos]=n%3;
			cnt+=c[pos];
			n/=3;
		}
		if(cnt>k)
		{
			cout<<-1<<'\n';
		}
		else if(cnt==k)
		{
			for(int i=1;i<=pos;i++)
			{
				ans+=c[i]*a[i-1];
			}
			cout<<ans<<'\n';
		}
		else
		{
			for(;pos>1&&cnt+c[pos]*2<=k;)
			{
				c[pos-1]+=c[pos]*3;
				cnt+=c[pos]*2;
				c[pos]=0;
				pos--;
			}
			if(pos>1)
			{
				c[pos]-=(k-cnt)/2;
				c[pos-1]+=(k-cnt)/2*3;
			}
			for(int i=1;i<=pos;i++)
			{
				ans+=c[i]*a[i-1];
			}
			cout<<ans<<'\n';
		}
	}
	return 0;
}

23:33:打开了 D 题,发现是一道毒瘤数学题。

然后就推了一个小时的数学公式。

最终时间复杂度:O(t)O(t)O(t) 左右(可能带个小常数)。

首先先声明:我的做法有点与众不同,也有点抽象,请谨慎观看。

首先我们肯定要确定最后一个数是什么,那在这之前因为我们有的只是前面几项的数字位数之和,所以我们要先解决最后一个数是多少位这个问题。

我们先来做个小统计:

  • 一位数的个数:999 个。
  • 两位数的个数:909090 个。
  • 三位数的个数:900900900 个。
  • ddd 位数的个数:9×10d−19\times10^{d-1}9×10d−1

然后我们求一下到第 ddd 位数字一共有多少位数:

1×9+2×90+3×900⋯+d×10d−11\times9+2\times90+3\times900\dots+d\times10^{d-1}1×9+2×90+3×900⋯+d×10d−1

本来到这暴力枚举就行了,但闲不下来的作者非要搞事情,于是我把这个式子设成了 SSS,即:

S=∑i=1d9×i×10i−1S=\sum_{i=1}^d9\times i\times10^{i-1}S=i=1∑d9×i×10i−1

提个公因数:

S=9×∑i=1di×10i−1S=9\times\sum_{i=1}^di\times10^{i-1}S=9×i=1∑di×10i−1

然后把后半部分设成一个新的函数 TTT,则:

T=∑i=1di×10i−1T=\sum_{i=1}^di\times10^{i-1}T=i=1∑di×10i−1

然后我们试着用数学公式把 TTT 表示出来。

首先对 TTT 乘以一个 101010:

10T=∑i=1di×10i10T=\sum_{i=1}^di\times10^i10T=i=1∑di×10i

两式相减得:

9T=d×10d−100−101−102−⋯−10d−19T=d\times10^d-10^0-10^1-10^2-\dots-10^{d-1}9T=d×10d−100−101−102−⋯−10d−1\

看到这让我想起了一个数竞里的公式:

an−bn=(a−b)(an−1+an−1b+⋯+bn−1)a^n-b^n=(a-b)(a^{n-1}+a^{n-1}b+\dots+b^{n-1})an−bn=(a−b)(an−1+an−1b+⋯+bn−1)

于是我干了这么一件事:

9T=d×10d−(100+101+⋯+10d−1)9T=d\times10^d-(10^0+10^1+\dots+10^{d-1})9T=d×10d−(100+101+⋯+10d−1)

81T=9×d×10d−(10−1)(100+101+⋯+10d−1)81T=9\times d\times10^d-(10-1)(10^0+10^1+\dots+10^{d-1})81T=9×d×10d−(10−1)(100+101+⋯+10d−1)

81T=(10−1)×d×10d−(10d−1)81T=(10-1)\times d\times10^d-(10^d-1)81T=(10−1)×d×10d−(10d−1)

81T=d×10d+1−d×10d−10d+181T=d\times10^{d+1}-d\times10^d-10^d+181T=d×10d+1−d×10d−10d+1

81T=d×10d+1−(d+1)×10d+181T=d\times10^{d+1}-(d+1)\times10^d+181T=d×10d+1−(d+1)×10d+1

T=d×10d+1−(d+1)×10d+181T=\cfrac{d\times10^{d+1}-(d+1)\times10^d+1}{81}T=81d×10d+1−(d+1)×10d+1

然后我们不难发现:这个 101010 的幂是可以预处理的,这个 TTT本身也是可以证明出来是单调递增的,于是我们可以使用二分查找 ddd 的值,然后带进去看看当前的总位数是否 ≥k\ge k≥k,这样下来时间复杂度就优化了一个 log⁡\loglog。

接下来就是算最后一个数字了,这里我不过多讲怎么算(笑话,位数都给你了,还能算不出来),不会的自己看代码。

然后再算 kkk 指向的是最后一个数字的哪一位,这里也不过多讲,不会的看代码。

最终到了统计环节,很容易想到用一个数位 DP 就可以解决,但爱整花活的作者偏不。我先把最后一个数字单独统计了一下(因为最后一个数字不是所有数位都要),然后就开始算从 1→ed−11\to ed-11→ed−1 的每一位的值的和。

我是这样做的:首先把一个数字拆成三部分:高位(h)、当前要计数的位(m)和低位(l)。

然后我们就看看那个当前计数的位能贡献出多少的值来。

首先如果高位取的值是 0→h−10\to h-10→h−1,那么当前这一位是 0→90\to90→9 随便选的,而且低位也是:

这时不难发现:高位一共有 hhh 种情况,低位一共有 10d10^d10d(其中 ddd 表示低位的位数)种情况,而当前的位置是 0→90\to90→9 随便选的,共计贡献 454545,而这种情况一共有 h×10dh\times10^dh×10d 个,所以第一种情况的答案就是 45×h×10d45\times h\times10^d45×h×10d。

其次就是高位取得 hhh,那么当前这一位可以取 0→m−10\to m-10→m−1,用高斯求和公式就可以知道贡献是 m(m−1)2\cfrac{m(m-1)}{2}2m(m−1),而低位还是 10d10^d10d 种情况随便选,因此这种情况的贡献是 m(m−1)2×10d\cfrac{m(m-1)}{2}\times10^d2m(m−1)×10d。

最后就是中间那一位也是 mmm,那么低位就只能选 0→l0\to l0→l 了,一共 l+1l+1l+1 种情况,而这是中间那一位数只能取 mmm,所以此时贡献是 m(l+1)m(l+1)m(l+1)。

综上,对于当前这一位的总贡献是 45h×10d+m(m−1)2×10d+m(l+1)45h\times10^d+\cfrac{m(m-1)}{2}\times10^d+m(l+1)45h×10d+2m(m−1)×10d+m(l+1)。这下又省了点空间复杂度。

然后就可以码代码了。

00:45:推出所有公式并写完代码,最后一发直接 AC。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,k;
int T(int x)
{
	int p=1,q=1;
	for(int i=1;i<=x+1;i++)
	{
		p*=10;
		q*=10;
	}
	q/=10;
	p*=x;
	q*=(x+1);
	p-=q;
	p++;
	int r=p/81;
	return r;
}
int S(int x)
{
	return 9*T(x);
}
int qpow(int x,int y)
{
	int z=1;
	for(int i=1;i<=y;i++)
	{
		z*=x;
	}
	return z;
}
int sum(int x)
{
	if(x==0)
	{
		return 0;
	}
	int p=1,ans=0;
	for(;p<=x;p*=10)
	{
		int h=x/(p*10);
		int m=x/p%10;
		int l=x%p;
		ans+=h*p*45;
		ans+=m*(m-1)/2*p;
		ans+=(l+1)*m;
	}
	return ans;
}
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>k;
		int l=1,r=20,d=0,ans=0;
		while(l<=r)
		{
			int mid=l+r>>1;
			if(S(mid)>=k)
			{
				r=mid-1;
				d=mid;
			}
			else
			{
				l=mid+1;
			}
		}
		int pre=S(d-1);
		int oth=k-pre;
		int ed=(oth-1)/d+qpow(10,d-1);
		int pos=(oth-1)%d;
		string s=to_string(ed);
		for(int i=0;i<=pos;i++)
		{
			ans+=(s[i]-'0');
		}
		cout<<ans+sum(ed-1)<<'\n';
	}
	return 0;
}

然后比赛结束了。

至于 E 题,是比赛结束后我再打的,这里也简单讲一下。

首先我们要让综合最大,用点贪心思想就知道肯定要从大到小排序,然后尽量选大的,因此我们做一个前缀和。

现在我们有了两个前缀和 prea,prebprea,prebprea,preb,我们假设从 a 中选出了 iii 个数,那 bbb 中就只能选 z−iz-iz−i 个数,所以我们就是要求 max⁡{preai+prebz−i}\max\{prea_i+preb_{z-i}\}max{preai+prebz−i}。

那 iii 的范围怎么求呢?

很明显,iii 要满足两个条件:

{0≤i≤min⁡{z,x}0≤z−i≤min⁡{z,y}\begin{cases}0\le i\le\min\{z,x\}\\0\le z-i\le\min\{z,y\}\end{cases}{0≤i≤min{z,x}0≤z−i≤min{z,y}

我们对第二个式子移一下项就可以得到:

i≥max⁡{0,z−y}i\ge\max\{0,z-y\}i≥max{0,z−y}

于是 iii 的范围就是 max⁡{0,z−y}≤i≤min⁡{z,x}\max\{0,z-y\}\le i\le\min\{z,x\}max{0,z−y}≤i≤min{z,x}。

如果你直接暴力枚举这个区间,你会获得一个大大的 TLE(不要问我怎么知道的)。

因此我们需要优化它,怎么优化呢?

看到标签中的三分,我突然灵光一闪:可不可以用三分解?

简单证明了一下函数单调性,发现可以(这里留给读者自己去证,如果没学过三分,自己去查资料)!

于是我们可以使用三分优化缩小这个区间再暴力枚举(我这里定的范围缩小到差小于等于 555)。

于是就可以完美的 AC 这道题了。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,m,q;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>m>>q;
		vector<int>a(n+6);
		vector<int>b(m+6);
		for(int i=0;i<n;i++)
		{
			cin>>a[i];
		}
		for(int i=0;i<m;i++)
		{
			cin>>b[i];
		}
		sort(a.begin(),a.end(),[&](int x,int y)
		{
			return x>y;
		});
		sort(b.begin(),b.end(),[&](int x,int y)
		{
			return x>y;
		});
		vector<int>s1(n+6);
		vector<int>s2(m+6);
		s1[1]=a[0];
		for(int i=2;i<=n;i++)
		{
			s1[i]=s1[i-1]+a[i-1];
		}
		s2[1]=b[0];
		for(int i=2;i<=m;i++)
		{
			s2[i]=s2[i-1]+b[i-1];
		}
		while(q--)
		{
			int x,y,z,ans=0;
			cin>>x>>y>>z;
			int l=max(0ll,z-y),r=min(x,z),mid1,mid2;
			l=max(l,z-m);
			r=min(r,z);
			r=min(r,n);
			while(r-l>=5)
			{
				mid1=(l+l+r)/3;
				mid2=(l+r+r)/3;
				if(s1[mid1]+s2[z-mid1]<s1[mid2]+s2[z-mid2])
				{
					l=mid1;
				}
				else
				{
					r=mid2;
				}
			}
			for(int i=l;i<=r;i++)
			{
				if(i<=n&&z-i<=m)
				{
					ans=max(ans,s1[i]+s2[z-i]);
				}
			}
			cout<<ans<<'\n';
		}
	}
	return 0;
}

F 题,据说很简单,但还没写出代码,这里先挖个坑。

相关推荐
摸鱼一级选手5 分钟前
十大经典 Java 算法解析与应用
java·算法·排序算法
Ldawn_AI2 小时前
4+ 图论高级算法
算法·深度优先·图论
Xの哲學2 小时前
Linux PCI 子系统:工作原理与实现机制深度分析
linux·网络·算法·架构·边缘计算
NuyoahC4 小时前
笔试——Day46
c++·算法·笔试
Keying,,,,4 小时前
力扣hot100 | 图论 | 200. 岛屿数量、994. 腐烂的橘子、207. 课程表、208. 实现 Trie (前缀树)
算法·leetcode·图论
楼田莉子5 小时前
C++算法学习专题:滑动窗口
开发语言·数据结构·c++·学习·算法·leetcode
2501_924731116 小时前
智慧矿山误报率↓83%!陌讯多模态融合算法在矿用设备监控的落地优化
人工智能·算法·目标检测·视觉检测
zh_xuan7 小时前
LeeCode 40.组合总和II
c语言·数据结构·算法
都叫我大帅哥7 小时前
动态规划:从懵逼到装逼,一篇让你彻底搞懂DP的终极指南
java·算法