CodeForces 732D Exams

/*

这题的思路据是二分+贪心,二分枚举所有解,贪心判断能否考完 
注意,这题自己写代码时,出了一个很隐蔽的bug,当时本来已经把变量 n、m 定义为全局变量了,但居然在main函数里又定义了一次,这就相当于屏蔽了全局变量,虽然输入是正确的,但是,在judge()函数判断能否考完的过程中,所用的n、m都是错的(因为此时真正的是作为局部变量出现的,但judge()函数种使用的为全局) 
以及注意,一般情况下,是不建议定义全局变量的,这题其实不定义也未尝不可,只是judge()函数就要传递3个参数了 

  BTW,其实入门经典里有提到过,全局变量被屏蔽,可能会导致错误。我以为我还算是仔细,今天看来,其实我也还是很粗心的...
  
  最初写这题总结的时候,我还不会二分法,参考了blog:
  http://www.cnblogs.com/westwind1005/p/5976882.html
*/ 



#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N];
int d[N];
int pass[N];
int n, m;
int judge(int x)
{
	memset(pass, 0, sizeof(pass));
	int pre = x - 1;
	for (int i = x; i >= 1; i--) //从后往前遍历天数
	{
		pre = min(pre, i - 1); //不断更新为这门考试的准备时间,最多也只能是i - 1天的复习时间
		if (d[i] && !pass[d[i]] && a[d[i]] <= pre)
		{
			pass[d[i]] = 1;
			pre -= a[d[i]] + 1; //+1为下次考试所占的一天,若下次要考试,则下次的准备时间,除了要减去这次考试的准备时间,还要减去下次作为考试的那天
		}
	}
	for (int i = 1; i <= m; i++)
	if (!pass[i]) return 0;
	return 1;
}
int main()
{
	while (cin >> n >> m)
	{	
		int ans = -1;
		for (int i = 1; i <= n; i++) cin >> d[i];
		for (int i = 1; i <= m; i++) cin >> a[i];
		
		int l = 1,r = n;
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if (judge(mid))
			{
				ans = mid; r = mid - 1;
			}
			else
			{
				l = mid + 1;
			}
		}
		cout << ans << endl;
	}		
	return 0;
}


/*
  法二
  后来在另外一个blog上,看到judge函数的另外一种写法,这种写法比较容易理解,所以我也自己写了一次
  思路来自: http://blog.csdn.net/qq_34374664/article/details/52853212
  BTW,上面那篇博客里,博主对这题的解释十分详细,比我自己的代码的解释详细多了...
*/


#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N];
int d[N];
int pass[N];
int n, m;
int judge(int x)
{
	memset(pass, 0, sizeof(pass));
	int sum = 0, cnt = 0; // cnt 用来记录通过的科目数,sum用来目前还需要在前面找多少天复习 
	for (int i = x; i >= 1; i--) //从后往前遍历天数
	{
		if (d[i] && !pass[d[i]]) // 如果可以考试且这门课没考,则考 
		{
			pass[d[i]] = 1;
			sum += a[d[i]];
			cnt++;
		}
		else if (sum > 0) //如果还需要从前面抽出时间复习,那么就用这天复习,否则就当作休息;注意一定要当前需要前面sum天复习,sum才能自减,如果sum本来就为0,就不可减了,否则WA 
		{
			sum--;
		} 
	}
	if (sum > 0|| cnt < m) return 0;
	return 1;
}
int main()
{
	while (cin >> n >> m)
	{	
		int ans = -1;
		for (int i = 1; i <= n; i++) cin >> d[i];
		for (int i = 1; i <= m; i++) cin >> a[i];
		
		int l = 1,r = n;
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if (judge(mid))
			{
				ans = mid; r = mid - 1;
			}
			else
			{
				l = mid + 1;
			}
		}
		cout << ans << endl;
	}		
	return 0;
}


/*
  法三:
  法三利用了栈,我觉得是三种写法总最容易懂的,但似乎也就没有前两种简洁了。
  这三种方法,本质上是一样的,只是写法的区别,思路上差异不大
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N];
int pass[N];
int n, m;

struct day
{
	int test, have; //变量分别表示:当天能考的科目,已为该科目准备了多久
	day()
	{
		have = 0;
	}
}d[N];

int judge(int x)
{
	int ans = 0; // 记录有几门科目考完 
	memset(pass, 0, sizeof(pass));
	stack<day> s; 
	day now; // 当前天数 
	for (int i = x; i >= 1; i--)
	{
		if (d[i].test && !pass[d[i].test]) //  从后往前遍历,如果某天有考试,且还没考,那就考这科 
		{
			pass[d[i].test] = 1;
			s.push(d[i]);
		}
		else if ( !s.empty() ) // 否则,取出最近的一次(没准备好)的考试,这天用来准备 
		{
			now = s.top();
			s.pop();
			now.have++;
			
			if ( now.have == a[now.test] ) //如果需要的准备时间,全都准备好了,则真正出栈。否则,准备时间子增后,再次入栈
			ans++;
			else s.push(now);
		}
	}
	if (ans == m) return 1;
	return 0;
}

int main()
{
	while (cin >> n >> m)
	{	
		int ans = -1;
		for (int i = 1; i <= n; i++) cin >> d[i].test;
		for (int i = 1; i <= m; i++) cin >> a[i];
		
		int l = 1,r = n;
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if (judge(mid))
			{
				ans = mid; r = mid - 1;
			}
			else
			{
				l = mid + 1;
			}
		}
		cout << ans << endl;
	}		
	return 0;
}





原文地址:https://www.cnblogs.com/mofushaohua/p/7789505.html