文章目錄
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);
}
}