noip 2018年 信息学竞赛资料下载-提取码:04uf
一、单项选择题(共 10 题,每题 2 分,共计 20 分; 每题有且仅有一个正确选项)
答案:D
解析:考察进制转换,我们可以先将A,B转换为二进制,就可以发现A,B相等,但是与D不同,所以选D
答案:D
解析:因为C,C++,pascal都是需要编译的语言,非解释性语言
而python是交互式的,也是解释性语言
答案:B
答案:A
解析:等比数列求和,
答案:D
解析:,求和:
答案:B
解析:先建一棵表达式树,其先序遍历就是前缀表达式
答案:B
解析:
其实吧凭感觉都应该选 的,下面是我的证明方法:
设该线段在数轴上为 [ 0 , 1 ]
设第一个点坐标为 x ,则第二个点与它的期望距离为(如图)
由于x取遍 [ 0 , 1 ] 两点期望距离为:
答案:A
解析:对于A,令n=1,2个节点的二叉树形态有2种,但是C1=1,显然错误
答案:D
解析:一个人在第 i 轮可以得到的红球期望数量为:,而 所以每个人得到红球期望数量为1,而得到蓝球数量必定为1,所以为1:1
答案:B
解析:排除法+模拟
二 、 不定 项选择题(共 5 题,每题 2 分,共计 10 分 ;每题有一个或多个正确选
项,多选或少选均不得分 )
答案:AB
解析:草稿纸是不允许带的
答案:CD
解析:画图求解
答案:ABD
解析:dijstra算法不适用于负权图,而且它用于求单点到其他点的最短路
答案:ABD
解析:树的基本知识
答案:BCD
由于点数很小,手动模拟下。从条件3开始找,即可找到答案。
首先如果b是a的子集,那么条件必然成立。然后手动简单玩一下,发现只有1位和2位情况存在特例。手动找到这些的答案即可。
科学的解释是:设a and b=x,a xor x=y,b xor x=z,则(x+y)(x+z)=x(x+y+z),即yz=0,即a and b=a或a and b=b
四、阅读程序写结果(共 4 题,每题8 分,共计 32 分)
#include
int main() {
int x;
scanf("%d", &x);
int res = 0;
for (int i = 0; i < x; ++i) {
if (i * i % x == 1) {
++res;
}
}
printf("%d", res);
return 0;
}
输入:15
输出:4
解析:简单模拟即可。这种题非常需要细心、耐心。
#include
int n, d[100];
bool v[100];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d", d + i);
v[i] = false;
}
int cnt = 0;
for (int i = 0; i < n; ++i) {
if (!v[i]) {
for (int j = i; !v[j]; j = d[j]) {
v[j] = true;
}
++cnt;
}
}
printf("%d\n", cnt);
return 0;
}
输入:10 7 1 4 3 2 5 9 8 0 6
输出:6
解析:可以看到是读入一个序列,每个点都有一个出度。可以发现这是在找环的个数,手动模拟或者画个图数一下都可以。
#include
using namespace std;
string s;
long longmagic(int l, int r) {
long long ans = 0;
for (int i = l; i <= r; ++i) {
ans = ans * 4 + s[i] - 'a' + 1;
}
return ans;
}
int main() {
cin >> s;
int len = s.length();
int ans = 0;
for (int l1 = 0; l1 < len; ++l1) {
for (int r1 = l1; r1 < len; ++r1) {
bool bo = true;
for (int l2 = 0; l2 < len; ++l2) {
for (int r2 = l2; r2 < len; ++r2) {
if (magic(l1, r1) == magic(l2, r2)&& (l1 != l2 || r1 != r2)) {
bo = false;
}
}
}
if (bo) {
ans += 1;
}
}
}
cout << ans << endl;
return 0;
}
输入:abacaba
输出:16
解析:magic(l,r)是对于s[l,r]的字符串哈希,底下枚举了两个子串,那么答案其实就是不重复出现的子串个数,手动数一下就好了。
#include
using namespace std;
const int N =110;
bool isUse[N];
int n, t;
int a[N], b[N];
bool isSmall() {
for (int i = 1; i <= n; ++i)
if (a[i] != b[i]) return a[i] < b[i];
return false;
}
boolgetPermutation(int pos) {
if (pos > n) {
return isSmall();
}
for (int i = 1; i <= n; ++i) {
if (!isUse[i]) {
b[pos] = i; isUse[i] = true;
if (getPermutation(pos + 1)) {
return true;
}
isUse[i] = false;
}
}
return false;
}
void getNext() {
for (int i = 1; i <= n; ++i) {
isUse[i] = false;
}
getPermutation(1);
for (int i = 1; i <= n; ++i) {
a[i] = b[i];
}
}
int main() {
scanf("%d%d", &n, &t);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= t; ++i) {
getNext();
}
for (int i = 1; i <= n; ++i) {
printf("%d", a[i]);
if (i == n) putchar('\n'); else putchar('');
}
return 0;
}
输入1:6 10 1 6 4 5 32
输出 1:2 1 3 5 6 4 (3 分)
输入2:6 200 1 5 3 4 26
输出 2:3 2 5 6 1 4 (5 分)
解析:可以发现这一大堆函数唯一的用处就是找到字典序大于他的下一个排列(其实看到输入都是全排列的元素、以及一个next大概就可以猜到了。),对于第一个询问可以手动找到下10个排列。对于第二个询问,可以把后几位带在一起算,每次看把某一位更新成下一个值需要加上多少。当然也可以直接进行康托展开对全排列进行计数。
五、完善程序(共 2 题,每题 14 分,共计 28 分)
举例来说,如果n=5且p为1 5 4 2 3,则q为2 6 6 5 6。
下列程序读入了排列p,使用双向链表求解了答案。试补全程序。(第二空2分,其余3分)
数据范围 1 ≤ n ≤ 105。
#include
using namespace std;
const int N =100010;
int n;
int L[N], R[N],a[N];
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
a[x] = i ;
}
for (int i = 1; i <= n; ++i) {
R[i]= i + 1;
L[i] = i - 1;
}
for (int i = 1; i <= n; ++i) {
L[ R[a[i]]] = L[a[i]];
R[L[a[i]]] = R[a[i] ];
}
for (int i = 1; i <= n; ++i) {
cout << R[i]<< " ";
}
cout << endl;
return 0;
}
解析:
2,3,4空 可以考虑仿写。完善程序出现“复读机”套路是很常见的。当然不能全部复读,要讲究对称性。
5空 我们发现题目既然要求第i个数后面最近的一个比他大的,那么必然是i的后继,即R[i]。
当然已经做出前几空了把题面的那组数据带进去就会发现的确是R[i]。
如果原理不懂得可以画图进行模拟。不过反正写题本身很简单。
求小猪买齐所有物品所需最少的总额。
输入:第一行一个数 N。接下来 N 行,每行两个数。第 i 行的两个数分别代表 a[i],b[i]。
输出:输出一行一个数,表示最少需要的总额,保留两位小数。
试补全程序。(第一空 2 分,其余 3 分)
#include
#include
using namespace std;
const int Inf =1000000000;
const int threshold = 50000;
const int maxn =1000;
int n, a[maxn],b[maxn];
bool put_a[maxn];
int total_a,total_b;
double ans;
int f[threshold];
int main() {
scanf("%d", &n);
total_a = total_b = 0;
for (int i = 0; i < n; ++i) {
scanf("%d%d", a + i, b + i);
if (a[i] <= b[i]) total_a += a[i];
else total_b += b[i];
}
ans = total_a + total_b;
total_a = total_b = 0;
for (int i = 0; i < n; ++i) {
if( a[i] * 0.95<= b[i] ){
put_a[i] = true;
total_a += a[i];
} else {
put_a[i] = false;
total_b += b[i];
}
}
if(total_a >= threshold){
printf("%.2f", total_a * 0.95 +total_b);
return 0;
}
f[0] = 0;
for (int i = 1; i < threshold; ++i)
f[i] = Inf;
int total_b_prefix = 0;
for (int i = 0; i < n; ++i)
if (!put_a[i]) {
total_b_prefix += b[i];
for (int j = threshold - 1; j >= 0;--j) {
if (total_a +j + a[i] >= threshold && f[j] != Inf)
ans = min(ans, (total_a + j + a[i]) * 0.95+f[j] + total_b – total_b_prefix);
f[j] = min(f[j] + b[i], j >= a[i] ? f[j – a[i]] : Inf);
}
}
printf("%.2f", ans);
return 0;
}
解析:
首先先考虑算法:如果第一家便宜肯定选第一家。
(以下内容感谢知乎@林泽辉 指正)
我们考虑程序后半段的思路,大致就是假设能够在第一家买够50000,最少花多少钱。
那么如果打过九五折第一家更优,那么必然会选择第一家。反正剩下的物品都是中间物品。
那剩下的一些呢?我们称为“中间物品”。尽可能选择其中一些“中间物品”在A买,凑足50000元,使得比B便宜,但是尽可能的少。这是一个背包问题。
实现上,f[i,j]表示前i个物品,在额外在A店花了j元的情况下,购买B店“中间物品”的最小值。i呢?滚动数组空间降维。
考虑为什么要先进行这个贪心,如果直接进行正常的背包的话,背包的大小将会是 物品个数 * 物品大小 ,如果先进行贪心的话,背包的大小会减小到50000.这样就可以接受了。
第一空:如果直接a[i]<=b[i]的话上文就算过了,没必要单独循环一次。考虑贪心那么必然是看加了优惠之后a[i]是否优于b[i];
第二空:考虑printf里面的部分,那么如果a的总和已经满足优惠,直接优惠掉即可;
第三空:考虑仿写min里面的部分,那么肯定是当前枚举的优惠幅度超过了50000。这是考虑如果我们要买第i件,还额外花了j元在“中间物品”上的情况;
第四空:这是计算总价,第一店所有东西打折后,加上所有第二点需要购买的东西;
第五空:考虑它为啥要判j>=a[i],这个是做个背包问题都知道,这是背包问题的转移。