2018NOIP提高组模拟1
———————————————————————————————20180819
1·Number
题目描述
如果一个数能够表示成两两不同的 3 的幂次的和,就说这个数是好的。
比如 13 是好的,因为 13 = 9 + 3 + 1 。
又比如 90 是好的,因为 90 = 81 + 9 。
现在我们用 a[i] 表示第 i 小的好数。
比如 a[1] = 1, a[2] = 3, a[5] = 10 。
给定 L,R,请求出 a[L] 到 a[R] 的 和 mod 232。
输入格式
第一行一个整数 T,表示数据组数。
接下来 T 行,每行两个整数 L,R 表示一组询问。
输出格式
输出 T 行,每行为一个整数,表示相应询问的答案。
整数,表示相应询问的答案。
输入样例
5
1 3
3 3
4 5
6 7
2 5
输出样例
8
4
19
25
26
备注
【数据范围】
对 30% 的输入数据:1≤T≤100;R≤1000 。
对 100% 的输入数据:1≤T≤100000;1≤L≤R≤10^18。
题解
如果用三进制表示好数,那必定是一个01串,这让人很容易联想到二进制。
所以我们可以用十进制转二进制再用三进制算。
例如19的二进制为10011,当三进制算出第19大的好数为81+3+1=85。
我们可以用二进制准确寻找到第i小的好数。
再统计a[1]到a[R]的和 和 a[1]到a[L]的和。
然后我们就可以用类似数位DP的方法, 确定每一位对于答案的贡献,累加这个值。
注意中间过程会超过long long 的范围
#include<iostream>
#include<cstdio>
using namespace std;
#define ULL unsigned long long
void read(ULL &x){
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
}
ULL mod=1LL<<32LL,e,f,t;
ULL ask(ULL x){
ULL pre=0,ans=0,c2=1,c3=1,sum=0;
for(int step=0;x;x>>=1,step++){
if(x&1){
ans=(ans+pre*c3)%mod;
ans=(ans+sum*c2)%mod;
}
if(step)c2=c2*2%mod;//
pre=(pre+(x&1)*c2)%mod;
sum=(sum+c3)%mod;
c3=c3*3%mod;
}
return ans;
}
int main(){
read(t);
while(t--){
read(e);read(f);
printf("%I64d\n",(ask(f+1)-ask(e)+mod)%mod);//
}
return 0;
}
2·Dp
题目描述
一块土地有 n 个连续的部分,用 H[1],H[2],…,H[n] 表示每个部分的最初高度。有 n 种泥土可用,他们都能覆盖连续的 k 个部分,第 i 种泥土的价格为 C[i],可以使 i,i+1,…,i+k-1 部分的高度增加 E[i](如果 i+k>n,那就覆盖 i,…,n ),我们必须满足以下条件:
1、每种泥土只能使用一次。
2、成本必须小于等于 m 。
要求在上述条件下,使得最低的部分的高度尽量高,请求出这个高度。
输入格式
第一行三个整数 n,m,k,表示土地有几个部分,最大预算成本以及每种泥土能覆盖的部分数。
接下来 n 行,每行三个整数 H[i],E[i],C[i]。
输出格式
输出一个整数,表示满足条件的情况下,最低部分的高度的最大值。
样例输入
4 20 1
1 3 5
1 7 3
4 6 9
3 5 13
样例输出
3
备注
【数据范围】
对 30% 的输入数据:1≤n≤20 。
对 100% 的输入数据:1≤k≤11;1≤n≤100;0≤m;H[i],E[i],C[i]≤10^6 。
题解
1 二分加贪心(预计70分)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 0x7fffffff
int n,m,k,h[110],e[110],c[110],a[110],b[110],cost;
bool check(int x){
memcpy(a,h,sizeof(h));
memset(b,0,sizeof(b));
cost=0;
for(int i=1;i<=n;i++){
if(a[i]<x){
cost+=c[i];
if(cost>m)return false;
for(int j=i;j<i+k;j++)
a[j]+=e[i];
if(a[i]<x){
for(int j=i-k+1;j<i;j++){
if(b[j]){
b[j]=0;
for(int l=i;l<j+k;l++)
a[l]+=e[j];
if(a[i]>=x)continue;
}
}
}
if(a[i]<x)return 0;
}
else b[i]=1;
}
return 1;
}
int erfen(){
int l=0,r=inf;
while(l+1<r){
int mid=(l+r)>>1;
if(check(mid))l=mid;
else r=mid;
}
if(check(r))return r;
return l;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&h[i],&e[i],&c[i]);
printf("%d",erfen());
return 0;
}
正解(预计100分)
最小的最大——二分的明显标志。
二分最少用多少钱。
检验时用f[i][j]表示前i个部分满足要,且j为第i –k + 1 到第i 中泥土的选取情况下
(j为二进制,j<2^11[2048]) 最少用了多少钱。
每次向右移动一个单位的土地,相当于第i –k + 1块土地不考虑,
转而考虑第i + 1块土地,枚举所以可能的状态。
所以复杂度为O (n * 2^k * logX)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f3f
int n,m,k,h[110],e[110],c[110],f[101][2500];
bool check(int x){
memset(f,0x3f,sizeof(f));
f[0][0]=0;
//枚举所有状态
for(int i=1;i<=n;i++)
for(int j=0;j<1<<k;j++)
if(f[i-1][j]<inf){
int cnt=h[i];
for(int l=1;l<k;l++)
if(j&(1<<(l-1)))cnt+=e[i-l];//算之前加的
if(cnt>=x)
if(j&(1<<(k-1))){
if(f[i-1][j]<f[i][(j^(1<<(k-1)))<<1])
f[i][(j^(1<<(k-1)))<<1]=f[i-1][j];
}
else{
if(f[i-1][j]<f[i][j<<1])
f[i][j<<1]=f[i-1][j];
}
//转移状态
if(cnt+e[i]>=x)
if(j&(1<<(k-1))){
if(f[i-1][j]+c[i]<f[i][((j^(1<<(k-1)))<<1)+1])
f[i][((j^(1<<(k-1)))<<1)+1]=f[i-1][j]+c[i];
}
else{
if(f[i-1][j]+c[i]<f[i][(j<<1)+1])
f[i][(j<<1)+1]=f[i-1][j]+c[i];
}
}
int ans=inf;
for(int j=0;j<1<<k;j++)
if(f[n][j]<ans)ans=f[n][j];
return (ans<=m);
}
int erfen(){
int l=0,r=inf;
while(l+1<r){
int mid=(l+r)>>1;
if(check(mid))l=mid;
else r=mid;
}
if(check(r))return r;
return l;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&h[i],&e[i],&c[i]);
printf("%d",erfen());
return 0;
}
3·Change
题目描述
Alice 和 Bob 又聚在一起了!他们已经厌倦了取石子游戏,现在他们热衷于切题。于是,Alice 找到了一道题让 Bob 做。Alice 有一张 N*M 的表格,每个格子上有一个值 a[i][j] (1≤i≤N,1≤j≤ M),Alice 将会给 Bob 若干个操作,操作分以下三类:
交换两行
交换两列
输出某一个格子上的值
由于 Bob 正在为给 Alice 出题而发愁,他请你完成这个题。
输入格式
第一行包含三个整数 N,M,Q,表示表格有 N 行 M 列,以及有 Q 个操作。
接下来 N 行,每行 M 个数用来描述 Alice 的表格。
接下来 Q 行,每行一个字符 S 和两个整数 x,y。其中 S 取 c,r,g 中的一个。
如果 S=c ,交换 x,y两列(1≤x,y≤m);
如果 S=r ,交换 x,y两行(1≤x,y≤n);
如果 S=g ,输出 a[x][y](1≤x≤n;1≤y≤m)。
输出格式
对于每一个 S=g 的操作,输出要求的数并换行。
样例数据 1
输入
3 3 5
1 2 3
4 5 6
7 8 9
g 3 2
r 3 2
c 2 3
g 2 2
g 3 2
输出
8
9
6
备注
【数据范围】
对 50% 的输入数据 :1≤n,m,Q≤100。
对 100% 的输入数据 :1≤n,m≤1000;1≤Q≤500000;a[i][j]≤10^6。
题解
我们不难发现交换行的时候对列的情况实质没有影响,
交换列的时候对行的情况实质没有影响;
所以我们只要用两个数组维护在每步操作后,
现在的第i行是原来的第几行,现在的第j列是原来 的第几列就行了;
#include<iostream>
#include<cstdio>
using namespace std;
void read(int &x){
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
}
int n,m,q,a[1001][1001],idx[1001],idy[1001];
int main(){
read(n);read(m);read(q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
read(a[i][j]);
for(int i=1;i<=1000;i++){
idx[i]=i;idy[i]=i;
}
while(q--){
char c=1;int e,f;
while(c<'a'||c>'z')c=getchar();
read(e);read(f);
if(c=='g'){
printf("%d\n",a[idx[e]][idy[f]]);
continue;
}
if(c=='c'){
swap(idy[e],idy[f]);
continue;
}
if(c=='r')swap(idx[e],idx[f]);
}
return 0;
}