小学奥数
可以尝试不看题解做吗?但是不看题解有的题怕是真的做不出来吧 ~
\(\;\) 合并果子 \(\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\) 的情况下串串的变换总数。
则
于是直接计算即可。
\(*:\) 为什么呢?因为每个 \(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;
}

浙公网安备 33010602011771号