Week 12 作業

A - 必做題 - 1

給出n個數,zjm想找出出現至少(n+1)/2次的數, 現在需要你幫忙找出這個數是多少?

輸入

本題包含多組數據:
每組數據包含兩行。
第一行一個數字N(1<=N<=999999) ,保證N爲奇數。
第二行爲N個用空格隔開的整數。
數據以EOF結束。

輸出

對於每一組數據,你需要輸出你找到的唯一的數。

樣例輸入

5
1 3 2 3 3
11
1 1 1 1 1 5 5 5 5 5 5
7
1 1 1 1 1 1 1

樣例輸出

3
5
1

思路

綜述

最初:想的是沒有說數據範圍,想必可能超過int,所以乾脆開了long long.於是寫了個O(n^2)的算法,直接導致TLE;
改進:開1e6的數組來存儲,i索引號對應的數字是數字i出現次數。

總結

最初拿到這個題感覺有點小坑:沒有說數據範圍,直接導致了第一次提交超時:(想必應該是題目不夠嚴謹)
在這裏插入圖片描述

代碼

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int N;
const int maxn = 1e6;
int a[maxn];
int term;
void init(){for(int i=0;i<1e6;i++)a[i]=0;}

int main(){
	while(cin>>N){
		init();
		for(int i=0;i<N;i++){
			cin>>term;
			a[term]++;
		}
		
		int num = (N+1)/2; 
		
		for(int i=0;i<1e6;i++){
			if(a[i]>=num){
				cout<<i<<endl;
				break;
			}
		}
	}
}

B - 必做題 - 2 三維bfs搜索

zjm被困在一個三維的空間中,現在要尋找最短路徑逃生!
空間由立方體單位構成。
zjm每次向上下前後左右移動一個單位需要一分鐘,且zjm不能對角線移動。
空間的四周封閉。zjm的目標是走到空間的出口。
是否存在逃出生天的可能性?如果存在,則需要多少時間?

輸入

輸入第一行是一個數表示空間的數量。
每個空間的描述的第一行爲L,R和C(皆不超過30)。
L表示空間的高度,R和C分別表示每層空間的行與列的大小。
隨後L層,每層R行,每行C個字符。
每個字符表示空間的一個單元。’#‘表示不可通過單元,’.‘表示空白單元。
zjm的起始位置在’S’,出口爲’E’。每層空間後都有一個空行。
L,R和C均爲0時輸入結束。

輸出

每個空間對應一行輸出。
如果可以逃生,則輸出如下
Escaped in x minute(s).
x爲最短脫離時間。
如果無法逃生,則輸出如下
Trapped!

樣例輸入

    3 4 5
    S….
    .###.
    .##..
    ###.#

    #####
    #####
    ##.##
    ##…

    #####
    #####
    #.###
    ####E

    1 3 3
    S##
    #E#
    ###

    0 0 0

樣例輸出

Escaped in 11 minute(s).
Trapped!

思路

綜述

bfs搜索的模板題;
最初拿到題目的時候有點蒙。細細思考發現和二維空間並無差異。
不可以用dfs,dfs可以找到出口,但是找不到最短的路線。

總結&&拓展

二維地圖可以輕易拓展到三維;
於是n維空間的種種題目也可以類比做出。

踩踩坑

scanf("%c",ch);這樣會讀入空格和回車符
在這裏插入圖片描述以下兩種寫法可以忽略回車符和空格

cin>>ch;
scanf(" %c",&ch);

代碼

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
struct node{
	int x,y,z,step;
	node(){
		step=0;
	}
};

char mp[40][40][40];
int vis[40][40][40];
int L,R,C;//L-->x  R-->y  C-->z
int sx,sy,sz;
int ex,ey,ez;
//依次是 上下東西南北 
int dx[] = {0,0,0,0,1,-1};
int dy[] = {0,0,1,-1,0,0};
int dz[] = {1,-1,0,0,0,0};

int check(node thenode){
	if(thenode.x < 0 || thenode.x >= L)return 1;
	if(thenode.y < 0 || thenode.y >= R)return 1;
	if(thenode.z < 0 || thenode.z >= C)return 1;
	if(mp[thenode.x][thenode.y][thenode.z]=='#')return 1;
	if(vis[thenode.x][thenode.y][thenode.z])return 1;
	return 0;
}

