NOIP1996-2000 题解

本文包含了NOIP1996-2001部分题目的题解。

NOIP1996

提高T3 挖地雷

题目地址

题目已经帮你拓扑排序完了,直接转移就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int n,V,mp[25][25]={0},m[25],dp[25]={0},pre[25],ans[25],maxi=0,at;
int main(){
scanf("%d",&n);
int i,j;
fill(pre,pre+n,-1);//记录路径
for(i=0;i<n;i++)
scanf("%d",&m[i]);
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
scanf("%d",&mp[i][j]);
dp[0]=maxi=m[0],at=0;
for(i=1;i<n;i++){
for(j=i-1;j>=0;j--)
if(mp[j][i]&&dp[j]>=dp[i])
dp[i]=dp[j],pre[i]=j;
dp[i]+=m[i];
if(dp[i]>maxi)
maxi=dp[i],at=i;
}
for(j=0,i=at;i!=-1;i=pre[i])
ans[j++]=i+1;
for(i=j-1;i>=0;i--){
printf("%d",ans[i]);
if(i)printf(" ");
}
printf("\n%d\n",maxi);
return 0;
}


提高T4 砝码称重

题目地址

用了完全背包,但是数据很弱,好像没有必要。
可以暴力枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int f[1005]={0},n,v[6],ans=0;
void packX(int vv){//二进制枚举版本
int p=1,vi=vv,j;
while(n>=p){
for(j=1000;j>=vi;j--)
f[j]=max(f[j],f[j-vi]);
n-=p,p<<=1,vi<<=1;
}
if(n){
for(vi=n*vv,j=1000;j>=vi;j--)
f[j]=max(f[j],f[j-vi]);
}
}
int main(){
v[0]=1,v[1]=2,v[2]=3,v[3]=5,v[4]=10,v[5]=20;
f[0]=1;
for(int i=0;i<6;i++){
scanf("%d",&n);
if(n)packX(v[i]);
}
for(int i=1;i<=1000;i++)
ans+=f[i];
printf("Total=%d\n",ans);
return 0;
}

NOIP1997

普及T1 棋盘

题目地址

自己推一下就行了。
长方形(含正方形)个数为

正方形个数为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,m,a1=0,a2=0;
int main(){
scanf("%lld%lld",&n,&m);
if(n<m)swap(n,m);
a1=m*n*(n+1)*(m+1)/4;
for(int i=1;i<=m;i++)
a2+=(n-m+i)*i;
printf("%lld %lld\n",a2,a1-a2);
return 0;
}

普及T2 三角形

题目地址

暴力枚举即可。
用了全排列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
using namespace std;
int ans=0,p,rec[20],now[20]={1,2,3,4,5,6,7,8,9};
void cmp(){
if(!ans||memcmp(now,rec,sizeof(int)*9)<0)
memcpy(rec,now,sizeof(rec));
ans++;
}
int ok(){
return ((now[0]<now[5]&&now[5]<now[8])
&&(now[1]<now[3]&&now[6]<now[7]&&now[2]<now[4])
&&(now[0]+now[1]+now[3]+now[5]==p)
&&(now[6]+now[7]+now[8]+now[5]==p)
&&(now[0]+now[2]+now[4]+now[8]==p));
}
int main(){
scanf("%d",&p);
do{
if(ok())cmp();
}while(next_permutation(now,now+9));
if(!ans){
printf("NO\n");
return 0;
}
printf("%d\n",ans);
printf("%d\n%d %d\n%d %d\n%d %d %d %d\n",rec[0],
rec[1],rec[2],rec[3],rec[4],rec[5],rec[6],
rec[7],rec[8]);
return 0;
}


普及T3 街道

题目地址

就是非常正常的DP。
递推式

之后再用一个简单的高精度就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll M=1000000000000000,dp[55][55][2]={0},n,m,x1,x2,y1,y2,ok=1;
void add(ll u1,ll v1,ll u2,ll v2){
ll a=dp[u1][v1][0]+dp[u2][v2][0];
dp[u2][v2][0]=a%M,a/=M;
a+=dp[u1][v1][1]+dp[u2][v2][1];
dp[u2][v2][1]=a%M;//简单的高精度
}
void solve(){
dp[1][1][0]=1;
for(ll i=1;i<=n;i++)
for(ll j=1;j<=m;j++)
if((ok&&(i<x1||i>x2||j<y1||j>y2))||(!ok))
add(i-1,j,i,j),
add(i,j-1,i,j); //递推过程
dp[n][m][1]%=100000;
if(dp[n][m][1]>0)printf("%lld%015lld\n",dp[n][m][1],dp[n][m][0]);
else printf("%lld\n",dp[n][m][0]);
}
int main(){
scanf("%lld%lld%lld%lld%lld%lld",&n,&m,&x1,&y1,&x2,&y2);
if(!x1&&!x2&&!y1&&!y2)ok=0;
else{
if(x1>x2)swap(x1,x2);
if(y1>y2)swap(y1,y2);
}
solve();
return 0;
}

