[FJSC2016]day1
楼房
Description
给出\(n\)个矩形的顶点坐标(每个矩形的底边都在\(x\)轴上),求这\(n\)个矩形所组成图形的轮廓线的顶点。
Input
第一行一个整数\(n\),表示矩形个数。
以下\(n\)行,每行\(3\)个整数,分别表示矩形的x坐标区间及矩形的高度\(h[i]\)。
Output
第一行一个整数\(m\),表示轮廓线顶点个数。
以下\(m\)行,每行一个坐标表示轮廓线上的顶点。从左到右遍历轮廓线并顺序输出顶点。第一个和最后一个节点的\(y\)坐标必然为\(0\)。
Sample Input
2
3 0 2
4 1 3
Sample Output
6
0 0
0 3
1 3
1 4
3 4
3 0
HINT
\(1\;\leq\;n\;\leq\;100000,1\;\leq\;h[i]\;\leq\;10^9,-10^9\;\leq\;l[i]<r[i]\;\leq\;10^9\).
Solution
这道题是离散+线段树的裸题了。用扫描线+堆也是可以做的。(由于本弱太弱,所以线段树不小心写炸了\(QAQ\))
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100001
#define M 1000001
using namespace std;
struct building{
int l,r,h;
}a[N];
struct linetree{
int l,r,h;
}lt[M];
struct answer{
int x,y;
}ans[N<<1];
int p[N<<1],n,cnt,tot;
inline void build(int i,int l,int r){
lt[i].l=l;lt[i].r=r;
if(l+1<r){
int lef=i<<1,rig;rig=lef|1;
int mid=(l+r)>>1;
build(lef,l,mid);
build(rig,mid,r);
}
}
inline void cover(int i,int l,int r,int h){
if(p[lt[i].l]>=l&&p[lt[i].r]<=r)
lt[i].h=max(lt[i].h,h);
else if(lt[i].l+1<lt[i].r){
int lef=i<<1,rig;rig=lef|1;
int mid=(lt[i].l+lt[i].r)>>1;
lt[lef].h=max(lt[lef].h,lt[i].h);
lt[rig].h=max(lt[rig].h,lt[i].h);
if(r>p[mid]) cover(rig,l,r,h);
if(l<p[mid]) cover(lef,l,r,h);
}
}
inline void ask(int i){
if(lt[i].l+1<lt[i].r){
int lef=i<<1,rig;rig=lef|1;
lt[lef].h=max(lt[lef].h,lt[i].h);
lt[rig].h=max(lt[rig].h,lt[i].h);
ask(lef);ask(rig);
}
else{
ans[++cnt].x=p[lt[i].l];
ans[cnt].y=lt[i].h;
}
}
inline void init(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].h,&a[i].l,&a[i].r);
p[++cnt]=a[i].l;p[++cnt]=a[i].r;
}
sort(p+1,p+1+cnt);tot=1;
for(int i=2;i<=cnt;i++)
if(p[i]!=p[i-1])
p[++tot]=p[i];
cnt=0;
build(1,1,tot);
for(int i=1;i<=n;i++)
cover(1,a[i].l,a[i].r,a[i].h);
cnt=0;ask(1);
ans[++cnt].x=p[tot];
tot=0;
for(int i=1;i<=cnt;i++)
if(ans[i].y!=ans[i-1].y)
tot+=2;
printf("%d\n",tot);
for(int i=1;i<=cnt;i++)
if(ans[i].y!=ans[i-1].y){
printf("%d %d\n",ans[i].x,ans[i-1].y);
printf("%d %d\n",ans[i].x,ans[i].y);
}
}
int main(){
freopen("build.in","r",stdin);
freopen("build.out","w",stdout);
init();
fclose(stdin);
fclose(stdout);
return 0;
}
单词背诵
Description
分别给你\(n\)个单词作为单词表和一篇包含\(m\)个单词的文章。
在文章中求出一段连续的区间使得区间所包含的单词表中的单词数最多(同个单词出现多次算一个)且区间内单词数最少。
Input
第一行一个数\(n\),表示单词表单词数。
接下来\(n\)行,每行是一个长度不超过\(10\)的字符串,表示一个单词表中的单词。
接着是一个数\(m\),表示文章中的单词数。
然后是\(m\)行长度不超过\(10\)的字符串,每个表示文章中的一个单词。
Output
输出文件共\(2\)行。
第\(1\)行为文章中最多包含的单词表单词数。
第\(2\)行表示在文章中包含最多单词表中的单词的最短的连续段的长度。
Sample Input
3
hot
dog
milk
5
hot
dog
dog
milk
hot
Sample Output
3
3
HINT
\(n\leq10^3,m\leq10^5\).
Solution
这道题其实是由\(2\)个子问题组成:求区间所包含的单词表中的单词数(以下简称所含单词数)最大值\(ans\)和满足最大值的最短区间长度。
前者只需要扫一遍文章,哈希判断是否在单词表中即可。
后者由于数据范围不支持\(O(n^2)\)的算法,所以需运用到双指针扫描:
- 将头尾指针指向第一个元素;
- 向右移动尾指针,直到指到最后一个元素或区间内所含单词数\(=ans\);
- 向右移动头指针,直到区间为空或再向右移一个会导致区间内所含单词数\(\not=ans\),最短区间长度\(len=min(len\),当前区间长度\()\);
- 如果尾指针没有指向最后一个元素,将头尾指针均向右移一个,返回步骤\(2\);否则结束。
(综上所述就是一道傻逼题,然后但是好像是因为一个特别傻逼的错然后爆零???)
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define L 12
#define Y 131
#define N 1001
#define M 100001
#define K 100003
using namespace std;
int h,t,sum;
int fir[K],nxt[K],cnt;
int u[N],to[M],la[N],lb[M],m,n,ans,tot;
char key[K][L],a[N][L],b[M][L];
inline int hash(char a[],int l){
int ret=0;
for(int i=1;i<=l;i++)
ret=(ret*Y*Y%K+a[i]*Y)%K;
return ret;
}
inline void init(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",a[i]+1);
la[i]=strlen(a[i]+1);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%s",b[i]+1);
lb[i]=strlen(b[i]+1);
}
for(int i=1,j,k;i<=n;i++){
k=hash(a[i],la[i]);
for(j=fir[k];j;j=nxt[j])
if(!strcmp(key[j]+1,a[i]+1))
break;
if(!j){
nxt[++cnt]=fir[k];fir[k]=cnt;
for(j=1;j<=la[i];j++)
key[cnt][j]=a[i][j];
}
}
for(int i=1,j,k;i<=m;i++){
k=hash(b[i],lb[i]);
for(j=fir[k];j;j=nxt[j])
if(!strcmp(key[j]+1,b[i]+1))
break;
if(j&&!u[j]) tot++;
u[j]++;to[i]=j;
}
memset(u,0,sizeof(u));
ans=m;h=1;
for(t=1;t<=m;t++){
if(to[t]&&!u[to[t]]) ++sum;
++u[to[t]];
while(sum==tot){
ans=min(t-h+1,ans);
u[to[h]]--;
if(!u[to[h]]) sum--;
++h;
}
}
if(!tot) ans=0;
printf("%d\n%d\n",tot,ans);
}
int main(){
freopen("word.in","r",stdin);
freopen("word.out","w",stdout);
init();
fclose(stdin);
fclose(stdout);
return 0;
}
矩阵
Description
给定一个\(N\)行\(M\)列的非负整数矩阵,求一个最大的正方形子矩阵,该矩阵满足:
矩阵中每一个元素权值都大于\(0\);
在满足上述条件的前提下,矩阵面积最大;
在满足上述条件的前提下,选择元素和最小的。
Input
第一行两个整数\(N,M\)。
接下来\(N\)行,每行\(M\)个整数。
Output
\(2\)个数,用空格隔开,第一个数为满足条件的矩阵的面积,第二个数为该矩阵各元素之和。
Sample Input
3 7
1 1 1 0 2 1 1
1 1 1 0 1 1 1
1 1 1 0 1 1 1
Sample Output
9 9
HINT
\(N,M\leq1000\).
Solution
这道题可以分解成\(2\)个子问题:求满足条件的矩阵的最大边长\(len\)、满足最大边长的最小元素和\(sum\)。
前者可以通过枚举正方形的左边所在列,然后求出以当前行的最大长度为长,向上/下最多可以扩展到哪(即求第一个长度小于当前行的,单调队列即可),边长\(=min\)(长,宽)。
后者可以通过判断当前\(i,j\)是否满足以\((i,j)\)为左上角顶点的满足条件的正方形边长是否等于\(len\)。如果满足条件,二维前缀和计算出这个正方形的和,取最小值。
(其实这天的题我口头\(AK\)了,只是都写炸了【我真是太弱了】)
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1005
using namespace std;
typedef long long ll;
int r[N][N],l[N][N],q[N],n,m,h,t,ans;
ll a[N][N],s[N][N],sum=1e15;
inline int read(){
int ret=0;char c=getchar();
while(!isdigit(c))
c=getchar();
while(isdigit(c)){
ret=ret*10+c-'0';
c=getchar();
}
return ret;
}
inline ll read_ll(){
ll ret=0;char c=getchar();
while(!isdigit(c))
c=getchar();
while(isdigit(c)){
ret=ret*10+c-'0';
c=getchar();
}
return ret;
}
inline void init(){
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=read_ll();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
s[i][j]=s[i][j-1]+a[i][j];
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
s[i][j]+=s[i-1][j];
for(int i=1,k;i<=n;i++){
for(int j=1;j<=m;j=k){
for(k=j;k<=m&&a[i][k];k++);
for(int l=j;l<k;l++)
r[i][l]=k;
while(k<=m&&!a[i][k]) ++k;
}
}
for(int j=1;j<=m;j++){
h=1;t=0;
for(int i=1;i<=n;i++){
while(h<=t&&r[i][j]<=r[q[t]][j]) t--;
l[i][j]=i-q[t];q[++t]=i;
}
}
q[0]=n+1;
for(int j=1;j<=m;j++){
h=1;t=0;
for(int i=n;i;i--){
while(h<=t&&r[i][j]<=r[q[t]][j]) t--;
l[i][j]+=q[t]-i-1;q[++t]=i;
ans=max(ans,min(l[i][j],r[i][j]-j));
}
}
for(int j=1,k;j<=m;j++){
for(int i=1;i<=n;i=k){
for(k=i;k<=n&&r[k][j]-j>=ans;k++);
if(k-i>=ans){
for(int l=k-ans;l>=i;l--)
sum=min(sum,s[l+ans-1][j+ans-1]-s[l-1][j+ans-1]-s[l+ans-1][j-1]+s[l-1][j-1]);
}
while(k<=n&&r[k][j]-j<ans) ++k;
}
}
printf("%d %lld\n",ans*ans,sum);
}
int main(){
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
init();
fclose(stdin);
fclose(stdout);
return 0;
}