NOIP2016 模拟赛[一中题]题解&总结

这套题的难度总体适中,第二题比第一题水系列!然而还是暴露出了自己的许多问题

改代码的时候吧写好的代码复制到另外一个文件去改是个好习惯,但是要记得改回来……下次比赛最后几分钟真的不能再写代码了,不然会死的很惨的

那么就总结一下今天的几道题

马云
(jackma.cpp/c/pas)
【问题描述】
Mr_he 因讨厌马云而彻底放弃网购,他的日常用品都要到商场去购买,而且必须付现金。但是现金购买,经常会遇到找零的问题,那么现在请你帮助他解决这样一个问题:
现在Mr_he 手上有n 种不同面值的硬币,每种硬币有无限多个。为了方便购物,他希望带尽量少的硬币,但是要能组合出1 到m 之间的任意值。
【输入格式】
输入文件名为jackma.in。
第一行为两个整数:m 和n,他们的意义如题目描述。
接下来的n 行,每行一个整数,第i+1 行的整数表示第i 种硬币的面值。
【输出格式】
输出文件名为jackma.out
最少需要携带的硬币数量,如果无解则输出-1。


【输入输出样例】

输入:

20 4
1 2 5 10

输出:

5
【数据范围】
50%的数据:1<=n<=10, 1<=m<=10^3;
100%的数据:1<=n<=100,1<=m<=10^9;

开始还准备搜索的,看到m范围瞬间爆炸

然后想的是dp,因为这道题和完全背包问题很相似,但是并不好写(也许好写吧,当时没仔细想),而且就算写出来了也过不了所有数据

正解是贪心,每次拿当前能拿的最大的一个,这样算出来总数肯定是最少的,如果看代码比较难理解可以试试手推样例

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,x,s[1005];
int main(){
    cin>>x>>n;
    int i,j,sum=0,ans=0;
    for(i=1;i<=n;i++)cin>>s[i];
    sort(s+1,s+1+n);
    if(s[1]!=1){
        puts("-1");
        return 0;
    }
    while(true){
        if(sum>=x){
            printf("%d",ans);
            return 0;
        }
        for(i=n;i>=1;i--)
            if(s[i]<=sum+1){sum+=s[i],ans++;break;}
    }
}

打望
(peek.cpp/c/pas)
【问题描述】
Mr_he 是一个求变的人,所以每天从学校机房回到家都要走不同的路径,当然劳累一天然后漫步
在新鲜的大路上,打望过往行人和车辆也是一件非常惬意的事。
那么现在已经知道,从Mr_he 的学校到家有n 个交叉路口,把他们从1..n 编号,我们认为编
号为1 的是学校,编号为2 的为家,有m 条双向大路把这些路口连接起来。
Mr_he 打算每天沿着一条不同的路径回家(如果两条路径有一条道路不同,那么我们认为这两条
路径是不同的),欣赏不同的风景。但他不想太晚回家,因此他不打算走“回头路”。换句话说,他只
沿着满足如下条件的道路(A,B)走:存在一条从B 出发回家的路径比所有从A 出发回家的路径都短。
那么你的任务是帮助Mr_he 计算一共有多少条不同的回家路径。
【输入格式】
输入文件名为peek.in。
第一行为n,m,交叉点的数目和道路的数目。
以下 m 行每行 3 个整数:a,b,d(1≤a,b≤n,1≤d≤1000000),表示有一条连接a 和b
的双向道路,长度为d。
【输出格式】
输出文件名为peek.out。
输出路径条数。这个数可能很大,请输出 mod 20080814 的结果。

【输入输出样例】

输入:

5 6
1 3 2
1 4 2
3 4 3
1 5 12
4 2 34
5 2 24

输出:

2

【数据范围】
20%的数据:1<n≤10
50%的数据:1<n≤100
100%的数据:1<n≤1000,n-1≤m≤100000

很容易想到的递推,f[i]表示到达i号节点的总方案,f[i]+=f[v] v∈{son[i]}

然后就是判断一条路是否有效了,其实很好判断,只要求出2号点到所有点的最短路即可,直接比较dis[u]和dis[v]就行了