提高T1 棋盘问题2(待考察?)

题目地址

本题我只想出来了一种比较简单的搜索方法。。。
实际上原题数据很小,基本上各种搜索方法都可以过,但是当$N = 10$的时候就很难卡过去了。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cctype>
#define INF 2000000000
using namespace std;
typedef long long ll;
int read(){
int f=1,x=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return f*x;
}
int n,sqr,mat[15][15]={0},dx[]={0,0,-1,1},dy[]={-1,1,0,0},ans=0;
bool isprime[205]={0},used[105]={0};
void dfs(int x,int y){
if(ans)return ;
for(int i=2;!ans&&i<=sqr;i++){
if(!used[i]&&(!x||isprime[i+mat[x-1][y]])&&(!y||isprime[i+mat[x][y-1]])){
mat[x][y]=i,used[i]=1;
if(x==y){
if(x==n-1){ans=1;return;}
else dfs(x,y+1);
}else if(y==n-1)dfs(x+1,x);
else if(x==n-1)dfs(y+1,y+1);
else if(y>x)dfs(x,y+1);
else if(x>y)dfs(x+1,y);
if(!ans)mat[x][y]=0,used[i]=0;
}
}
}
void init(){
n=read();
sqr=n*n;
for(int i=3;i<=200;i+=2){
isprime[i]=1;
for(int j=3;j*j<=i;j+=2)
if(i%j==0){
isprime[i]=0;break;
}
}
}
void solve(){
mat[0][0]=1,used[1]=1;
dfs(0,1);
if(!ans)printf("NO\n");
else{
for(int i=0;i<n;i++){
for(int j=0;j<n-1;j++)
printf("%d ",mat[i][j]);
printf("%d\n",mat[i][n-1]);
}
}
}
int main(){
init();
solve();
return 0;
}

NOIP1998

普及T1 寻找三位数

题目地址

很简单的枚举。
只要从101枚举到333即可,然后记录每一位。
可以采用特殊策略优化,比如当前数字必须不是5的倍数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <cstdio>
#include <cstring>
using namespace std;
char k[10];
int main(){
int a,b,c,d,fl,i;
for(a=101;a<=333;a++){
if(a%5==0)continue;
memset(k,0,sizeof(k));
fl=1,b=a,c=a*2,d=a*3;
k[b%10]++,k[c%10]++,k[d%10]++;
k[(b/10)%10]++,k[(c/10)%10]++,k[(d/10)%10]++;
k[b/100]++,k[c/100]++,k[d/100]++;
for(i=1;i<=9;i++)
if(k[i]!=1)
fl=0;
if(fl)printf("%d %d %d\n",b,c,d);
}
return 0;
}


普及T2 阶乘之和

题目地址

高精度乘法+加法水过。

1
2



普及T3 幂次方

题目地址

递归计算即可。

1
2



提高T1 火车站

题目地址

设第二站上的人数是$p$,则:容易看出每一站上下车的人都等于$fib[i]a+fib[j]p$,其中$i$和$j$为相邻正整数。
那么只需要算出最终车上的人等于多少$a$加多少$p$,解出$p$,然后带入第$x$站的数据即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cctype>
#define INF 2000000000
using namespace std;
typedef long long ll;
int read(){
int f=1,x=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return f*x;
}
int a,n,m,x,fib[25],xs1[25],xs2[25];
void init(){
a=read(),n=read(),m=read(),x=read();
fib[0]=0,fib[1]=1;
for(int i=2;i<=24;i++)
fib[i]=fib[i-1]+fib[i-2];
}
void solve(){
xs1[1]=xs1[2]=1,xs2[1]=xs2[2]=0;
for(int i=3;i<n;i++)
xs1[i]=xs1[i-1]+fib[i-2]-fib[i-3],
xs2[i]=xs2[i-1]+fib[i-1]-fib[i-2];
printf("%d\n",xs1[x]*a+xs2[x]*(m-xs1[n-1]*a)/xs2[n-1]);
}
int main(){
init();
solve();
return 0;
}


提高T2 拼数

题目地址

NOIP1999

普及T1 Cantor表

题目地址

找规律,分奇偶讨论,最后$ \mathcal O(1)$计算。

1
2
3
4
5
6
7
8
#include <cstdio>
using namespace std;
int main(){
int n,i;scanf("%d",&n);
for(i=1;n>(i*i+i)/2;i++);
if(i%2==0)n=i*i+1-n;
printf("%d/%d\n",i+(i*i-i)/2+1-n,n-(i*i-i)/2);
}


普及T2 回文数

题目地址

