Codeforces Round #568 (Div. 2) (E/暴力+F/位运算+暴力)

心得

D题分类讨论,开始写错了好几发,最终WA了4发,赛中搞过去了,然而rank800+GG

E题和F题都是可补的,然而F题补了之后还是觉得好巧妙,是现有水平较难吸收的类型

利用F题,又一次提到O(3^n)枚举集合的子集,学了一下这个东西,就是一个板子,单开了一篇博客

E.Polycarp and Snakes

在n*m(n<=2e3,m<=2e3)的图上画蛇,蛇是水平的或是数值的,

蛇是按字母增序画的,先a后b等等,后画的可以覆盖先画的

给出一张图,点代表空地,其余字母代表蛇,

问地图能否满足画蛇条件,不能输出NO,

能的话输出YES,输出最大蛇的数量,每条蛇的左上坐标、右下坐标

题解

由于覆盖,考虑倒序挑出蛇来,每挑出一条将其标为特殊符号*,代表这个地方可以画字典序更小的蛇

初始更新行的最小值、最大值,列的最小值、最大值,

如果行最小==行最大,说明蛇是水平的;如果列最小==列最大,说明蛇是竖直的;

否则蛇的占地规模>=2*2,不满足题意,

如果出现图上只有ac两条蛇之类的情形,可以理解为b蛇完全被c蛇覆盖,沿用c结果即可;

先找到图中字典序存在的最大的字母,倒序模拟即可

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+10;
int t,n,m;
char s[maxn][maxn];
int Cmx[26],Cmn[26];//行 
int Rmx[26],Rmn[26];//列
int mx;//最大字母 
bool flag; 
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		mx=-1;
		flag=1;//有无解 
		scanf("%d%d",&n,&m);
		for(int i=0;i<26;++i)
		{
			Cmx[i]=-1;Cmn[i]=n+1; 
			Rmx[i]=-1;Rmn[i]=m+1;
		}
		for(int i=0;i<n;++i)
		scanf("%s",s[i]);
		for(int i=0;i<n;++i)
		{
			for(int j=0;j<m;++j)
			{
				if(s[i][j]=='.')continue;
				int v=s[i][j]-'a';
				mx=max(mx,v);//当前出现最大字母 
				Cmx[v]=max(Cmx[v],i);Cmn[v]=min(Cmn[v],i);
				Rmx[v]=max(Rmx[v],j);Rmn[v]=min(Rmn[v],j);
			}
		}
		for(int i=mx;i>=0;--i)
		{
			if(Cmx[i]==-1)
			{
				Cmx[i]=Cmx[i+1];
				Cmn[i]=Cmn[i+1];
				Rmx[i]=Rmx[i+1];
				Rmn[i]=Rmn[i+1];
				continue;
			}
			if(Cmn[i]==Cmx[i])
			{
				for(int j=Rmn[i];j<=Rmx[i];++j)
				if(s[Cmx[i]][j]=='a'+i||s[Cmx[i]][j]=='*')s[Cmx[i]][j]='*';
				else
				{
					flag=0;
					break;
				}
			}
			else if(Rmn[i]==Rmx[i])
			{
				for(int j=Cmn[i];j<=Cmx[i];++j)
				if(s[j][Rmx[i]]=='a'+i||s[j][Rmx[i]]=='*')s[j][Rmx[i]]='*';
				else
				{
					flag=0;
					break;
				}
			}
			else flag=0;
			if(!flag)break; 
		}
		if(!flag)puts("No");
		else 
		{
			puts("YES");
			printf("%d\n",1+mx);
			for(int i=0;i<=mx;++i)
			printf("%d %d %d %d\n",1+Cmn[i],1+Rmn[i],1+Cmx[i],1+Rmx[i]);
		} 
	}
	return 0;
} 

F.Two Pizzas

n(1<=n<=1e5)个人想在m(2<=m<=1e5)块披萨中只挑两块,披萨块共有9种

以下i行,第i个人想吃cnt[i]种披萨,以下cnt[i]个数f1到fcnt[i](1<=f<=9)

以下j行,第j块披萨有cost[j],代表买这块披萨的代价,由num[j]种披萨块组成,以下num[j]个数g1到gnum[j](1<=g<=9)

每个人只有所有种数被满足,才会去吃披萨,

输出挑出的两块披萨的编号,使之满足人数最大,

满足人数相同的情况下,使之两块披萨代价和最小

题解

人想吃的披萨只有1到9,按位压成集合S,cnt[S]存这个位里有几个人

披萨块组成的披萨只有1到9,按位压成集合T,vector<pair>T动态存这个位里的两个披萨的最小代价和对应位置

暴力枚举第一个披萨所在集合,暴力枚举第二个披萨所在集合,

记录最小代价和,和两个集合的并集,暴力枚举统计该并集的子集内有多少人,

优先按人数更新,其次按最小代价和更新;

虽然枚举的顺序优先级变了,但是更新的时候顺序是对的,所以答案是对的

题目没卡时限,O(2^9*2^9*2^9)跑得挺快,实际上可以直接枚举集合子集降到O(2^9*3^9)

代码

#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
using namespace std; 
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const int N=512;
int n,m,k,v,bit;
int cnt[N],b[N],c;
vector<P>s[N];//(x,y)记录(cost,pos) 
int pos1,pos2;
int curres,res,curcost,cost,mask;
P ans; 
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&k);
		bit=0;
		for(int j=1;j<=k;++j)
		{
			scanf("%d",&v);
			v--;
			bit|=(1<<v);
		}
		cnt[bit]++;//这个位有多少人 
	}
	for(int i=1;i<=m;++i)
	{
		scanf("%d",&c);
		scanf("%d",&k);
		bit=0;
		for(int j=1;j<=k;++j)
		{
			scanf("%d",&v);
			v--;
			bit|=(1<<v);
		}
		s[bit].pb(P(c,i));
		sort(s[bit].begin(),s[bit].end());
		while(s[bit].size()>2)s[bit].pop_back();//每个集合只维护两个最小的(代价,位置) 
	} 
	res=0;
	cost=2*INF+1;//防止INF+INF 
	for(int i=1;i<N;++i)
	{
		for(int j=1;j<N;++j)
		{
			if(i==j)
			{
				if(s[i].size()<2)continue;
				curcost=s[i][0].fi+s[i][1].fi;
				pos1=s[i][0].se;
				pos2=s[i][1].se;
			}
			else
			{
				if(!s[i].size()||!s[j].size())continue;
				curcost=s[i][0].fi+s[j][0].fi;
				pos1=s[i][0].se;
				pos2=s[j][0].se;
			}
			mask=i|j;
			curres=0;
			for(int p=1;p<N;++p)
			{
				if((p&mask)==p)
				curres+=cnt[p];
			}
			//最小代价基础上 统计最大值 2^27 
			if(res<curres||(curres==res&&cost>curcost))
			{
				res=curres;
				cost=curcost;
				ans=P(pos1,pos2);
			}	
		}
	}
	printf("%d %d\n",ans.fi,ans.se); 
	return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值