2019.10.3:蔡是原罪.所以蒟蒻决定从\(1997\)年开始刷每年的普及组和提高组的题.普及组应该都会刚完,提高组有些题选择性放弃.
2019.10.9:很抱歉,我又决定咕咕咕了,因为我发现以前的题太难了!!!!!!!!!!!!!!!!
\(1997\)普及组/提高组:
棋盘问题1
题意:设有一个\(N \times M\)方格的棋盘\((1≤N≤100,1≤M≤100)\),求出该棋盘中包含有多少个正方形、多少个长方形(不包括正方形).
分析:直接推式子.正方形:长和宽相等,直接枚举边长即可,\(\sum_{i=1}^{min(n,m)}(n-i+1)(m-i+1)\).矩形(包括正方形):长和宽可以不等,要分别枚举,\(\sum_{i=1}^n\sum_{j=1}^m(n-i+1)(m-i+1)=\sum_{i=1}^n(n-i+1)\sum_{j=1}^m(m-i+1)\),然后运用等差数列求和公式即可得\(\frac{n(n+1)}{2}\frac{m(m+1)}{2}=(n+1)(m+1)nm/4.\)
长方形数量即为两式相减.
#include
#include
#include
#include
#include
#include
#include
棋盘问题(2)
题意:在\(N \times N\)的棋盘上\((1≤N≤5)\),填入\(1,2,…,N^2\)共\(N^2\)个数,使得任意两个相邻的数之和为素数.如有多种解,则输出第一行、第一列之和为最小的排列方案;若无解,则输出“NO”.
直接爆搜的话,因为要保证第一行、第一列之和为最小的排列方案,所以\(n=5\)会超时,所以就\(dfs\)的同时记录了第一行第一列上的值,如果已经超过了当前的最小值,直接\(return\),一个小剪枝即可.
#include
#include
#include
#include
#include
#include
#include
斐波那契数列(升级版)
题意:请你求出第n个斐波那契数列的数mod\(2^{31}\)之后的值.并把它分解质因数.
分析:就直接根据题意分两步来做即可.
#include
#include
#include
#include
#include
#include
#include
棋盘问题(2)【加强】
题意:在\(N \times N\)的棋盘上\((1≤N≤10)\),填入\(1,2,…,N^2\)共\(N^2\)个数,使得任意两个相邻的数之和为素数.
分析:预处理出\(a[i][j]\)表示加上i之后是质数的第j个数,\(b[i][j][k]\)表示加上i且加上j之后是质数的第k个数,其实就是为了在搜索枚举每个位置填什么数字的时候能够快一点,没必要1到\(n^2\)的枚举.
因为题目要保证第一行第一列的和最下,所以先搜索第一行,然后搜索第二列,再搜索剩下的格子.
然后发现\(n=7\)时方案不合法,\(n=9\)超时,打个表算了.
#include
#include
#include
#include
#include
#include
#include
\(1998\)普及组:
三连击
题意:将\(1,2, \cdots ,9\)共\(9\)个数分成\(3\)组,分别组成\(3\)个三位数,且使这\(3\)个三位数构成\(1:2:3\)的比例,试求出所有满足条件的\(3\)个三位数.
分析:直接枚举第一个数的每一位,然后表示出第二,三个数,最后\(check\)一下是否合法即可.
#include
#include
#include
#include
#include
#include
#include
阶乘之和
题意:用高精度计算出\(S=1!+2!+3!+…+n! (n≤50)\).其中“!”表示阶乘,例如:\(5!=5 \times 4 \times 3 \times 2 \times 1\).
分析:因为不喜欢高精,也不会高精,就直接\(kuai\)的以前写的.
#include
using namespace std;
int len=1,t[10001],ans[10001],anslen,n;
void jiecheng(int v){ //定义函数计算n的阶乘;
for(int i=1;i<=len;i++){
t[i]=t[i]*v; //每个位数都乘v;
}
int i=1;
while(t[i]>9||i9){ //当满足进位条件;
ans[i+1]=ans[i+1]+ans[i]/10; //进位;
ans[i]=ans[i]%10; //只保留个位;
anslen=max(anslen,i+1);
}
anslen=max(anslen,i); //判断当前数的位数,避免了多余的计算;
}
}
int main(){
scanf("%d",&n);
t[1]=1;
for(int i=1;i<=n;i++){
jiecheng(i),jia();
}
for(int i=anslen;i>=1;i--){
printf("%d",ans[i]);
}
return 0;
}
幂次方
题意略.
分析:一道分治好题,为什么难度才普及\(-\),挺难的啊.
#include
#include
#include
#include
#include
#include
#include
19998提高组:
车站
题意:火车从始发站(称为第\(1\)站)开出,在始发站上车的人数为\(a\),然后到达第\(2\)站,在第\(2\)站有人上、下车,但上、下车的人数相同,因此在第\(2\)站开出时(即在到达第\(3\)站之前)车上的人数保持为\(a\)人。从第\(3\)站起(包括第\(3\)站)上、下车的人数有一定规律:上车的人数都是前两站上车人数之和,而下车人数等于上一站上车人数,一直到终点站的前一站(第\(n-1\)站),都满足此规律。现给出的条件是:共有\(N\)个车站,始发站上车的人数为\(a\),最后一站下车的人数是\(m\)(全部下车)。试问\(x\)站开出时车上的人数是多少?\(a(≤20)\),\(n(≤20)\),\(m(≤2000)\),和\(x(≤20)\).
分析:这道题我推了一个小时的式子,我真的太弱了,而且还是手列表格.
设第一,二站上车人数为x人:
\[ \begin{matrix} num & 1 & 2 & 3 & 4 & 5 & 6 & 7\\ up & a & x & a+x & a+2x & 2a+3x & 3a+5x & 5a+8x\\ down & 0 & x & x & a+x & a+2x & 2a+3x & 3a+5x\\ \end{matrix} \]
然后第n站下车的人数,就是第n-1站开出时车上的人数,第n站是没有人上车的.然后发现这一站下车的人数可以一一和上一站上车的人数抵消,所以第\(n-1\)站开出时车上的人数为 第一站上车人数+第\(n-1\)站上车人数-第二站下车人数 .所以有式子\(f([(n-1)-2]+1)a+(f[(n-1)-1]-1)x=m\)
解方程解出\(x\)之后,第p站开出时车上的人数,还是根据上式\(ans=f([p-2]+1)a+(f[p-1]-1)x\).
#include
#include
#include
#include
#include
#include
#include
拼数
题意:设有\(n\)个正整数\((n≤20)\),将它们联接成一排,组成一个最大的多位整数
分析:就利用\(string\)可以直接相加,并且直接比较大小的性质.
#include
#include
#include
#include
#include
#include
#include
进制位
题意略.
分析:数论难,字符串细节多\(->\)这道题好难啊.
仔细观察一下样例,就会发现,进制=字母个数=\(n-1\),第二问就这么解决了.然后再仔细观察一下样例,就会发现,对于每一个字母,它那一行上两位数的个数就是它的值(这个值还等于字母总个数-1-该字母在两位数的个位上出现的次数).就这样判断得出答案即可.
细节问题我写进注释里面.
#include
#include
#include
#include
#include
#include
#include
\(1999\)普及组:
Cantor表
题意:现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的:
\(1/1\) , \(1/2\) , \(1/3\) , \(1/4\), \(1/5\), …
\(2/1\), \(2/2\) , \(2/3\), \(2/4\), …
\(3/1\) , \(3/2\), \(3/3\), …
\(4/1\), \(4/2\), …
\(5/1\), …
…
我们以\(Z\)字形给上表的每一项编号。第一项是\(1/1\),然后是\(1/2\),\(2/1\),\(3/1\),\(2/2\),…求表的第\(N(N<=10004000)\)项.
分析:又是一道普及\(-\),推了一个小时.主要就是各种分类讨论吧.也不好解释.
#include
#include
#include
#include
#include
#include
#include
回文数
题意:若一个数(首位不为零)从左向右读与从右向左读都一样,我们就将其称之为回文数。例如:给定一个十进制数\(56\),将\(56\)加\(65\)(即把\(56\)从右向左读),得到\(121\)是一个回文数。
又如:对于十进制数\(87\):
STEP1:\(87\)+\(78\) = \(165\)
STEP2:\(165\)+\(561\) = \(726\)
STEP3:\(726\)+\(627\) = \(1353\)
STEP4:\(1353\)+\(3531\) = \(4884\)
在这里的一步是指进行了一次\(N\)进制的加法,上例最少用了\(4\)步得到回文数\(4884\)。
写一个程序,给定一个\(N\)(\(2 \le N \le 10,N=16\))进制数\(M\)(\(100\)位之内),求最少经过几步可以得到回文数。如果在\(30\)步以内(包含\(30\)步)不可能得到回文数,则输出Impossible!
.
分析:又是一道字符串,又搞了一个小时,普及组的题都这么难的么;没有思维难度啊,就每一步按照题意来模拟就行,每得到一个数字就去\(check\)是否回文即可.
#include
#include
#include
#include
#include
#include
#include
导弹拦截
题意:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.输入导弹依次飞来的高度(雷达给出的高度数据是$ \le 50000$的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统.
分析:绿题反而更好写???两问毫不相干,分来来做,对于第一问"这套系统最多能拦截多少导弹",设\(f[i]\)表示一套系统打下的第i枚导弹的高度,那么扫描的时候能打就打,不能打就去前面找到第一个比当前导弹低的替换掉.
第二问也是能打就打,不能打就新建一个系统.
#include
#include
#include
#include
#include
#include
#include
\(1999\)提高组:
旅行家的预算
题意:一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的).给定两个城市之间的距离\(D1\)、汽车油箱的容量\(C\)(以升为单位)、每升汽油能行驶的距离\(D2\)、出发点每升汽油价格\(P\)和沿途油站数\(N\)(\(N\)可以为零),油站\(i\)离出发点的距离\(Di\)、每升汽油价格\(Pi\)(\(i=1,2,…,N\)).计算结果四舍五入至小数点后两位.如果无法到达目的地,则输出\("No\) \(Solution".\)
分析:最不喜欢做贪心+模拟的题了,细节超级多,容许我留个坑.
邮票面值设计
题意:给定一个信封,最多只允许粘贴\(N\)张邮票,计算在给定\(K\)(\(N+K≤15\))种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值\(MAX\),使在\(1\)至\(MAX\)之间的每一个邮资值都能得到.例如,\(N=3\),\(K=2\),如果面值分别为\(1\)分、\(4\)分,则在\(1\)分~\(6\)分之间的每一个邮资值都能得到(当然还有\(8\)分、\(9\)分和\(12\)分);如果面值分别为\(1\)分、\(3\)分,则在\(1\)分~\(7\)分之间的每一个邮资值都能得到.可以验证当\(N=3\),\(K=2\)时,\(7\)分就是可以得到的连续的邮资最大值,所以\(MAX=7\),面值分别为\(1\)分、\(3\)分.
分析:直接\(dfs\)枚举选哪\(k\)种面值的邮票,每搜索到一个值,就\(dp\)(这应该算是个多重背包吧)计算当前所能够产生的最大值,以便于搜索数量的减少.
最近做了好多道先\(dfs\)穷举状态,然后\(dp\)计算该状态下的最优解的题.本题还在于\(dp\)的值有利于我们搜索剪枝.
#include
#include
#include
#include
#include
#include
#include
\(2000\)普及组:
题意:税收与补贴问题
这道题不太想讲,因为看懂题意就搞了很久,然后严重怀疑第四个数据点是有问题的,搞得我额外为这个点写了几十行代码,很不爽.
#include
#include
#include
#include
#include
#include
#include
计算器的改良
字符串+大模拟=不想做.
\(2000\)提高组
方格取数
题意:\(n*n\)的棋盘,有些点有权值,两次从左上角走到右下角,一个点的权值经过后就变成零了,求最大得分.
直接设\(f[i][j][k][l]\)暴力转移即可.
费用流也可以做.时间复杂度都差不多,但是代码量就........
#include
#include
#include
#include
#include
#include
#include
乘积最大
题意略.
\(DP\)很好想,设\(f[i][j]\)表示前i个数分成j段的最大乘积,则\(f[i][j]=max(f[k][j-1]+sum[k+1][j])\).初始化\(f[i][0]=1,f[i][1]=sum[1][i]\),目标\(f[n][m+1]\).
\(long\) \(long\)有\(60\)分,\(int128\)有\(80\)分,高精\(100\)分,懒得写了.
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
const int N=50;
int n,m;
__int128 sum[N][N],f[N][N];
char s[N];
inline void print(__int128 x){
if(!x)return;
if(x<0)putchar('-'),x=-x;
print(x/10);putchar(x%10+'0');
}
int main(){
scanf("%d%d%s",&n,&m,s+1);
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j)
for(int k=i;k<=j;++k)
sum[i][j]=(__int128)sum[i][j]*10+s[k]-'0';
for(int i=1;i<=n;++i)f[i][0]=1,f[i][1]=sum[1][i];
for(int i=1;i<=n;++i)
for(int j=1;j<=m+1;++j){
if(i=j-1)f[i][j]=max(f[i][j],f[k][j-1]*sum[k+1][i]);
}
}
print(f[n][m+1]);puts("");
return 0;
}