当时这代码写了两次,第一次写的for循环(显然是错的),然后改成了记忆化搜索的形式,结果手贱多加了个vis数组来判断一个点是否已经被讨论了(其实不能加),结果从100坑成了10分

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#define LL long long
using namespace std;
const LL maxn=1005,mod=20080814,inf=1e15;
inline void _read(LL &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
LL f[maxn],n,m,last[maxn],dis[maxn];
bool vis[maxn];
struct node{
	LL a,b,c,Next;
	node(LL a,LL b,LL c,LL Next):a(a),b(b),c(c),Next(Next){}
};
vector<node>s;
void insert(LL a,LL b,LL c){
	s.push_back(node(a,b,c,last[a]));
	last[a]=s.size()-1;
}
void spfa(){
	queue<LL>q;
	LL i,x,v;
	for(i=1;i<=n;i++)dis[i]=inf;
	vis[2]=1,dis[2]=0;
	q.push(2);
	while(q.size()){
		x=q.front();q.pop();
		vis[x]=0;
		for(i=last[x];i>=0;i=s[i].Next){
			v=s[i].b;
			if(dis[v]>dis[x]+s[i].c){
				dis[v]=dis[x]+s[i].c;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}
LL dp(LL x){
	if(f[x]!=-1)return f[x];
	LL i,v,ans=0;
	for(i=last[x];i>=0;i=s[i].Next){
		LL v=s[i].b;
		if(dis[x]<dis[v])(ans+=dp(v))%=mod;
	}
	return f[x]=ans%mod;	
}
int main(){
	memset(last,-1,sizeof(last));
	memset(f,-1,sizeof(f));
	_read(n);_read(m);
	LL i,j,x,y,z;
	for(i=1;i<=m;i++){
		_read(x);_read(y);_read(z);
		insert(x,y,z);insert(y,x,z);
	}
	spfa();
	f[1]=(LL)1;
	dp(2);
	cout<<f[2]%mod;
}

 


游戏

【问题描述】


【输入格式】
输入文件名为game.in。
第一行有两个用空格隔开的正整数m,n,他们分别表示该谜题的方格列数和行数。
接下来m 行从左至右描述了谜题的纵向信息。每一行有若干个数字,这些数字表示该列从上至下
将会出现的连续黑色方格数。每一行用一个数字0 作为结束。
接下来n 行从上至下描述了谜题的横向信息。每一行有若干个数字,这些数字表示该行从左至右
将会出现的连续黑色方格数。每一行用一个数字0 作为结束。
【输出格式】
输入文件名为game.out。
将谜题的解打印出来。每一个方格用两个字符表示。其中,一个白色方格用“ ”(两个空格)
表示,一个黑色方格用“##”表示。因此,输出数据一共有n 行,每行2m 个字符。
我们保证输入数据有唯一解。

【输入输出样例】

输入:
4 3
2 0
0
1 1 0
2 0
1 0
1 1 0
1 2 0

输出:

    ##
##    ##
##  ####

【数据范围】
对于30%的数据,m,n<=3;
对于50%的数据,m,n<=10;
对于100%的数据,m,n<=20。

看到数据范围就晓得是道搜索题,虽然暴力分还是比较好得,但是正解并没有那么容易

首先搜索的时候是讨论当前的点放还是不放,这样暴力分就有了,然后考虑剪枝

这道题的剪枝还是很好想到的,考虑当前已经决策了的点的左上角形成的小矩形,如果那个小矩形已经有了不满足给出的条件的地方说明这种状态行不通,于是剪枝

所以接下来就是写判断状态是否合法的check函数了,还是很麻烦的……

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=25;
int n,m,Qn[maxn][maxn],Qm[maxn][maxn];
int cntm[maxn],cntn[maxn],tot;
bool s[maxn][maxn],havans;
bool check(int x,int y){
	int cnt=0,i=1,p;
	while(i<=x){
		if(s[i][y]){
			p=i+1;
			while(s[p][y]&&p<=x)p++;
			p--;
			if(cnt>=cntm[y]) return false;
			if(p==x&& p-i+1>Qm[y][cnt]) return false;
			if(p!=x && p-i+1!=Qm[y][cnt]) return false;
			cnt++;	 i=p+1;
		}
		else i++;
	}
	int need=cntm[y]-cnt-1;
	for(;cnt<cntm[y];cnt++)need+=Qm[y][cnt];
	if(n-x<need) return false;
	i=1;cnt=0;
	while(i<=y){
		if(s[x][i]){
			p=i+1;
			while(s[x][p]&&p<=y)p++;
			p--;
			if(cnt>=cntn[x]) return false;
			if(p==y && p-i+1>Qn[x][cnt]) return false;
			if(p!=y && p-i+1!=Qn[x][cnt]) return false;
			cnt++;	 i=p+1;
		}
		else i++;
	}
	need=cntn[x]-cnt-1;
	for(;cnt<cntn[x];cnt++)need+=Qn[x][cnt];
	if(m-y<need) return false;
	return true;
}
void dfs(int x,int y){
	int i,j;
	if(havans)return;
	if(x==n&&y==m+1){
		if(check(n,m)){
			for(i=1;i<=n;i++){
		    	for(j=1;j<=m;j++){
					if(s[i][j]){putchar('#');putchar('#');}
					else {putchar(32);putchar(32);}
				}
		    	putchar(10);
			}
			havans=1;	
		}
		return;
	}
	if(y==m+1)return dfs(x+1,1);
	if(check(x,y))dfs(x,y+1);
	s[x][y]=1;
	if(check(x,y))dfs(x,y+1);
	s[x][y]=0;
}
int main(){
    cin>>m>>n;
    int i,j,x;
    for(i=1;i<=m;i++)
        while(cin>>x&&x)Qm[i][cntm[i]++]=x;
    for(i=1;i<=n;i++)
        while(cin>>x&&x)Qn[i][cntn[i]++]=x;
    dfs(1,1);
}

总结:多做搜索!搜索是硬伤,许多细节要注意,一不注意就是漫长的几个小时调试时间

以后改代码改完了之后一定要想到把改完的覆盖原来的代码,因为这点小事让AC代码变WA真心不值得

By_SuperGate



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值