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;
}

posted @ 2020-10-22 21:54  bifanwen  阅读(116)  评论(0编辑  收藏  举报