int bfs(){
	memset(vis,0,sizeof(vis));
	queue<node> qq;
	
	while(!qq.empty())qq.pop();
	
	node Node;
	Node.x = sx,Node.y = sy,Node.z = sz,Node.step=0;
	qq.push(Node);
	
	while(!qq.empty()){
		node now = qq.front();
		qq.pop();
		//找到出口 
		if( ex==now.x && ey==now.y && ez==now.z ){
			return now.step;
		}		
		//遍歷六個方向 
		node next; 
		for(int i=0;i<6;i++){
			next.x = now.x + dx[i];
			next.y = now.y + dy[i];
			next.z = now.z + dz[i];
			next.step = now.step+1;
			
			
			if(check(next))continue;//判斷是否可行 
			
			vis[next.x][next.y][next.z] = 1;
			qq.push(next);
			
		}
		
	}
	return 0;
}


int main(){
	
	while(1){
		cin>>L>>R>>C;
		if(L==0 && R==0 && C==0)break;
		
		for(int i=0;i<L;i++){
			for(int j=0;j<R;j++){
				scanf("%s",mp[i][j]);
				for(int k=0;k<C;k++){
					
					if(mp[i][j][k]=='S'){
						sx = i;
						sy = j;
						sz = k;
					}if(mp[i][j][k]=='E'){
						ex = i;
						ey = j;
						ez = k;						
					}
				}
			}
		}
		
		int flag = bfs();
		
		if(flag==0 ){
			cout<<"Trapped!"<<endl;
		}else{
			printf("Escaped in %d minute(s).\n",flag);
		}
		
	}
	
}

C - 必做題 - 3 m段最長子串

東東每個學期都會去寢室接受掃樓的任務,並清點每個寢室的人數。
每個寢室裏面有ai個人(1<=i<=n)。從第i到第j個宿舍一共有sum(i,j)=a[i]+…+a[j]個人
這讓宿管阿姨非常開心,並且讓東東掃樓m次,每一次數第i到第j個宿舍sum(i,j)
問題是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情況是不被允許的。也就是說m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人數可以爲負數。。。。(1<=n<=1000000)

輸入

輸入m,輸入n。後面跟着輸入n個ai 處理到 EOF

輸出

輸出最大和

樣例輸入

1 3 1 2 3
2 6 -1 4 -2 3 -2 3

樣例輸出

6
8 

思路

綜述

該題用到方法有:dp+滾動數組;
題意歸結爲一句話就是m段最長子串問題;
難點就在定狀態和狀態轉移方程以及滾動數組的應用。

狀態定義

dp[i][j]表示前j個數據共i個區間所能取到的最大值;(注意其中第a[j]包含在內)

狀態轉移:

(注意其中第a[j]包含在內)

dp[i][j] = max(dp[i][j-1]+a[j] , dp[i-1][k] + a[j])(1<=k<=j-1)

總結

動態規劃感覺有這麼一個流程
寫出最原始的狀態定義和狀態轉移方程->滾動數組進行優化空間複雜度->其他方面優化時間複雜度
直接貼出多次不成功代碼,供學習借鑑

最原始的沒有滾動數組的

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

int n,m;
const int maxn = 1e6+10;
const int maxm = 100;
int a[maxn];
int dp[maxm][maxn];
int dpmax[maxn];

void init(){
    memset(dp,0,sizeof(dp));
    memset(dpmax,0,sizeof(dpmax));
}

void in(){
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }


}

void work()
{
    //work
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
        	dp[i][j] = dp[i][j-1] + a[j];

            for(int k=i-1;k<=j-1;k++){
                dp[i][j] = max(dp[i][j],dp[i-1][k]);
            }
			
        }
    }
    
    
    int ans = -123;
    for(int i=m;i<=n;i++)ans = max(dp[m][i],ans);
    cout<<ans<<endl; 
    // cout<<max(dp[m][n][0],dp[m][n][1])<<endl;
}


int main(){
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        init();
        in();
        work();

	}
    

}

添加滾動數組,但是超時

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

int n,m;
const int maxn = 1e6+10;
const int maxm = 100;
int a[maxn];
int dp[maxn][2];
int dpmax[maxn];

void init(){
    memset(dp,0,sizeof(dp));
    memset(dpmax,0,sizeof(dpmax));
}

void in(){
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }


}

