小学奥数

可以尝试不看题解做吗?但是不看题解有的题怕是真的做不出来吧 ~

\(\;\) 合并果子 \(\texttt{P6033}\)

显然,每次都找最小的两个数合并。

在合并之后查最小数,这样的操作涉及

  • 添加
  • 删除
  • 全局最小

显然这是堆的模板。

但是发现这样的复杂度是 \(\mathcal{O(n\log n)}\) 的,不足以通过本题。所以考虑优化。

发现添加的数总是越来越大(单调不降)的,故你要考虑维护一个队列 \(q2\) 来存。

每次操作中,如果 \(q2\) 的队首 \(>\) \(q1\) 的队首,那么选择 \(q1.front\) ,反之亦然。

注意到 \(q1\) 只添不删,所以只要先排序就好了

然后维护两个队列就好了。

这时你发现了一个新的复杂度瓶颈:排序。

这样做要先让 \(q1\) 保持有序。注意到 \(\alpha\leq 10^5\) ,使用桶排序。

复杂度:\(\mathcal{O(n+\alpha)}\) ,可以说相当小了。

\(\texttt{ Vladik and fractions (CF743C )}\)

这个就有意思了。

对于一个整数数 \(n\not = 0\),构造正整数 \(x,y,z,\) 满足 \(\dfrac{1}{x}+\dfrac{1}{y}+\dfrac{1}{z}=\dfrac{2}{n} (\#)\)

考虑拆分 \(\dfrac{2}{n}\)

\(\#\) 得, \(\dfrac{1}{x}+\dfrac{1}{y}+\dfrac{1}{z}=\dfrac{1}{n}+\dfrac{1}{n} (*).\)

不妨令 \(z=n,\dfrac{1}{x}+\dfrac{1}{y}=\dfrac{1}{n}.\)

\(\dfrac{1}{n}=\dfrac{1}{n+1}+\dfrac{1}{n(n+1)}.\)

这个时候令 \(x=n+1,y=n(n+1).\)

结束。

套路:\(\dfrac{1}{n}=\dfrac{1}{n+1}+\dfrac{1}{n(n+1)}.\)

\(\texttt{ * Missing Numbers (CF1081E)}\)

神奇。觉得不应该评绿,至少往上两个等级。

设答案数组为 \(q\).

考虑 \(q\) 的前缀和,设为 \(s\)

发现新数 \(2i+1\) 和旧数 \(2i\) 之间有关联。\(q[2i+1]=s[2i+1]-s[2i]\),也就是 \(s\) 的差分。

因为在完全正确的情况下 \(s[j](j\in [1,n])\) 都有 \(s[j]=m^2(n\in N_+)\)

\(s[2i]=n^2,s[2i+1]=m^2.\) 那么 \(q[2i+1]=m^2-n^2=(m-n)(m+n).\)

有一性质:\(m\) 要尽量小,证明显然。在 \(m=i\) 都可以成功的情况下 \(m=j(j\leq i)\) 也可以成功。证明显然。

因为 \(n\) 确定,令 \(m-n\) 尽量小。枚举之。

结束。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+10;
int q[N],s[N],n,mina,minb;
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n;
	for(int i=1;i<=n/2;++i) cin>>q[i<<1];
	for(int i=2;i<=n;i+=2){
		bool fl=1;
		for(int x=ceil(sqrt(q[i]));x>=1;--x){
			int y=q[i]/x;
			if(x*y==q[i]&&(x&1)==(y&1)){
				int a=(x+y)>>1,b=(y-x)>>1;
				if(b*b<=s[i-2]) continue;
				fl=0, mina=a,minb=b; break;
			}
		} if(fl) return puts("No"),0;
		q[i-1]=minb*minb-s[i-2], s[i-1]=s[i-2]+q[i-1], s[i]=s[i-1]+q[i];
	}
	puts("Yes"); for(int i=1;i<=n;++i) cout<<q[i]<<" ";
	return 0;
}

代码没有太多细节。觉得这个题还是很简单的。

\(\texttt{ Graph Coloring (CF662B)}\)

久违的 \(\texttt{2 - sat.}\)

\(\texttt{ Explorer Space (CF1517D)}\)

稍微傻一点。

不难发现,回路与去路相同 \(\#\)。也就是答案是第一次 \(\dfrac{k}{2}\) 步得到的答案 \(*\ 2.\)

所以用 \(dp\) 求解第一次走过去的答案就好了。

\(\#\) 的证明:

用反证法。

假设有比去路更短的回路,那么与这条回路相同的去路也比现在的回路更短。

即新去路比旧去路更短。旧去路并不是最优秀的。

与 “旧去路是最短的” 矛盾。故假设不成立。

\(\texttt{ * AquaMoon and Chess (CF1545B)}\)

\(1\)\(1\) 始终要粘在一起才能跳跃,并且在跳跃之后还是粘在一起。

也就是有很多形如 \(11\) 的元素。而单独的 \(1\) 则被我们认为对答案并不产生贡献。

答案就是许多 \(11\) 和许多 \(0\) 的组合。

设一共有 \(a\) 个形如 \(11\) 的结构。有 \(c\) 个形如 \(0\) 的结构。

答案即 \(C_{a+c}^c.\) 题目颇有难度。觉得至少蓝。

#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int N=2e5+10,p=998244353;
bitset<N> s;
char x; int n,t,fc[N]={1},ifc[N],a,c;
inline ll qpow(ll a,ll b){
	ll r=1;
	while(b){
		if(b&1) r=r*a%p;
		a=a*a%p, b>>=1;
	}
	return r;
}
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	for(int i=1;i<N;++i) fc[i]=fc[i-1]*i%p;
	cin>>t;
	while(t--){
		cin>>n, a=c=0;
		for(int i=1;i<=n;++i) cin>>x, x-='0', s[i]=x;
		for(int i=1;i<=n;++i){
			if(s[i]==s[i+1]&&s[i]) ++a,++i;
			else if(!s[i]) ++c;
		}
		cout<<fc[a+c]*qpow(fc[a],p-2)%p*qpow(fc[c],p-2)%p<<"\n"; 
	}
	return 0;
}

\(\texttt{ a-Good String (CF1385D)}\)

递归好题。

\(f(l,r)\) 是修改 \(l,r\) 的区间为好串的代价。求 \(f(l,mid)\)\(f(mid+1,r)\) 的最小值。

可以理解为分治。

复杂度显然为 \(\mathcal{O(n)}.\)

\(\texttt{ * Make Nonzero Sum (CF1753A1\&2)}\)

看到 \(\texttt{Alex_Wei}\) 写了这道,我也写写。

首先有 \(0\) 无论什么符号都不会影响答案。

所以考虑对 \(1,-1\) 进行分析。设他们的下标是 \(b[i]\)

用配对法。

\(i,i+1\) 配为一对,分类讨论如下:

  • \(a[b[i]]=a[b[i+1]]\) 要让他们符号相反。
    • 如果间距是奇数,那直接 \([b[i],b[i+1]]\) 进去就好了
    • \([b[i],b[i+1]-2]\)\([b[i+1]-1,b[i+1]].\)
  • 第二种就考虑符号相同,这个简单。全部为正不就好了。

代码有点吃力,细节不少。

#include <bits/stdc++.h>
#define ll long long
#define add push_back
#define mp make_pair
#define pii pair<int,int>
#define fir first
#define sec second
using namespace std;
const int N=2e5+10;
stack<int> st;
vector<int> v;
vector<pii> as;
int a[N],b[N],top,s,r,T;
int n; int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>T;
	while(T--){
		as.clear(),cin>>n,top=0,r=0,s=0;
		for(int i=1;i<=n;++i){
			cin>>a[i],s+=a[i]!=0;
			if(a[i])b[++top]=i;
		}
		if(s&1){ cout<<"-1\n"; continue; }
		for(int i=1;i<=top;i+=2){
			if(r+1<b[i]) as.add(mp(r+1,b[i]-1));
			if(a[b[i]]!=a[b[i+1]]) as.add(mp(b[i],b[i+1]-1)),as.add(mp(b[i+1],b[i+1]));
			else if(b[i+1]-b[i]&1)as.add(mp(b[i],b[i+1]));
			else as.add(mp(b[i],b[i+1]-2)),as.add(mp(b[i+1]-1,b[i+1]));
			r=b[i+1];
		}
		if(r<n)as.add(mp(r+1,n));
		cout<<as.size()<<"\n";
		for(pii it:as) cout<<it.fir<<" "<<it.sec<<"\n";
	}
	return 0;
}

\(\texttt{ Range = √Sum (CF1758D)}\)

考虑构造极差。根据高斯求和公式,在 \(n\) 为偶数的情况下直接构造 \(i\in[1,\dfrac{n}{2}],n\pm i\) 就好了极差显然是 \(n\),和是 \(n^2.\)\(n\) 是奇数的情况下沿用这样的方法就行,\(n\) 扩展到 \(2n.\)

\(\texttt{ Factorial Divisibility (CF1753B)}\)

简单题。考虑到阶乘的性质 \(n!(n+1)=(n+1)!,\) 直接用桶仅为看看后导零数量就好了。

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,x,s,cnt;
int a[N],t[N];
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>x;
	for(int i=1;i<=n;++i) cin>>a[i],++t[a[i]];
	for(int i=1;i<x;++i) t[i+1]+=t[i]/(i+1), t[i]%=(i+1);
	for(int i=1;i<x;++i) if(t[i]) return cout<<"No\n",0;
	return cout<<(t[x]?"Yes\n":"No\n"),0;
}

\(\texttt{ Make Array Good ( CF1762B)}\)

\(a_i\) 变成最小的比 \(a_i\) 大的二的整次幂即可。

\(\texttt{ * Another Array Problem (CF1763C)}\)

稍微难一点的题目。

分类讨论如下:

  • \(n= 2:\) 要么操作要么不操作。 \(ans=\max\{a_1+a_2,2|a_1-a_2|\}.\)

  • \(n=3\)\(ans=\max\{\Sigma a,3(a_2-a_1),3(a_2-a_3),3a_1,3a_3\}.\)

  • \(n\ge4:\) \(ans=n\max\{a_i\}(i\in[1,n]).\)

    为什么呢?注意到连续操作两次相同的 \(\{l,r\}\) 就可以让 \(a_i,i\in[l,r]\) 全部变成 \(0.\)

    然后唯一留下最大数,再把所有 \(0\) 操作成最大数。

代码显然,题目难度较大。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int t,n,x;
int a[N];
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;++i) cin>>a[i],x=max(x,a[i]);
		if(n==3) cout<<max(max(max(a[1]+a[2]+a[3],3*a[1]),max(3*a[3],3*(a[2]-a[1]))),3*(a[2]-a[3]))<<"\n";
		else if(n==2) cout<<max(a[1]+a[2],2*abs(a[2]-a[1]))<<"\n";
		else cout<<n*x<<"\n";
	}
	return 0;
}

