题解:P10192 [USACO24FEB] Moorbles S

一看到这道题就感觉是爆搜,于是我们就枚举每一次Bessie的可能选择,选可能的最小字典序的序列,写出了这样的代码:

#include <iostream>
using namespace std;

int T,n,m,k;
int a[300010][10];

bool check(int p,int sum)   //判断在第p轮时Elsie有sum颗弹珠是否必胜
{
	while(p<=m)
	{
		if(sum<=0)
		{
			return false;   //输了
		}
		int jsmax=0;   //最大的可能出的奇数
		int osmax=0;   //最大的可能出的偶数
		int jsmin=1e9;   //最小的可能出的奇数
		int osmin=1e9;   //最小的可能出的偶数
		for(int i=1;i<=k;i++)
		{
			if(a[p][i]%2==1)
			{
				jsmax=max(jsmax,a[p][i]);
				jsmin=min(jsmin,a[p][i]);
			}
			else
			{
				osmax=max(osmax,a[p][i]);
				osmin=min(osmin,a[p][i]);
			}
		}
		if(jsmin!=1e9 && osmin!=1e9)   //偶数和奇数都可能出,最大化损失
		{
			sum-=min(jsmax,osmax);
		}
		else if(osmin!=1e9)   //只可能出偶数,最小化利益
		{
			sum+=osmin;
		}
		else if(jsmin!=1e9)   //同理
		{
			sum+=jsmin;
		}
		p++;   //开始下一轮
	}
	return sum>0;
}

void dfs(int p,int sum)
{
	if(p>m)
	{
		return;
	}
	int jsmax=0;   //最大的可能出的奇数
	int osmax=0;   //最大的可能出的偶数
	int jsmin=1e9;   //最小的可能出的奇数
	int osmin=1e9;   //最小的可能出的偶数
	for(int i=1;i<=k;i++)
	{
		if(a[p][i]%2==1)
		{
			jsmax=max(jsmax,a[p][i]);
			jsmin=min(jsmin,a[p][i]);
		}
		else
		{
			osmax=max(osmax,a[p][i]);
			osmin=min(osmin,a[p][i]);
		}
	}
	if(jsmin==1e9)   //只出偶数,肯定得猜偶数
	{
		if(p==m)
		{
			cout<<"Even";
		}
		else
		{
			cout<<"Even ";
		}
		dfs(p+1,sum+osmin);
	}
	else if(check(p+1,sum-jsmax))   //判断猜偶数是否还可以必胜
	{
		if(p==m)
		{
			cout<<"Even";
		}
		else
		{
			cout<<"Even ";
		}
		dfs(p+1,sum-jsmax);
	}
	else   //只能猜奇数了
	{
		if(p==m)
		{
			cout<<"Odd";
		}
		else
		{
			cout<<"Odd ";
		}
		if(osmin==1e9)
		{
			dfs(p+1,sum+jsmin);
		}
		else
		{
			dfs(p+1,sum-osmax);
		}
	}
}

int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n>>m>>k;
		for(int i=1;i<=m;i++)
		{
			for(int j=1;j<=k;j++)
			{
				cin>>a[i][j];
			}
		}
		if(!check(1,n))   //无解
		{
			cout<<-1<<endl;
			continue;
		}
		dfs(1,n);
		cout<<endl;
	}
	return 0;
}

很明显,这样会超时,于是考虑优化。

可以发现,在check的时候,我们做了很多重复的计算,导致时间复杂度退化到了 $O(n^{2})$。所以,我们可以预处理出在第ppp轮时可能的最大损失,这样就可以将时间优化到 $O(n)$。

上代码:

#include <iostream>
using namespace std;

int T,n,m,k;
int a[300010][10];
int f[300010];   //f[i]表示第i轮可能的最大损失

void dfs(int p,int sum)
{
	if(p>m)
	{
		return;
	}
	int jsmax=0;
	int osmax=0;
	int jsmin=1e9;
	int osmin=1e9;
	for(int i=1;i<=k;i++)
	{
		if(a[p][i]%2==1)
		{
			jsmax=max(jsmax,a[p][i]);
			jsmin=min(jsmin,a[p][i]);
		}
		else
		{
			osmax=max(osmax,a[p][i]);
			osmin=min(osmin,a[p][i]);
		}
	}
	if(jsmin==1e9)
	{
		if(p==m)
		{
			cout<<"Even";
		}
		else
		{
			cout<<"Even ";
		}
		dfs(p+1,sum+osmin);
	}
	else if(sum-jsmax>=f[p+1])   //最大损失也能扛得住,猜偶数必胜
	{
		if(p==m)
		{
			cout<<"Even";
		}
		else
		{
			cout<<"Even ";
		}
		dfs(p+1,sum-jsmax);
	}
	else
	{
		if(p==m)
		{
			cout<<"Odd";
		}
		else
		{
			cout<<"Odd ";
		}
		if(osmin==1e9)
		{
			dfs(p+1,sum+jsmin);
		}
		else
		{
			dfs(p+1,sum-osmax);
		}
	}
}

int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n>>m>>k;
		for(int i=1;i<=m;i++)
		{
			for(int j=1;j<=k;j++)
			{
				cin>>a[i][j];
			}
		}
		f[m+1]=1;   //判断需要,要是0就直接输了
		//这里和之前的check有点像
		for(int i=m;i>=1;i--)
		{
			int jsmax=0;
			int osmax=0;
			int jsmin=1e9;
			int osmin=1e9;
			for(int j=1;j<=k;j++)
			{
				if(a[i][j]%2==1)
				{
					jsmax=max(jsmax,a[i][j]);
					jsmin=min(jsmin,a[i][j]);
				}
				else
				{
					osmax=max(osmax,a[i][j]);
					osmin=min(osmin,a[i][j]);
				}
			}
			if(jsmin!=1e9 && osmin!=1e9)
			{
				f[i]=f[i+1]+min(jsmax,osmax);
			}
			else if(jsmin==1e9)
			{
				f[i]=max(1,f[i+1]-osmin);
			}
			else if(osmin==1e9)
			{
				f[i]=max(1,f[i+1]-jsmin);
			}
		}
		if(f[1]>n)
		{
			cout<<-1<<endl;
			continue;
		}
		dfs(1,n);
		cout<<endl;
	}
	return 0;
}

写完了才发现根本不用递归,循环就够了

posted @ 2024-03-24 15:28  xlaser  阅读(19)  评论(0)    收藏  举报  来源