void work()
{
	int now = 0;
	dp[1][now] = a[1];
    for(int i=2;i<=n;i++) dp[i][now] = max(dp[i-1][now]+a[i],a[i]);
	now = !now; 
	//work
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
        	dp[j][now] = dp[j-1][now] + a[j];

            for(int k=i-1;k<=j-1;k++){
                dp[j][now] = max(dp[j][now],dp[k][!now]);
            }
			
        }
        now = !now;
    }
    
    
    int ans = -123;
    for(int i=m;i<=n;i++)ans = max(dp[i][now],ans);
    cout<<ans<<endl; 

}


int main(){
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        init();
        in();
        work();

	}
    

}

完整的代碼

#include <iostream>
using namespace std;
int n,m;
const int maxn = 1e6+50;
int dp[maxn];
int num[maxn];

int calc(){
	int term=0;
	for(int i=1;i<=m;i++){
		 
		term=0;
		for(int k=1;k<=i;k++)term+=num[k];
		dp[n] = term;
		for(int j=i+1;j<=n;j++){
			if(term<dp[j-1]) term = dp[j-1];
			term += num[j];
			dp[j-1] = dp[n];
			if(term>dp[n])dp[n]=term;
		}
	}
	return dp[n];
	
}

int main(){
 	while(scanf("%d%d",&m,&n)!=EOF){
		for(int i=1;i<=n;i++){
			scanf("%d",&num[i]);
			dp[i] = 0;
			 
		} 
		printf("%d\n",calc());
	} 
}

D - 選做題 - 1

We give the following inductive definition of a “regular brackets” sequence:
the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:
(), [], (()), ()[], ()[()]
while the following character sequences are not:
(, ], )(, ([)], ([(]
Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.
Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].

輸入

The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.

輸出

For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.

樣例輸入

((()))
()()()
([]])
)[)(
([][][)
end

樣例輸出

6
6
4
0
6

思路

綜述

課上例題,是正常的動態規劃,沒有用到滾動數組之類的;

狀態定義

總結共兩種情況:

f[i][j]表示子序列[i,j]變成合法序列需要添加的最少括號的數量。
最終答案就應該是f[1][n]

狀態轉移

總結共兩種情況:

* f[i][j] = min{f[i][k] + f[k+1][j]},( i≤k<j)
* 若S形如[S’](S’) ,那麼f[i][ j] = min{f[i+1][ j-1]}

總結

動態規劃的經典例題,動態規劃題目最開始沒有入門,做題多了之後發現有章可循;

坑點

初始化問題:
以後都單獨寫一個函數,多組數據之間需要初始化的變量都統一進行處理,這樣不容易遺漏之類的;

void init(){
	//初始化,因爲合法數據是j>i所以,不需要初始化上三角 
	for(int i=0;i<110;i++)
		for(int j=0;j<110;j++)
			dp[i][j]=0;
}

match匹配函數
注意有兩組可能

int match(int i,int j){
	if( (s[i]=='('&&s[j]==')') || (s[i]=='['&&s[j]==']') ){
		return 1;
	}
	return 0;
}

代碼

#include <iostream>
#include <cstring>
#include <string>
#include <string.h>
#include <cmath>
#include <algorithm>
using namespace std;

string s;
int dp[110][110];

int match(int i,int j){
	if( (s[i]=='('&&s[j]==')') || (s[i]=='['&&s[j]==']') ){
		return 1;
	}
	return 0;
}
void init(){
	//初始化,因爲合法數據是j>i所以,不需要初始化上三角 
	for(int i=0;i<110;i++)
		for(int j=0;j<110;j++)
			dp[i][j]=0;
}
int calc(){
	int n = s.size();
	for(int i=0;i<n-1;i++)
		dp[i][i+1] = match(i,i+1);
	for(int len=2;len<=n-1;len++){
		for(int i = 0 , j ; (j=i+len) < n ;i++){
			dp[i][j] = dp[i+1][j-1]+match(i,j);
			for(int k=i;k<j;k++)			
				dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]);
				
		}
	} 
	return dp[0][n-1];
}
int main(){
	while(1){
		cin>>s;
		if(s=="end")break;
		init();
		int num = calc();
		num*=2; 
		cout<<num<<endl;
	}
}

E - 選做題 - 2

馬上假期就要結束了,zjm還有 n 個作業,完成某個作業需要一定的時間,而且每個作業有一個截止時間,若超過截止時間,一天就要扣一分。
zjm想知道如何安排做作業,使得扣的分數最少。
Tips: 如果開始做某個作業,就必須把這個作業做完了,才能做下一個作業。

輸入

