洛谷P5962 [BOI2004]ships 船
本题不难发现是找连通块,但是这样做会 \(M\) (没试过不知道会不会 \(T\)),所以我们再考虑优化,观察输入,会发现给的是区间,所以我们只需要把区间存下来,把相邻两行相交的区间合并就行了。
用 \(line1\) 存当前行每艘船的位置,\(line2\) 存上一行每艘船的位置。都是结构体,包括船的左端点 \(l\),右端点 \(r\),编号 \(pos\)。
并查集,当然要有 \(f\) 数组,然后再用 \(siz\) 存船的大小。
找祖先时路径压缩,合并时判断两个 \(siz\) 的大小,把小的接在大的下面。
inline int Find(int x)
{
return f[x]==x?x:f[x]=Find(f[x]);
}
inline void Union(int a,int b)
{
int fa=Find(a),fb=Find(b);
if(fa==fb) return;
if(siz[fa]>siz[fb]) f[fb]=fa,siz[fa]+=siz[fb];
else f[fa]=fb,siz[fb]+=siz[fa];
}
本题输入也比较恶心,没见过这样的输入,不过也没办法。
可以先输入一个字符串,然后枚举一遍,把数读出来。
inline int read(int &pos)
{
int res=0;
while(s[pos]>='0'&&s[pos]<='9')
res=res*10+s[pos++]-'0';
return res;
}
其他就没什么了。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1000010
using namespace std;
struct node
{
int l,r,pos; //l:左端点,r:右端点,pos:编号
}line1[N],line2[N]; //line1:当前行,line2:上一行
int n,len1,len2;
int f[N],siz[N],cnt,ans[1010]; //f:父亲,siz:大小,cnt:船的个数,ans:输出时的桶
char s[N]; //输入
inline int read(int &pos)
{
int res=0;
while(s[pos]>='0'&&s[pos]<='9')
res=res*10+s[pos++]-'0';
return res;
}
inline int creat(int x) //新建船
{
f[++cnt]=cnt;
siz[cnt]=x;
return cnt;
}
inline int Find(int x)
{
return f[x]==x?x:f[x]=Find(f[x]);
}
inline void Union(int a,int b)
{
int fa=Find(a),fb=Find(b);
if(fa==fb) return;
if(siz[fa]>siz[fb]) f[fb]=fa,siz[fa]+=siz[fb];
else f[fa]=fb,siz[fb]+=siz[fa];
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
memcpy(line2,line1,sizeof(node)*len1); //这里一定要这样写,因为这是结构体
len2=len1;
len1=0;
int p=0; //枚举上一行
scanf("%s",s);
int len=strlen(s);
for(int j=0; j<len; j++)
{
if(s[j]==';') break;
int x=read(j);
int y=x;
if(s[j]=='-') j++,y=read(j);
line1[len1]=(node){x,y,creat(y-x+1)};
for(; p<len2&&line2[p].r<x; p++); //上一行的船和当前的船不相交
for(; p<len2&&line2[p].l<=y; p++) //相交
Union(line1[len1].pos,line2[p].pos);
len1++;
if(p) p--;
}
}
for(int i=1; i<=cnt; i++)
if(f[i]==i) ans[siz[i]]++;
for(int i=1000; i>=1; i--) //答案在1000以内,直接枚举
if(ans[i]) printf("%d %d\n",i,ans[i]);
return 0;
}
$$A\ drop\ of\ tear\ blurs\ memories\ of\ the\ past.$$