心得
D题分类讨论,开始写错了好几发,最终WA了4发,赛中搞过去了,然而rank800+GG
E题和F题都是可补的,然而F题补了之后还是觉得好巧妙,是现有水平较难吸收的类型
利用F题,又一次提到枚举集合的子集,学了一下这个东西,就是一个板子,单开了一篇博客
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动态存这个位里的两个披萨的最小代价和对应位置
暴力枚举第一个披萨所在集合,暴力枚举第二个披萨所在集合,
记录最小代价和,和两个集合的并集,暴力枚举统计该并集的子集内有多少人,
优先按人数更新,其次按最小代价和更新;
虽然枚举的顺序优先级变了,但是更新的时候顺序是对的,所以答案是对的
题目没卡时限,跑得挺快,实际上可以直接枚举集合子集降到
代码
#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;
}