\(\; \; \texttt{*}\) 密令 $\texttt{(P1385)} $

\(s\) 为串串的字符字典序之和。发现 **每个串串在 \(n,s\) 相同时等价 **\(*\)

\(f[i][j]\)\(n=i,s=j\) 的情况下串串的变换总数。

\[f[i][j]=\sum_{k=0}^{25} f[i-1][j-k]. \]

于是直接计算即可。

\(*:\) 为什么呢?因为每个 \(n=n',s=s'\) 的串串 \(S,S'\) 的变换都是相同的,每个 \(S\) 都可以通过变换达到 \(S'\) ,又发现两种操作都不会影响 \(s.\) 所以里面的出不去,外面的进不来。

故性质成立。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e2+5,M=5e3+5,p=1e9+7;
int f[N][M];
void build(void){
	for(int i=0;i<26;++i) f[1][i]=1;
	for(int i=2;i<N;++i) {
		f[i][0]=1;
		for(int j=1;j<M;++j) for(int k=0;k<26;++k) if(j-k>=0)
			f[i][j]+=f[i-1][j-k], f[i][j]=f[i][j]%p;
	}
}
int s,n,t;
char ch[N];
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>t, build();
	while(t--){
		memset(ch,0,sizeof ch), s=0;
		cin>>(ch+1), n=strlen(ch+1);
		for(int i=1;i<=n;++i) s+=ch[i]-'a';
		cout<<f[n][s]-1<<"\n";
	}
	return 0;
}

