NOIP2004提高组题解

T1:津津的储蓄计划

考察知识:模拟

算法难度:X 实现难度:X+

分析:按照题目的要求模拟就可以了,只是要考虑严谨,还要看懂题目

代码:

#include<cstdio>
int cost,rest,store,fail;
int main(){
	for(int i=1;i<=12;i++){
		scanf("%d",&cost);
		rest+=(300-cost);
		if(rest<0) {fail=i;break;}
		else store+=120*(rest/100),rest%=100;
	}
	if(fail) printf("%d\n",-fail);
	else printf("%d\n",store+rest);
	return 0;
}

T2:合并果子

考察知识:二叉堆,模拟,贪心

算法难度:XX 实现难度:XX+

分析:

直接每次合并最小的两堆就可以了

还是那句话:当年不能用STL,所以我们还是按当年的要求来吧(不用优先队列)

用priority_queue很方便,就不贴代码了,下面的代码是用二叉堆实现的,注释里面讲述了使用方法,你需要有二叉树的的知识储备才容易读懂

代码:

#include<cstdio>
#include<algorithm>
const int maxn=10005;
int heap[maxn],np,n,t,sum;//维护一个小根堆
/*heap是储存二叉树的一个数组,2*i,2*i+1是i的
左右儿子,保证这棵二叉树父亲的权值不比儿子大*/ 
void add(int x){//添加元素 
	heap[++np]=x;//把np理解为数组的指针-指向末尾元素
	for(int i=np,j=i/2;j>0;i=j,j/=2){//调整二叉树的节点权值 
		if(heap[j]>heap[i]) std::swap(heap[i],heap[j]);
		else break;
	}
}
void del(){//删除最小元素 
	heap[1]=heap[np--];//把根赋最后节点的值,删除最后节点
	for(int i=1,j=2*i;j<=np;i=j,j*=2){//调整二叉树 
		if(j<np&&heap[j+1]<heap[j]) j++;
		if(heap[i]>heap[j]) std::swap(heap[i],heap[j]);
		else break;
	} 
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&t),add(t);
	while(--n){
		t=heap[1],del();
		t+=heap[1],del();
		add(t),sum+=t;
	}
	printf("%d\n",sum); 
	return 0;
}

T3:合唱队形

考察知识:序列型动态规划

算法难度:XX+ 实现难度:XX+

分析:

其实就是求一次最长上升子序列,然后反着求一次最长下降子序列,然后找最高点就可以了

代码:

#include<cstdio>
#include<algorithm>
int n,a[105],f1[105],f2[105],mx;
//f1(i)=max(f1(j))+1||1<=j<i a[j]<a[i]
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",a+i),f1[i]=f2[i]=1;
	
	for(int i=2;i<=n;i++)
	  for(int j=1;j<i;j++) if(a[j]<a[i])
	    f1[i]=std::max(f1[i],f1[j]+1);
	
	for(int i=n-1;i>=1;i--)
	  for(int j=n;j>i;j--) if(a[i]>a[j])
	    f2[i]=std::max(f2[i],f2[j]+1);
	
	for(int i=1;i<=n;i++) mx=std::max(mx,f1[i]+f2[i]-1);
	printf("%d\n",n-mx);
	return 0;
}

T4:虫食算

考察知识:搜索+剪枝,数学,高斯消元

算法难度:XXXX 实现难度:XXXX

分析:这道题搜索并不难写,但是剪枝的程度决定了你能得多少分

我第一次花了两个小时写(调bug)完了第一份程序,如果不剪枝,时间复杂度为:O(n^2^n),可是我加了剪枝也才20分,然后我修改了一下,时间复杂度开了个根号,然后就可以90分了

下面先介绍90分方法:

首先是命名方式:

add[i]表示第i+1为应该向第i为进一

剪枝:1.如果当前某个字母必须赋某个值而这个值已经被使用,剪枝 2.如果某个字母已经被赋值了,就不用再枚举其可能的值了