有多組測試數據。第一行一個整數表示測試數據的組數
第一行一個整數 n(1<=n<=15)
接下來n行,每行一個字符串(長度不超過100) S 表示任務的名稱和兩個整數 D 和 C,分別表示任務的截止時間和完成任務需要的天數。
這 n 個任務是按照字符串的字典序從小到大給出。

輸出

每組測試數據,輸出最少扣的分數,並輸出完成作業的方案,如果有多個方案,輸出字典序最小的一個。

樣例輸入

2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3

樣例輸出

2
Computer
Math
English
3
Computer
English
Math

綜述

是一道動態規劃的題目;動態規劃裏面的狀壓dp;
主要是因爲對於某些題目來說,狀態尤其複雜,如果使用先前的多維數組 來表示,將會導致數組維度非常大,可操作性非常低
因此我們考慮使用一些編碼技術,比如二進制編碼,用一個數字來 表示一個狀態,實現狀態壓縮的目的

關於位運算:

在這裏插入圖片描述

狀態

f[S] 表示完成 S 作業集合後被扣的最少分數

假設S是5則轉換爲二進制就是101說明該狀態完成了作業1和作業3;

轉移方程

• sum = S 作業集合對應的總時間
• f[S|(1<<x)] = f[S] + tmp(作業 x 被扣的分數) 
• c[x] = 作業 x 完成所需時間 
• d[x] = 作業 x 的 DDL 
• tmp = max ( sum + c[x] – d[x], 0 )

如何保證字典序最小

跟學長學到的套路,這類題可以按字典序排序試試,可能會有奇效;

SUM求法

注意sum在代碼裏面經常出現,但是求解的過程複雜,將其剝離開來,單獨爲一個子函數,增加可讀性;

int sum(int node){
	int time=0;
	int cnt=1;
	for(int i=1;i <= (1<<n)-1;i<<=1){
		
		if(node & i){
			time += TASK[cnt-1].day;
		}
		cnt++;
	}

return time;
}

總結

最近養成的風格:
main函數裏面將大部分的操作都分離開,這樣一目瞭然,清晰爽快;

int main(){
	cin>>T;
	while(T--){
		init();
		in();
		work();
		cout<<ans[(1<<n)-1]<<endl;
		print((1<<n)-1);
	}
}

注意

注意print裏面的狀態還是某個作業的區分,一開始的時候入坑了。(沒區分好狀態和作業)

void print(int node){
	if(node == 0) return;
	print( node - ( 1 << way[node] ) );
	cout<<TASK[way[node]].name<<endl;
}

代碼

上文有詳細註釋

#include <iostream>
#include <algorithm>
#include <vector> 
#include <cstring>
#include <stdio.h> 
#include <cstdlib>
#include <string.h>
using namespace std;
const int maxn = (1<<15) + 50;
struct task{
	int ddl;
	int day;
	string name;
	bool operator < (const task & P)const{
		return name > P.name; 
	}
};

int T,n,m;
vector<task> TASK;
int way[maxn];
//int sum[maxn];
int ans[maxn];

void init(){
	TASK.clear();
	memset(ans,63,sizeof(ans));
	memset(way,0,sizeof(way));
}

void in(){
	cin>>n;
	task tempt;
	for(int i=0;i<n;i++){
		cin>>tempt.name>>tempt.ddl>>tempt.day;
		TASK.push_back(tempt);
	}
}

int sum(int node){
	int time=0;
	int cnt=1;
	for(int i=1;i <= (1<<n)-1;i<<=1){
		
		if(node & i){
			time += TASK[cnt-1].day;
		}
		cnt++;
	}

return time;
}

void work(){
	sort(TASK.begin(),TASK.end());
	ans[0]=0;
	int now;
	
	for(int S=1;S <= ( 1 << n ) - 1;S++){
		for(int i=0;i<TASK.size();i++){
			if( S & (1<<i) ){
				now = ans[S - (1<<i)] + max( sum(S - (1<<i)) + TASK[i].day - TASK[i].ddl , 0 );
				if(now < ans[S]){
					ans[S] = now;
					way[S] = i;
				} 
			}
		}
	}
}

void print(int node){
	if(node == 0) return;
	print( node - ( 1 << way[node] ) );
	cout<<TASK[way[node]].name<<endl;
}

int main(){
	cin>>T;
	while(T--){
		init();
		in();
		work();
		cout<<ans[(1<<n)-1]<<endl;
		print((1<<n)-1);
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章