\(\,\,\) $ ** $ 汪了个汪 \(\texttt{ (P9837)}\)

目前所见到的最有意思也是最有难度的构造。

原题题解是 \(\texttt{zig-zag pattern.}\) 但是我并不能理解。

所以学习了一下第一篇题解的做法。

先膜拜一下 [第一篇题解]( [P9837 lg noip B] 汪了个汪 - 洛谷专栏 (luogu.com.cn) ),把陈鑫他们都没在赛场上写出来的题讲出来了。

首先,发现一个简单的性质:

任意一行相邻两个数的对数是 \(\dfrac{n(n-1)}{2}\) 的,而这 \(n\) 个数所能组成的数的对数也是 \(\dfrac{n(n-1)}{2}\) 的。

所以这让我们产生联想。

把所有的 \(n\) 个数所能组成的对数分成 \(n-1\) 组并且填上去。

这又让我们产生了一步联想:

由于一共无序数对的差有 \(n-1\) 种:在原题不能同一行放两个数的情况下。

  • 差为 \(1\) ,这样的数对形如 \((x,x+1)\),因为 \(1\le x,x+1\le n\),所以总共 \(n-1\)
  • 差为 \(2\) ,这样的数对形如 \((x,x+2)\),因为 \(1\le x,x+2\le n\),所以总共 \(n-2\)
  • 差为 \(k\),这样的数对形如 \((x,x+k)\),因为 \(1\le x,x+k\le n\),所以总共 \(n-k\)