其实剪枝并不是很多,也不是很复杂,只不过细节还是很多,很容易写错,情况要分类讨论(我找bug又花了一个小时T_T)

所以忠告大家一定要加强静态查错,考虑每一个可能出错的地方

代码:

#include<iostream>
#include<cstdio>
int n,mp[30],used[30],add[30],got_ans;
char A[30],B[30],C[30];
void dfs(int row,int cur){
    if(row<0){
        if(!add[row+1])//第一位不该有进位 
            for(int i=0;i<n;i++) printf("%d ",mp[i]);
        got_ans=1;return;
    }
    if(got_ans) return;
    int idA=A[row]-'A',idC=C[row]-'A';
    if(cur) goto Cur1;
    //考虑第row列第一行的字母代表的数字: 
    if(mp[idA]>=0) {dfs(row,1);return;}//如果字母已经被赋值 
    for(int i=0;i<n;i++) if(!used[i]){//枚举可以赋的值 
    	mp[idA]=i,used[i]=1;
    	dfs(row,1);
    	mp[idA]=-1,used[i]=0;//修改回原状态 
    }return;
    Cur1://考虑第row列第二行:
    int idB=B[row]-'A';
    if(mp[idB]>=0){//如果字母二已被赋值,分类讨论 
        int t=mp[idA]+mp[idB]+add[row+1];
        add[row]=t/n;//注意进位 
        if(mp[idC]>=0&&t%n==mp[idC]){//如果字母三已被赋值
            dfs(row-1,0);
            add[row]=0;
        }
        else if(mp[idC]<0&&used[t%n]==0){//根据字母一二可以确定字母三 
            mp[idC]=t%n,used[t%n]=1;
            dfs(row-1,0);
            mp[idC]=-1,add[row]=used[t%n]=0;
        }
        return;
    }
    int t;
    for(int i=0;i<n;i++) if(!used[i]){//枚举第字母二可能的值 
        t=mp[idA]+i+add[row+1];
        add[row]=t/n;
        mp[idB]=i,used[i]=1;
        if(mp[idC]>=0&&(t%n==mp[idC])) dfs(row-1,0);
        else if(mp[idC]<0&&used[t%n]==0){
            mp[idC]=t%n,used[t%n]=1;
            dfs(row-1,0);
            mp[idC]=-1,used[t%n]=0;
        }
        mp[idB]=-1,add[row]=used[i]=0;
    }
}
int main(){ 
    scanf("%d%s%s%s",&n,A,B,C);
    for(int i=0;i<n;i++) mp[i]=-1;
    dfs(n-1,0);
    return 0;
}

其实只要再加一个剪枝就是100分算法了:3.考虑剩下的数,如果一列的三个数值都知道了那么就可以判断是否矛盾了

但是要考虑进位(true表示可以剪枝):

bool check(int limt){
	int a,b,c;
	for(int i=0;i<=limt;i++){
		a=mp[A[i]-'A'],b=mp[B[i]-'A'],c=mp[C[i]-'A'];
		//考虑要严谨 
		if(((a+b)%n)!=c&&((a+b+1)%n)!=c&&a>=0&&b>=0&&c>=0) return true; 
	}
	return false;
}

最后修改一下:

    if(got_ans||check(row)) return;

就可以AC了,那个之前超时的点500ms过,还是有点悬,但是比起以前速度快了60多倍

总结:

T1:刚学竞赛一个月就AC了,最近来做没有考虑严谨居然80分T_T

T2:第一次做出现在考试中,那时没有学优先队列,用map做的,100分,现在用二叉堆做,不小心写错一个字母(i写成了j)只得了20分T_T

T4:第一次同时搜索第一行,第二行,超时非常严重(有80分超时),搜索方法略微改变了一下,剪枝没有改就90了(但是找bug花了我很多时间),这说明了什么?:细节,严谨,前瞻性很重要。。。

总之这一年的题我做得非常浮躁,20+80+100+20=220,如果NOIP2018我还这么马虎,我就完了。。。

猜你喜欢

转载自blog.csdn.net/Hi_KER/article/details/81507987