不知不觉又欠了好多好多题目没有补…
因为第一次弄 弄成了mashup就️题目链接了
A
签到
B
最初考虑的时候认为可以先排序(比如按照y轴高度),然后按照从高到低的顺序用线段树进行区间更新、单点查询…后来发现排序出大问题…y轴高度并不能决定到底线段之间的优先级问题…假了之后就开始罚坐了orz
之后找了网上一些博客,发现流传最广的就是跟我们类似的假算法,尽管能过超弱的测试数据,但是很轻松就可以造一组数据卡掉。
最后在github里发现正解:UVA-Solutions/2013-2014 ACM-ICPC Brazil Subregional Programming Contest/B.cpp
基本算法是 扫描线
之前学习过扫描线相关知识,其最核心的思想就是动态维护集合。
在这道ballon中,考虑一条垂直于x轴的直线,从左到右开始扫描,根据遇见的点的不同类型,来进行相应的不同操作。我们设置一个集合,用来记录在当前扫描线扫过的区域中仍然尚未完结的线段的序号。容易发现,平面中有3种类型的点:线段的起点,线段的终点,x轴上的起始位置点。当遇到线段的起点后,将该起点所属的线段的序号加入集合;当遇到线段的终点后,将该终点所属的线段序号从集合中删除;当遇到起始位置点后,应该根据集合中的相关信息,得知它上升后的一些情况。如果我们可以知道每个起始位置点上升后遇到的第一个挡板编号,并且知道每个挡板编号通向的下一个挡板的编号(也可能水平卡住气球),那么我们就可以随便用个记忆化、并查集等操作快速知道气球的最终位置。那么现在的问题就是如何维护该集合,使得我们可以处理出:
因为当前集合中所有的值都是未完结的挡板,所以这些线段都会延伸到扫描线右端某个位置,这就为我们将这些线段排序提供了便利。考虑位置 P 11 ( x 0 , y 0 ) P11(x_0, y_0) P11(x0,y0)有某个起始点,对应终点 P 12 P12 P12,那么如果其右端有某个起点 P 21 ( x , y ) , x > x 0 P21(x, y), x > x_0 P21(x,y),x>x0,由于之前讨论的“延伸到右段某个位置”的特征,点 P 21 P21 P21对应的线只有在线 ( P 11 , P 12 ) (P11, P12) (P11,P12)下方才会优先被气球碰到,从而就可以根据叉乘——即有向面积来判断给定点在给定线的左右侧来判断线段之间的优先级。点 P 21 P21 P21在 P 11 P11 P11左侧时情况类似。
inline ll cross(POINT a, POINT b){return a.x * b.y - a.y * b.x;}
bool cmp(int x, int y){
POINT p11 = seg[x].first, p12 = seg[x].second, p21 = seg[y].first, p22 = seg[y].second;
if(p11.x < p21.x) return cross(p11 - p21, p12 - p21) > 0;
return cross(p21 - p11, p22 - p11) < 0;
}
这里涉及到一个不曾学过的知识点 orz
set<int, bool(*)(int, int)> s(cmp);
set<int, bool(*)(int, int)>::iterator it;
这样子写就可以为set指定关系比较的函数,具体原理才疏学浅不予考究。
另外,在扫描线算法中,点之间的优先级也至关重要。扫描线从左到右,显然第一关键字是x轴坐标;接下来因为题意规定,气球碰到板的两端也会被板引导/挡住,所以我们应该设置如下第二优先级: 线段起点 > 初始位置点 > 线段终点
。
完整代码
const int maxn = 1e5 + 10;
struct POINT{
ll x, y;
int type, id;
POINT(){};
POINT(ll _x, ll _y){
x = _x; y = _y;
}
POINT(ll _x, ll _y, int _type, int _id){
x = _x; y = _y; type = _type; id = _id;
}
bool operator < (const POINT &b)const{
if(x != b.x) return x < b.x;
if(type != b.type) return type < b.type;
return y < b.y;
}
POINT operator - (const POINT &b)const{
return POINT(x - b.x, y - b.y);
}
};
typedef pair<POINT, POINT> SEG;
POINT p[maxn * 3]; int top = 0;
SEG seg[maxn];
inline ll cross(POINT a, POINT b){return a.x * b.y - a.y * b.x;}
bool cmp(int x, int y){
POINT p11 = seg[x].first, p12 = seg[x].second, p21 = seg[y].first, p22 = seg[y].second;
if(p11.x < p21.x) return cross(p11 - p21, p12 - p21) > 0;
return cross(p21 - p11, p22 - p11) < 0;
}
set<int, bool(*)(int, int)> s(cmp);
set<int, bool(*)(int, int)>::iterator it;
int nex[maxn * 2], pnex[maxn];
int query[maxn];
int getid(){
it++;
if(it == s.end()) return -1;
return *it;
}
void sove(){
for(int i = 0; i < top; i++){
int id = p[i].id;
if(p[i].type == 0){
s.insert(id);
it = s.find(id);
if(seg[id].first.y > seg[id].second.y) nex[id] = getid();
}
else if(p[i].type == 1){
it = s.begin();
if(it == s.end()) pnex[id] = -1;
else pnex[id] = *it;
}
else{
it = s.find(id);
if(seg[id].first.y < seg[id].second.y) nex[id] = getid();
else if(seg[id].first.y == seg[id].second.y) nex[id] = -1;
s.erase(id);
}
}
}
//dp[i]记录第i个隔板的后续情况: 1.横者 -1 2.斜着记录下一个终点位置的坐标
POINT dp[maxn];
POINT dfs(ll x, ll y, int id){
if(id == -1) return POINT(x, y);
POINT p1 = seg[id].first, p2 = seg[id].second;
if(p1.y == p2.y) return POINT(x, p1.y);
if(~dp[id].x) return dp[id];
ll xx = p1.y > p2.y? p1.x: p2.x;
return dp[id] = dfs(xx, 0, nex[id]);
}
int main(){
// Fast;
for(int i = 0; i < manx; i++) dp[i].x = -1;
int n, q; scanf("%d %d", &n, &q);
for(int i = 0; i < n; i++){
POINT t1, t2;
scanf("%lld %lld %lld %lld", &t1.x, &t1.y, &t2.x, &t2.y);
if(t1.x > t2.x) swap(t1, t2);
t1.type = 0; t2.type = 2; t1.id = t2.id = i;
seg[i].first = t1; seg[i].second = t2;
p[top++] = t1; p[top++] = t2;
}
for(int i = 0; i < q; i++){
scanf("%d", query + i);
p[top++] = POINT(query[i], 0, 1, i);
}
sort(p, p + top);
sove();
for(int i = 0; i < q; i++){
POINT ans = dfs(query[i], 0, pnex[i]);
if(ans.y == 0) printf("%lld\n", ans.x);
else printf("%lld %lld\n", ans.x, ans.y);
}
}
C
暴力
D
暴力
E
签到
F
两次二分查找进行匹配,时间复杂度 O ( n ∗ l o g n ∗ l o g n ) O(n * logn * logn) O(n∗logn∗logn)
G
简单思维题。
每一行之间如果模数不对应相同,那么不可能通过统一的列交换来完成操作。而最小交换次数只需要找所有环的长度就可以容易得到。
H
*组合数学。
公式很容易推,但是如何化简…时隔久远真的不太有主意了…
最后学长用了递推式表达了最后的结果,线性递推式=>矩阵快速幂 AC
通过这道题高中学的组合数化简知识好像有点复苏,以前对于这类题就有退项做差的做法(然而也是才想起来),在程序设计比赛中其实就可以理解为是递推方程式的求解。往往一个漂亮的递推式而不是比较丑陋的求和式在ACM中更受欢迎。
I
DP
学长A的而我甚至没看题 orz
J
生成树(?)
学长之前做过就A了我也没看题 orz
好久没有打过套题…感觉还是套题能学到更多新东西 orz