套用高精度加法模板即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cctype>
#define INF 2000000000
using namespace std;
typedef long long ll;
int n, s1[1005], s2[1005];
char s[1005];
void init(){
scanf("%d%s", &n, &s);
}
bool judge(int len){
for(int i = 1; i <= (len >> 1); ++i)
if(s1[i] != s1[len - i + 1]) return false;
return true;
}
void add(){
memset(s2, 0, sizeof(s2));
int x = 0;
s2[0] = s1[0];
for(int i = 1; i <= s1[0]; ++i){
x += s1[i] + s1[s1[0] - i + 1];
s2[i] = x % n;
x /= n;
}
if(x > 0) s2[++s2[0]] = x;
}
void solve(){
int len = strlen(s);
s1[0] = len;
for(int i = len - 1; i >= 0; --i){
if(isdigit(s[i])) s1[len - i] = s[i] - '0';
else s1[len - i] = s[i] - 'A' + 10;
}
int flag = 0;
for(int i = 0; i <= 30; ++i){
if(judge(s1[0])){
flag = 1;
printf("STEP=%d\n", i);
break;
}
add();
memcpy(s1, s2, sizeof(s2));
}
if(!flag) printf("Impossible!");
}
int main(){
init();
solve();
return 0;
}


提高T1 导弹拦截

题目地址

先求最长不上升子序列,再求最长上升子序列。
(Dilworth定理,最长链和最长反链)
或者因为数据小,贪心的做法也能接受。
所以我无聊的写了三个版本:

$O(nlogn)$动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <cstdio>
#include <algorithm>
using namespace std;
int high[50],tot=0,ans=0,dp[50]={0},tmp;
int main(){
int i,j;
while(scanf("%d",&high[tot])==1)tot++;
for(i=0;i<tot;i++){
for(tmp=1,j=0;j<i;j++){
if(high[j]>=high[i])tmp=max(tmp,dp[j]+1);
}dp[i]=tmp;
}for(i=0;i<tot;i++)ans=max(ans,dp[i]);
fill(dp,dp+tot,0x7FFFFFFF);for(i=0;i<tot;i++)
*lower_bound(dp,dp+tot,high[i])=high[i];
tmp=lower_bound(dp,dp+tot,0x7FFFFFFF)-dp;
printf("%d\n%d\n",ans,tmp);
return 0;
}

$O(n^2)$动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdio>
#include <algorithm>
using namespace std;
int dat[55],n;
int dp(int o){
int dpp[55],ans=1;
fill(dpp,dpp+n,1);
for(int i=1;i<n;i++){
for(int j=0;j<i;j++)
if(o&&dat[j]<=dat[i])dpp[i]=max(dpp[i],dpp[j]+1);
else if(dat[j]<dat[i])dpp[i]=max(dpp[i],dpp[j]+1);
ans=max(ans,dpp[i]);
}return ans;
}
int main(){
n=0;while(~scanf("%d",&dat[n]))n++;
reverse(dat,dat+n);
printf("%d\n",dp(1));
reverse(dat,dat+n);
printf("%d\n",dp(0));
return 0;
}

$O(n^2)$贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
using namespace std;
int h[25],low[25],n=0,ans,f[25];
int main(){
int i,j,t;
while(~scanf("%d",&h[n]))n++;
for(f[0]=1,ans=i=1;i<n;i++){
f[i]=1;
for(j=0;j<i;j++)
if(h[j]>=h[i])f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
}
printf("%d\n",ans);
for(low[0]=h[0],ans=0,i=1;i<n;i++){
for(j=0;j<=ans;j++)
if(low[j]>=h[i]){low[j]=h[i];break;}
if(j>ans)low[++ans]=h[i];
}
printf("%d\n",ans+1);
return 0;
}

普及/提高T3 旅行家的预算

题目地址

尽量选最便宜的,如果油满了就换次便宜的,这样递归(循环下去)。最后油全满了就No solution。
这里使用了优先队列来取最优值。
只是懒得手写堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <queue>
using namespace std;
typedef double db;
typedef struct{
int id;db count,dis,pri;
}Pp;
bool operator<(Pp a,Pp b){return a.pri>b.pri;}
Pp dat[105];int n;
db ans=0,C,D,per;
priority_queue<Pp> pq;
void solve(){
pq.push(dat[0]);
int i,j,k;
Pp tmp;
for(i=1;i<=n;i++){
db need=(dat[i].dis-dat[i-1].dis)/per,mini;
for(;!pq.empty();){
tmp=pq.top();
for(mini=need,j=tmp.id;j<i;j++)
mini=min(C-dat[j].count,mini);
for(j=tmp.id;j<i;j++)
dat[j].count+=mini;
ans+=mini*tmp.pri;
if(mini<need)
need-=mini,pq.pop();
else break;
}
if(pq.empty()){ans=-1;return ;}
pq.push(dat[i]);
}
}
int main(){
int i,j;
scanf("%lf%lf%lf%lf%d",&D,&C,&per,&dat[0].pri,&n);
for(i=1;i<=n;i++)
scanf("%lf%lf",&dat[i].dis,&dat[i].pri);
dat[0].dis=0;
for(n++,i=0;i<=n;i++)
dat[i].count=0,dat[i].id=i;
dat[n].dis=D;
solve();
if(ans<0)printf("No Solution\n");
else printf("%.2lf\n",ans);
return 0;
}


提高T4 邮票面值设计

题目链接