以此类推,举例:差为 \(3\) 数数对 \((1,4),(2,5),(3,6)\) 并不能有序的放在一行,并且不能保证每行开头的数互不相同。

但是发现如果不把差为 \(k\) 的数对放在一起又有新的条件不能满足。

需要一种优秀的方法在不改变数对的情况下直接让配合合理起来。 考虑旋转。

不得不承认,这相当巧妙。

如果每列的数能满足这个条件,那么阵列同样可行。构造一个列满足条件的阵然后按照长度排序即可。

\(\texttt {Solved.}\)

for(int i=1;i<=n;++i,cout<<"\n"){
	int st=(i&1)?(2*n-i+1)>>1:i>>1;
	for(int q=1;q<=i;++q) cout<<st<<" ",q&1?st+=q:st-=q;
}

\(\;\) [信息与未来 2019] 堆栈计算机 \(\texttt{ (B3753)}\)

普及组模拟赛的题,到现在才想起要改。

首先考虑倍增地构造,这是很显然的。但是这样太过冗杂。

为了缩短代码量,放弃不断用堆栈模拟,换用递归。

void slv(int x){
	if(x==1) cout<<"1\n";
	else if(!(x&1)) slv(x>>1), cout<<"dup\nadd\n";
	else slv(x-1), cout<<"1\nadd\n";
}
int main(){ int n; cin>>n,slv(n); }

应该明确函数的意义。

\(\texttt{ Sofia and Strings (CF1898E)}\)

神仙题。

首先考虑不带排序的情况:贪心地选择最小的 \(i s.t. s_i=t_j\),不断选取这样的 \(i\) 直到竭尽。

继续考虑排序,注意到排序操作是不可逆的,所以直接将中间的所有 \(s_k<s_j\) 这些不可逆转的点全部删掉即可。

至于保证每次删去的总是最小的,直接用队列维护即可。

代码可以参考 \(\texttt{Luogu}\) 的第一个题解,写得非常优雅。

\(\;\) P1984 [SDOI2008] 烧水问题

一个 \(n\) 长度的全 \(0\) 序列,可以维护两个操作:两个数取平均值,不消耗代价;一个数 \(x\rightarrow 100\),消耗 \(100-x\) 的代价。

和彬哥花了 \(2 hours\) 才做出来的简单题。

但是讨论区喊降黄就有点过分了。

可以考虑前缀和和数的倍数关系是 \(q_i=\dfrac{a_i}{2i-2}\)。证明很简单,请读者自己证明。

int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		if(i^1)tmp=q*1.0/(2*i-2);
		else tmp=100;
		q+=tmp;
	}
	return printf("%.2f\n",q*4200.0/n),0;
}
posted @ 2024-05-21 13:31  ChihiroFujisaki  阅读(55)  评论(0)    收藏  举报