感想在前:说个实话,今天打比赛打的是真不怎么样,,睡觉,,没有提交上代码,就算是爆零了,也不应该会出现没有提交上代码的问题啊!!!!!!!
这里要解释一下捆绑测试:就是在规定的部分测试数据中,如果有一个是wa掉的,那么在该组测试数据全部wa掉,就是下面的情况。
第一题
三笔画(3lines)
【题目描述】
二维平面内有 n 个不同的点, Alice 需要在平面内画至多 3 条直线使得所有点在直线上。
问: Alice 能否完成任务, 如果能, 输出”YES”; 否则, 输出”NO”。
注意: 由于 Alice 的画图水平有限, 直线只能平行于坐标轴。
【输入数据】
第一行,一个整数 n。
接下来 n 行,第 i+1 行包含空格隔开的整数 xi,yi,表示第 i 个点的坐标。
【输出数据】
若 Alice 能完成任务, 输出”YES”, 否则输出”NO”。
【样例输入】
6
1 7
0 0
1 2
2 0
1 4
3 4
【样例输出】
YES
【样例解释】
三条直线分别为 x=1,y=0,y=4。
【数据范围】
对于 30%的数据,1 <= n <= 13。
对于 60%的数据,1 <= n <= 20。
对于 100%的数据,1 <= n <= 5e4,0 <= xi, yi <= 1e9。
题解
3lines:
考虑这三条直线的状态,只可能为
(1)三条水平线
(2)两条水平线+一条垂直线
(剩余情况交换 x,y 坐标即可)
我们用一个数组统计同一 y 坐标上有几个点。
对于 1)的情况,只需判断是否只有三个及以下的 y 坐标上有点即可。
对于 2)的情况,可以枚举垂直线的 x 坐标,将这条垂直线上的点全部删去,判断剩下的
点的 y 坐标是否只有两种及以下。
将点按 x 坐标排序后即可做到 O(n)的扫描。
由于坐标较大,可以离散化预处理。
错因
当然,我没有提交。。。。。。
但是我发现自己的思路是错误的。。。。
一开始想的是最小生成树和扩展域并查集。
读到这里的时候,可以想一下为什么是错误的。
但是最后在订正这一道题的时候,发现自己的思路是错误的。
原因:
-
对于并查集和最小生成树而言,只要有横坐标相等或者是纵坐标相等的话都是满足条件的,那么我们就可以直接合并或者是直接连上一条权值为0的边;
-
那么我们就可以通过统计最后一共有多少个祖先或者是最后的最小生成树的边数有多少并且和3进行比较一下,就可以得出答案;
-
这么乍一看好像是对的,但是最仔细思考一下就发现了这个答案是错误的,如果是
1 2
1 3
1 4
……
全部都是只有横坐标或者是纵坐标的其中一个相等的话,那么这样做是显然成立的; -
但是如果换一组样例呢?
1 2
2 3
3 4
这样的话用并查集或者是最小生成树求出来的答案是1,但是实际的答案却是2; -
因为不论是横坐标还是纵坐标我们都把他们笼统化了,并没有加以区别,所以会出现3,4情况的差别,所以就错了。。。。
code
#include<bits/stdc++.h>
using namespace std;
const int nn=5e4+10;
int n;
int xx[nn],yy[nn];
struct point
{
int x,y;
}p[nn];
int sum[nn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
void read_d()
{
n=read();
for(int i=1;i<=n;++i)
{
xx[i]=read(), yy[i]=read();
p[i].x=xx[i], p[i].y=yy[i];/*保留离散信息*/
}
sort(xx+1,xx+1+n); sort(yy+1,yy+1+n);
int n1=unique(xx+1,xx+1+n)-(xx+1);
int n2=unique(yy+1,yy+1+n)-(yy+1);
for(int i=1;i<=n;++i)
{
p[i].x=lower_bound(xx+1,xx+1+n1,p[i].x)-xx;
p[i].y=lower_bound(yy+1,yy+1+n2,p[i].y)-yy;
}
}
bool mycmp(point x,point y)
{
return x.x<y.x||(x.x==y.x&&x.y<y.y);
}
bool work()
{
int tot=0;
sort(p+1,p+1+n,mycmp);/*按照x进行排序*/
for(int i=1;i<=n;++i)
{
if(sum[p[i].y]==0) ++tot;/*sum[i]表示的是i表示的纵坐标出现过几次,tot记录的是如果直线全部都是横着的(切纵坐标),需要的刀数*/
sum[p[i].y]++;
}
if(tot<=3) return true;
int t=1,w;/*枚举坐标*/
while(t<=n)
{
w=t;
while(w<n&&p[w+1].x==p[t].x) w++;
for(int i=t;i<=w;++i)
if(sum[p[i].y]==1) sum[p[i].y]--,tot--;
if(tot<=2) return true;
for(int i=t;i<=w;++i)
if(!sum[p[i].y]) sum[p[i].y]++,tot++;
t=w+1;
}
return false;
}
int main()
{
freopen("3lines.in","r",stdin);
freopen("3lines.out","w",stdout);
read_d();
if(work())
{
printf("YES\n");
return 0;
}
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;++i)
swap(p[i].x,p[i].y);
if(work())
{
printf("YES\n");
return 0;
}
printf("NO\n");
return 0;
}
第三题
放积木(block)
【问题描述】
Alice 有 n 块积木,放置第 i 块积木会占据区间[Li, Ri]。
Alice 每次会腾出一个区间放积木,她希望放的积木尽可能多,对每个询问区间,你需
要回答 Alice 最多可放置的积木数量。
注意: 积木与积木的放置区间不可重叠,且任意选定的积木放置区间不能超出询问区间。
【输入格式】
第一行三个整数 n,q,len,表示积木的数量,询问数和 len 的大小(数据保证 1≤Li,Ri≤
len)。
接下来 n 行,每行两个整数 Li,Ri,表示砖头的魔法标记。
接下来 q 行,每行两个整数 ai,bi,表示 moreD 选定的区间。
【输出格式】
对于每组询问输出对应的答案。
【输入样例】
3 2 4
1 2
2 3
3 4
1 3
3 3
【输出样例】
1
0
【数据范围】
对于 30%的数据满足 n <= 10, q <= 10。
对于 60%的数据满足 n <= 1,000, q <= 1,000, 1 <= Li, Ri <= 1,000。
对于 100%的数据满足 n <= 1e5, q <= 1e5, 1 <= Li, Ri <= len <= 1e5。
题解
block:
对于 60%的数据,
我们可以用贪心算法。把所有线段按 Ri 小到大排序,每次询问时按 Ri 坐标从小到
大扫描所有线段,只要线段被区间包含且不与之前的线段冲突,那么就把这条线段加入
解。
时间复杂度 O(q*n)。
对于 100%的数据,
每次暴力扫描太慢了,考虑倍增。
f[i,j]表示从位置 i 开始,选择 2^j 条线段,Ri 最大的线段 Ri 最小是多少。显然所有
的 f[i,j]可以在 O(nlogn)的时间内计算出来(len 与 n 同级)。
询问时我们从 x 开始,从大到小枚举 k,如果选择 2^k 条线段后没有超出区间的范
围,那么答案加上 2^k,然后继续统计 f[x,k]+1~y 这段区间的答案。这样单次询问复杂
度是 O(logn)的,询问的总复杂度是 O(Qlogn),可以解决这道题。
对于60分的贪心,为什么这个做法是正确的呢??
- 首先做法是所有线段按 Ri 小到大排序,询问时按 Ri 坐标从小到大扫描所有线段,那么这样的话就可以保证所有的线段都是会被扫描一遍的而且还保证了没有后效性;
- 其次,对于每一个要扫描的区间,有两种可能的情况,第一种是可以直接构成答案,ans++;第二种是与前面冲突,不选;
- 为什么构成冲突的话就不选这个区间?如果选了这个区间,那么就要删去前面的一个区间,但是前面的区间更靠前,对于ans的贡献无疑是更大的,此时我们根据贪心的思想当然是选择前面的而不是后面的。
- 而且从前往后遍历保证了不重不漏,一定是最优的答案。
- 但是这个的复杂度有点高。。。。
60分code
#include<bits/stdc++.h>
using namespace std;
const int nn=1010;
int n,Q,m;
struct qv
{
int x,y,len;
}q[nn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
bool mycmp(qv x,qv y)
{
return x.y<y.y;
}
int main()
{
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
n=read(); Q=read(); m=read();
for(int i=1;i<=n;++i)
q[i].x=read(), q[i].y=read(), q[i].len=q[i].y-q[i].x+1;
sort(q+1,q+1+n,mycmp);
for(int i=1;i<=Q;++i)
{
int l=read(), r=read();
int ans=0, pos=l-1;
for(int i=1;i<=n;++i)
{
if(q[i].x<l||q[i].y>r) continue;
if(q[i].x<=pos) continue;/*与前面有重叠的部分*/
ans++;
pos=q[i].y;
}
printf("%d\n",ans);
}
return 0;
}
100分的倍增
#include<bits/stdc++.h>
using namespace std;
const int nn=1e5+10;
int n,Q,len,t;
int f[nn][30];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int main()
{
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
n=read(), Q=read(), len=read();
t=(int)(log(len)/log(2))+1;
for(int i=1;i<=len+2;++i)
for(int j=0;j<=t;++j)
f[i][j]=len+1;
for(int i=1;i<=n;++i)
{
int a=read(), b=read();
f[a][0]=min(f[a][0],b);
}
for(int i=len+1;i>=1;--i) f[i][0]=min(f[i][0],f[i+1][0]);
for(int j=1;j<=t;++j)
for(int i=len+1;i>=1;--i)
f[i][j]=min(f[i+1][j],f[f[i][j-1]+1][j-1]);
for(int i=1;i<=Q;++i)
{
int ans=0;
int l=read(), r=read();
for(int k=t;k>=0;--k)
if(f[l][k]<=r)
l=f[l][k]+1, ans+=(1<<k);
printf("%d\n",ans);
}
return 0;
}