NOIP 2018 提高组初赛试题 题目+答案+简要解析
(k^{h+1}-1)/(k-1)(kh+1−1)/(k−1)
k^{h-1}kh−1
k^hkh
(k^{h-1})/(k-1)(kh−1)/(k−1)
二、问题求解(每题5分,共10分)
1. 甲乙丙丁四人在考虑周末要不要外出郊游。
已知①如果周末下雨,并且乙不去,则甲一定不去;②如果乙去,则丁一定去;③如果丙去,则丁一定不去;④如果丁不去,而且甲不去,则丙一定不去。如果周末丙去了,则甲_____(去了/没去)(1分),乙_____(去了/没去)(1分),丁_____(去了/没去)(1分),周末_____(下雨/没下雨)(2分)。
答案:去了,没去,没去,没下雨
解析:送分题,根据条件判断即可
2. 方程 a*b =(a or b) * (a andb),在a, b都取[0、31]中的整数时,共有______组解。(*表示乘法;or表示按位或运算;and表示按位与运算)
解析:使得a|b==max(a,b),a&b==min(a,b)
显然,a,b存在子集关系时上面的式子才能成立。
例如:1011|1001=1011=max(1011,1001)
可以自己举例验证QwQ
我们设b是a的子集,枚举a在二进制下有多少个1,即从5里面取i个1,i从0到5。每个i都是一个C(5, i)
接着枚举子集,有2^i中方案(每一个1都看看放还是不放)
C(5,0)*2^0 +C(5,1)*2^1+…+C(5,5)*2^5
= 1*1 + 5*2 + 10*4 + 10*8 + 5*16 + 1*32
= 243
最后,由于我们限制了a>b,所以答案要*2
但是a==b的情况会被多算,所以答案减去32
最后结果:2*243 – 32 = 452
四、阅读程序写结果(共 4 题,每题8 分,共计 32 分)
1.
#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
解析:简单模拟
2.
#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
解析:继续模拟(逃
3.
#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)是l-r的hash,枚举两个子串,答案就是不重复出现的子串个数,手动枚举。
4.
#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 分)
解析:这一大堆函数就是求这个排列的下一个……手算即可(当然如果你觉得200算不出来可以使用康托展开)
五、完善程序(共 2 题,每题 14 分,共计 28 分)
1. 对于一个1到n的排列p(即1到n中每一个数在p中出现了恰好一次),令qi为第i个位置之后第一个比pi值更大的位置,如果不存在这样的位置,则qi =n+1。
举例来说,如果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;
}
解析:
纯靠猜。1空发现没有直接读入,肯定有鬼……瞎猜一种方法就行
2空仿写下句……
3,4空互相仿写……
5空我们肯定要求这个数右边比他大的数的位置,所以输出R
当然我蒻看不懂,有心情研究的julao们可以再去找找……
2. 一只小猪要买 N 件物品(N 不超过 1000)。
它要买的所有物品在两家商店里都有卖。第 i 件物品在第一家商店的价格是 a[i],在第二家商店的价格是 b[i],两个价格都不小于 0 且不超过 10000。如果在第一家商店买的物品的总额不少于 50000,那么在第一家店买的物品都可以打 95 折(价格变为原来的 0.95 倍)。
求小猪买齐所有物品所需最少的总额。
输入:第一行一个数 N。接下来 N 行,每行两个数。第 i 行的两个数分别代表 a[i],b[i]。
输出:输出一行一个数,表示最少需要的总额,保留两位小数。
试补全程序。(第一空 2 分,其余 3 分)
#include <cstdio> #include <algorithm> 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; intf[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]; elsetotal_b += b[i]; } ans =total_a + total_b; total_a= total_b = 0; for(int i = 0; i < n; ++i) { if( (1) ) { put_a[i]= true; total_a+= a[i]; }else { put_a[i]= false; total_b+= b[i]; } } if ((2) ) { printf("%.2f",total_a * 0.95 + total_b); return0; } //第二部分 f[0]= 0; for(int i = 1; i < threshold; ++i) f[i]= Inf; inttotal_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 ( (3) >= threshold && f[j] != Inf) ans = min(ans, (total_a + j +a[i]) * 0.95+ (4) ); f[j] = min(f[j] + b[i], j >= a[i] ? (5) :Inf); } } printf("%.2f",ans); return0; }
解析:
代码分为两个部分。
第一部分是一个贪心,我们假设满足了优惠条件,按照折后价格进行贪心,如果结果满足了优惠条件就直接输出,此时如果放弃某些b商品来买a不会再有更优策略
第二部分是一个dp……策略就是把原先买了b的东西买a以获得折扣
f[i,j]表示前i个物品,在额外在A店花了j元的情况下,购买B店物品花费的最小值。i呢?想想你的01背包是怎么优化的。
3空是一个转移判断条件,tot_a+j+a[i]是在a店话费的总钱数(本来花的钱+前面改了的钱+当前物品价格)
4空表示在b商店买的物品总价, total_b + f[j] - total_b_prefix
5空更新,看看这个商品在a商店买还是b商店买
太毒瘤了QwQ