1. 程式人生 > 實用技巧 >2020.10.22 模擬賽題解

2020.10.22 模擬賽題解

CSDN同步

注:所有題目並非作者版權,也並非本人原創。本人只是為了方便大家除錯,將校內的題面與資料利用平臺做成題目,並對題面進行部分美化。特此說明。

房間開燈(\(\text{lightson}\)

簡要題意:略。

彩蛋:我們老師說這道題可以做 \(TG \space T1\),爺笑了。

做法很顯然是寬搜,但是有細節。因為有回頭路。

比方說你 \((1,1) \rightarrow (1,2) \rightarrow (1,3)\),發現 \((1,3)\)\((2,1)\) 的燈開了,這時候你就要倒回去才能走到 \((2,1)\),進行下一步操作。

那你可能會說,好,我把開燈的位置都走一遍呢?也不對。如果你開燈的房間是你走不到的呢?這怎麼辦?很顯然我們需要一個數組來記錄 當前房間的門有沒有被開啟

。被開啟的話,之後被開燈就可以直接入隊搜尋;未被開啟的話,之後被開燈也無益。最後統計答案。

這樣思路很明顯了,但是程式碼有細節。

時間複雜度:\(\mathcal{O}(n^2 + m)\).(資料出水了)

#include<bits/stdc++.h>
using namespace std;

const int N=1e2+1;

inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}

inline void write(int x) {
	if(x<0) {putchar('-');write(-x);return;}
	if(x<10) {putchar(char(x%10+'0'));return;}
	write(x/10);putchar(char(x%10+'0'));
}

const int dx[4]={-1,0,0,1};
const int dy[4]={0,-1,1,0};

int n,m,ans=0;
vector<pair<int,int> > a[N][N];
bool h[N][N],lit[N][N],door[N][N];
// h 記錄佇列是否搜尋過這個元素
// lit 記錄該房間的燈是否開啟
// door 記錄該房間的門是否開啟
queue<pair<int,int> > q;

int main() {
//	freopen("lightson.in","r",stdin);
//	freopen("lightson.out","w",stdout);
	n=read(),m=read();
	while(m--) {
		int x=read(),y=read(),u=read(),v=read();
		a[x][y].push_back(make_pair(u,v));
	} q.push(make_pair(1,1)); lit[1][1]=1; door[1][1]=1;
	while(!q.empty()) {
		int x=q.front().first,y=q.front().second;
		q.pop(); if(h[x][y]) continue; h[x][y]=1;
		for(register int i=0;i<a[x][y].size();i++) {
			int nx=a[x][y][i].first,ny=a[x][y][i].second;
			lit[nx][ny]=1;
			if(door[nx][ny] && !h[nx][ny]) q.push(make_pair(nx,ny));
		}
		for(register int i=0;i<4;i++) {
			int nx=x+dx[i],ny=y+dy[i];
			if(nx<1 || ny<1 || nx>n || ny>n || h[nx][ny]) continue;
			door[nx][ny]=1; if(lit[nx][ny]) q.push(make_pair(nx,ny));
		}
	}
	for(register int i=1;i<=n;i++) 
	for(register int j=1;j<=n;j++) if(lit[i][j]) ans++/*,printf("%d %d\n",i,j)*/;
	printf("%d\n",ans);
	return 0;
}

水果盛宴(feast)

簡要題意:略。

顯然,\(T \leq 5 \times 10^6\) 是個線性做法,如何動態規劃?

一種思路是考慮 \(f_i\) 表示 \(\leq i\) 的最大飽腹值(不喝水),最後考慮喝水的情況。則:

\(f_i = \max(f_{i-A} + A , f_{i-B} + B)\)

如何考慮喝水?則 \(f_i\)\(f_{i \div 2}\) 如何建立聯絡?只能喝一次水,難以解決問題。

這樣我們考慮記憶化搜尋,試圖改變這個策略。現在我們的狀態多一維,由於搜尋的靈活性,我們的狀態轉移靈活了 \(114514\) 倍。

當前 \(dfs(x,f)\)

表示當前的飽腹值為 \(x\),是否喝過水(\(f=1\) 喝過,\(f=0\) 未喝過),可以輕鬆轉移。

注:這裡有個坑。就是飽腹值 \(x\) 為奇數的時候也可以喝水,\(x\) 變為原來的 \(\lfloor \frac{x}{2} \rfloor\),下取整。原題題意不明確,導致本人出錯,特此說明,題面中也有標註。

另外有個技巧:可以不必開 \(f[1 - 5 \times 10^6][2]\) 的陣列,一維雜湊即可解決問題。答案的記錄每次打擂即可解決!

時間複雜度:\(\mathcal{O}(T)\).

#include<bits/stdc++.h>
using namespace std;

const int N=5e6+1;

inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}

inline void write(int x) {
	if(x<0) {putchar('-');write(-x);return;}
	if(x<10) {putchar(char(x%10+'0'));return;}
	write(x/10);putchar(char(x%10+'0'));
}

int T,A,B,ans;
bool h[N];

inline void dfs(register int x,bool f) {
	if(h[x]) return; ans=max(ans,x);
	h[x]=1;
	if(!f/* && !(x&1)*/) dfs(x>>1,1); // 喝一口水
	if(x+A<=T) dfs(x+A,f);            // 拿一個 A
	if(x+B<=T) dfs(x+B,f);            // 拿一個 B
}

int main() {
//	freopen("feast.in","r",stdin);
//	freopen("feast.out","w",stdout);
	T=read(); A=read(); B=read();
	dfs(0,0); printf("%d\n",ans);
	return 0;
}