1. 程式人生 > >NOIP2016 模擬賽[一中題]題解&總結

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