大一下第九周学习笔记

上一周写了作业,这一周作业很多就提前写了

所以这周训练的时间会比较多一些

火力全开,把kuangbin两个专题,计算机和基础和凸包刷完

 

周一 4.26 (计算几何基础)

poj 2653(判断线段与线段相交)

这道题很显然的做法就是暴力,我也想到了,但是看到n有1e5以为会超时

实际上题目说了top stick不会超过1000,我对这句话没什么反应

应该要反映出来很多木块都是被排除掉的

所以写两个for循环,看起来是n方,但是第二层循环很多木条都是很早就退出循环了,因为top stick是很少的

判断线段相交,就是端点的向量叉乘就好

#include<cstdio>
#include<vector>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
const double eps = 1e-8;

struct point
{
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
};
struct line
{
	point a, b;
	line(point a = 0, point b = 0) : a(a), b(b) {}
}L[N];
int n;
vector<int> ans;

point operator - (point a, point b) { return {a.x - b.x, a.y - b.y}; }
double operator ^ (point a, point b) { return a.x * b.y - a.y * b.x; }

bool is_intersect(line i, line j)
{
	point a = i.a, b = i.b, c = j.a, d = j.b;
	double t1 = ((a - c) ^ (b - c)) * ((a - d) ^ (b - d));
	double t2 = ((c - a) ^ (d - a)) * ((c - b) ^ (d - b));
	return t1 < 0 && t2 < 0; //不写等号就是不包括端点相交。写了等号包括端点相交 
}

int main()
{
	while(scanf("%d", &n) && n)
	{
		ans.clear();
		_for(i, 1, n) scanf("%lf%lf%lf%lf", &L[i].a.x, &L[i].a.y, &L[i].b.x, &L[i].b.y);
		
		_for(i, 1, n)
		{
			bool ok = 1;
			_for(j, i + 1, n)
				if(is_intersect(L[i], L[j]))
				{
					ok = 0; 
					break;
				}
			if(ok) ans.push_back(i);
		}
	
		printf("Top sticks: %d", ans[0]);
		REP(i, 1, ans.size()) printf(", %d", ans[i]);
		puts(".");
	}
	
	return 0;
}

poj 1066(思维 + 判断线段相交)

这道题好秀啊

我自己思考的时候是觉得每个房间看作一个点,墙看作一条长度为1的边,建图bfs一遍

但是不会建图,不知这么弄。要枚举每个房间这件事情很困难

充分思考后还是不知道,就看了题解

非常秀

 

这道题要从宏观考虑,不要从微观考虑

考虑从墙边上的一个点要到达终点

如果中间存在一个墙分隔了这两个点,那么无论如何就一定要穿过这个墙,而且只穿过一次

显然不会返回再穿过一次

所以中间有多少个墙,就至少要穿多少次

那么这么算有多少个这样的墙呢

我们可以发现这样的墙一定和起点和终点的连线相交

还发现如果墙不和连线相交,那么我们可以沿着连线直接过去,也就是说这个墙是不需要穿过的

那么答案就很明了了,看有多少个和连线相交的墙,这个墙数再加上从边界进来的一次就是答案

那么枚举每个起点,找最小值就是答案了

 

那么起点怎么找?

题目说的是从线段的中点

画画图可以发现,一个顶点是可以代替其两侧的终点的

我们可以画画终点的位置,发现它可以代表其中一个中点的情况,而此时另一个中点一定要多穿一个墙,舍弃掉

所以我们枚举顶点作为起点就行,因为它可以代表所有中点为起点的情况

 

这里还有一个坑,就是n为0的时候

我在训练赛的时候也遇到类似的坑,就是真正合法的东西为0,这时候要特判

这是一个典型的巨坑,以后做题要意识到

#include<cstdio>
#include<algorithm>
#include<vector>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 50;
const double eps = 1e-8;

struct point
{
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
};
vector<point> node;

struct line 
{ 
	point a, b; 
}L[N];
int n;
double x, y;

point operator - (point a, point b) { return {a.x - b.x, a.y - b.y}; }
double operator ^ (point a, point b) { return a.x * b.y - a.y * b.x; }

bool intersect(point a, point b, point c, point d)
{
	double t1 = ((a - c) ^ (b - c)) * ((a - d) ^ (b - d));
	double t2 = ((c - a) ^ (d - a)) * ((c - b) ^ (d - b));
	return t1 < 0 && t2 < 0; //不严格相交 
}

int main()
{
	scanf("%d", &n);
	if(n == 0) return puts("Number of doors = 1") && 0;
	
	_for(i, 1, n) 
	{
		scanf("%lf%lf%lf%lf", &L[i].a.x, &L[i].a.y, &L[i].b.x, &L[i].b.y);
		node.push_back(L[i].a); node.push_back(L[i].b); 
	}
	scanf("%lf%lf", &x, &y);
	
	int ans = 1e9;
	REP(i, 0, node.size())
	{
		int cnt = 1;
		_for(j, 1, n)
			if(intersect(L[j].a, L[j].b, point(x, y), node[i]))
				cnt++;
		ans = min(ans, cnt);
	}
	printf("Number of doors = %d\n", ans);
	
	return 0;
}

周二 4.27(计算几何基础)

poj 1410(判断线段严格相交)

这题看起来很水,但是坑很多

1.输入矩形的坐标要处理一下

2.线段完全在矩形里也算,感觉题目没讲清楚,我以为是边界相交

然后我发现我之前的模板的一个错误

如果不严格相交,不包括交点的话,就是小于0就好

如果严格相交,包括交点,那首先要改成小于等于0

其次共线要特判,因为有共线但是不相交的情况,这种情况下为0

所以就共线的时候特判一下有没有相交,可以用点乘

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const double eps = 1e-8;
 
struct point
{
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
};
 
point operator - (point a, point b) { return {a.x - b.x, a.y - b.y}; }
double operator ^ (point a, point b) { return a.x * b.y - a.y * b.x; }
double operator * (point a, point b) { return a.x * b.x + a.y * b.y; }
 
int dcmp(double t)
{
	if(fabs(t) < eps) return 0;
	if(t > 0) return 1;
	return -1;
}
 
bool intersect(point a, point b, point c, point d)
{
	if(dcmp((a - b) ^ (c - d)) == 0) //共线的情况要特判 坑 
	{
		if((a - c) * (b - c) > 0 && (a - d) * (b - d) > 0) return false; //点乘均为正则共线但是不相交 
		return true; 
	}
	double t1 = ((a - c) ^ (b - c)) * ((a - d) ^ (b - d));
	double t2 = ((c - a) ^ (d - a)) * ((c - b) ^ (d - b));
	return dcmp(t1) <= 0 && dcmp(t2) <= 0; 
}
 
int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		double x1, y1, x2, y2;
		scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
		point a(x1, y1), b(x2, y2);
		
		double x3, y3, x4, y4;
		scanf("%lf%lf%lf%lf", &x3, &y3, &x4, &y4);
		if(x4 < x3) swap(x4, x3); //坑 
		if(y4 < y3) swap(y4, y3);
		
		bool ok = 0;
		if(intersect(a, b, point(x3, y3), point(x4, y3))) ok = 1;
		if(intersect(a, b, point(x3, y3), point(x3, y4))) ok = 1;
		if(intersect(a, b, point(x3, y4), point(x4, y4))) ok = 1;
		if(intersect(a, b, point(x4, y3), point(x4, y4))) ok = 1;
		if(x3 <= x1 && x1 <= x4 && y3 <= y1 && y1 <= y4 && x3 <= x2 && x2 <= x4 && y3 <= y2 && y2 <= y4) ok = 1; //线段在矩形里面,巨坑 
		
		if(ok) puts("T");
		else puts("F");
	}
	
	return 0;
}

poj 1696(计算夹角)

这题读完题就想到了凸包,想用凸包的那个算法

后来发现不太不一样,这是个螺旋,不是凸包

然后我突然发现所有点都可以选到,每次就选择最外围的那个,一直往里面绕圈就行

要做到这一点,就选择一个夹角最小的就可以了

注意可能有夹角相同的情况,这个时候选近的,这是一个坑

#include<cstdio>
#include<cmath>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)  
using namespace std;
 
const double eps = 1e-8;
const int N = 60;

struct point 
{
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
}p[N];
int vis[N], ans[N], top, n, start, t;

point operator - (point a, point b) { return {a.x - b.x, a.y - b.y}; }
double operator * (point a, point b) { return a.x * b.x + a.y * b.y; }

double length(point a)
{
	return sqrt(a.x * a.x + a.y * a.y);
}

double angle(point a, point b) //用acos求出夹角 
{
	return acos((a * b) / (length(a) * length(b)));
}

int dcmp(double x)
{
	if(fabs(x) < eps) return 0;
	if(x > 0) return 1;
	return -1;
}
 
int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		top = 0; 
		memset(vis, 0, sizeof(vis));
		
		double mi = 1e308;
		scanf("%d", &n);
		_for(i, 1, n)
		{
			scanf("%d%lf%lf", &t, &p[i].x, &p[i].y);
			if(mi > p[i].y)
			{
				mi = p[i].y;
				start = i;
			}
		}
		
		p[0] = point(0, mi); //初始点 
		ans[0] = 0;
		ans[++top] = start;
		vis[start] = 1;
		
		while(top < n)
		{
			mi = 1e308; int x;
			_for(i, 1, n)
				if(!vis[i])
				{
					double rad = angle(p[ans[top]] - p[ans[top - 1]], p[i] - p[ans[top]]);
					if(mi > rad || dcmp(mi - rad) == 0 && length(p[ans[top]] - x) > length(p[ans[top]] - i)) //注意,计算几何里面共线是一个常见的坑 
					{
						mi = rad;
						x = i;
					}
				}
			ans[++top] = x;
			vis[x] = 1;
		}
		
		printf("%d", top);
		_for(i, 1, top) printf(" %d", ans[i]);
		puts("");
	} 
	
	return 0;
}

周三 4.28

最近可能有出去比赛的机会,所以今天在整理模板,我翻了翻以前我每周写的笔记

发现下学期的训练量大了挺多,做的题多了很多,而上学期的训练有些杂乱无章

看来经过了一个学期,我的时间管理技巧好了很多,每天都有一定的训练量

最近就复习以前学的各种算法,先熟悉模板,然后熟悉各种变形

poj 3347(思维)

这道题我是看题解的,主要卡在不知道正方形的左右端点该怎么算

一看题解挺骚的

当前正方形一定是在某个之前的正方形旁边的

而如果考虑放在之前每个正方形旁边,那最终一定是放在最右的那个了

这样就可以算出左右端点了

然后就比较简单了,要覆盖必须边长更大,所以枚举其他更大正方形,更新左右端点的大小

最后如果l < r就可以看见

注意这里可能会有l == r的,而又是浮点数,所以要考虑eps

#include<cstdio>
#include<cmath>
#include<algorithm>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)  
using namespace std;

const int N = 60;
const double eps = 1e-8;
double l[N], r[N], s[N]; 
int n;

int dcmp(double x)
{
	if(fabs(x) < eps) return 0;
	if(x > 0) return 1;
	return -1;
}

int main()
{
	while(scanf("%d", &n) && n)
	{
		_for(i, 1, n) 
		{
			scanf("%lf", &s[i]);
			double mx = 0;
			_for(j, 1, i - 1)
				mx = max(mx, r[j] - fabs(s[j] - s[i]) / sqrt(2));
			l[i] = mx; r[i] = l[i] + s[i] * sqrt(2);
		}
		
		_for(i, 1, n)
		{
			_for(j, 1, i - 1)
				if(s[j] > s[i])
					l[i] = max(l[i], r[j]);
			_for(j, i + 1, n)
				if(s[j] > s[i])
					r[i] = min(r[i], l[j]);
			if(dcmp(l[i] - r[i]) < 0) printf("%d ", i); //浮点数写比较的时候,尤其是可能相等的时候,就要考虑eps。这里是有可能相等的 
		}
		puts("");
	}
	
	return 0;
}

周四 4.29(计算几何基础)

poj 2826(分类讨论 + 精度)

这题我是独立想出来的,各种情况都考虑到了,尤其是想到了被遮挡的情况

但是我因为精度问题过不了,最后看题解,ans改成ans + eps就过了

这篇博客谈计算几何中的浮点误差谈得很好,也提到这道题目https://blog.csdn.net/qq_40791842/article/details/96511961

如下为此博客中内容

现在考虑一种情况,题目要求输出保留两位小数。有个case的正确答案的精确值是0.005,按理应该输出0.01,但你的结果可能是0.005000000001(恭喜),也有可能是0.004999999999(悲剧),如果按照printf(“%.2lf”, a)输出,那你的遭遇将和括号里的字相同。

解决办法是,如果a为正,则输出a+eps, 否则输出a-eps

典型案例: POJ2826

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)  
using namespace std;

const double eps = 1e-8;

struct point
{
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
};
int n;

point operator - (point a, point b) { return {a.x - b.x, a.y - b.y}; }
point operator * (point a, double k) { return {a.x * k, a.y * k}; }  
point operator / (point a, double k) { return {a.x / k, a.y / k}; }   
double operator ^ (point a, point b) { return a.x * b.y - a.y * b.x; }

int dcmp(double x)
{
	if(fabs(x) < eps) return 0;
	if(x > 0) return 1;
	return -1;	
} 

bool intersect(point a, point b, point c, point d) 
{
	if(dcmp((a - b) ^ (c - d)) == 0) return false; //判断是否相交的时候注意共线的情况 
	double t1 = ((a - c) ^ (b - c)) * ((a - d) ^ (b - d));
	double t2 = ((c - a) ^ (d - a)) * ((c - b) ^ (d - b));
	return dcmp(t1) <= 0 && dcmp(t2) <= 0;
}

point make_point(point a, point b, point c, point d)
{
	double s1 = (a - c) ^ (d - c);
	double s2 = (b - c) ^ (d - c);
	return (b * s1 - a * s2) / (s1 - s2);
} 

int main()
{
	scanf("%d", &n);
	_for(i, 1, n)
	{
		double x1, y1, x2, y2, x3, y3, x4, y4;
		scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &x1, &y1, &x2, &y2, &x3, &y3, &x4, &y4);
		point a(x1, y1), b(x2, y2), c(x3, y3), d(x4, y4);
		
		if(!intersect(a, b, c, d))
		{
			puts("0.00");
			continue;
		}
		
		point p = make_point(a, b, c, d);
		vector<point> ve;
		if(dcmp(y1 - p.y) > 0) ve.push_back(a);
		if(dcmp(y2 - p.y) > 0) ve.push_back(b);
		if(dcmp(y3 - p.y) > 0) ve.push_back(c);
		if(dcmp(y4 - p.y) > 0) ve.push_back(d);
		
		if(ve.size() != 2)
		{
			puts("0.00");
			continue;
		}
		
		if(ve[1].y < ve[0].y) swap(ve[1], ve[0]);
		
		if(dcmp((ve[0].x - p.x) * (ve[1].x - p.x)) > 0 && dcmp(ve[1].y - ve[0].y) != 0)
			if(dcmp(ve[0].x - p.x) < 0 && ((ve[1] - p) ^ (ve[0] - p)) > 0 && dcmp(ve[1].x - ve[0].x) <= 0 ||
			   dcmp(ve[0].x - p.x) > 0 && ((ve[1] - p) ^ (ve[0] - p)) < 0 && dcmp(ve[1].x - ve[0].x) >= 0)
				{
					puts("0.00");
					continue;
				}

		double ans = ((ve[0] - p) ^ (ve[1] - p)) / 2;
		printf("%.2f\n", fabs(ans) * (ve[0].y - p.y) / (ve[1].y - p.y) + eps);
	}
	
	return 0;
}

周六 5.1(五一集训)

五一每天一场训练赛

每天就是补题和相关知识点

今天发挥不好

带着一股气猛刷题

HDU 6533(预处理 + 快速乘)

这题推公式就好了

注意题目的定义,小心被翻译误导

注意几点

(1)可以预处理子树的个数。我尝试了等比数列求和,但是发现要用逆元,而p不一定为质数,很麻烦。发现可以直接预处理

(2)中间一个地方会爆long long 用快速乘

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
ll a[N], n, cal[N], p;
int k, m;

ll add(ll a, ll b) { return (a + b) % p; }
ll mul(ll a, ll b)
{
	ll res = 0;
	for(; b; b >>= 1)
	{
		if(b & 1) res = add(res, a);
		a = a * 2 % p;
	}
	return res;
}

int main()
{
	while(~scanf("%d%d%d%lld", &k, &m, &n, &p))
	{
		_for(i, 1, k) scanf("%lld", &a[i]);
		sort(a + 1, a + k + 1);
		
		cal[0] = 1; ll r = 1;
		_for(i, 1, m - 2) 
		{
			r = mul(r, n);
			cal[i] = add(cal[i - 1], r);
		}
		
		int pos = 1, cnt = 1;
		ll ans = 0;
		
		_for(now, 2, m)
		{
			cnt *= n;
			ll sum = 0;
			_for(j, pos, pos + cnt - 1) sum = add(sum, a[j]);
			pos += cnt;
			ans = add(ans, mul(sum, cal[m - now]));	
		}
		printf("%lld\n", ans);
	}
	
	return 0;
}

然后猛补莫队

P3901 数列找不同(莫队裸题)

发现之前我已经思考过类似的莫队的问题了,解决问题的关键在于分块

莫队用来处理区间问题,主要是利用之前处理的答案,然后再区间上进行修改左右端点

但是按照左端点排序会很容易超时,直接n方

我们可以把左端点放到块中,用到了分块的思想

就可以优化

一共根号n个块,每次移动最多n,所以复杂度是n根号n的

排序的时候左端点按照块排序,右端点可以奇偶块不同防卡,奇偶优化

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
struct query
{
	int l, r, id, bl;
}q[N];
int a[N], cnt[N], ans[N], n, m, sum;

bool cmp(query a, query b)
{
	if(a.bl != b.bl) return a.bl < b.bl;
	if(a.bl & 1) return a.r < b.r; //奇偶优化 防卡 
	return a.r > b.r;
}

void add(int x)
{
	if(++cnt[a[x]] == 1) sum++; //这里是数不是下标 
}

void erase(int x)
{
	if(--cnt[a[x]] == 0) sum--; //cnt数组记录每个数出现了多少次 
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%d", &a[i]);
	
	int block_size = sqrt(n);
	_for(i, 1, m)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		q[i] = {l, r, i, l / block_size};
	}
	
	sort(q + 1, q + m + 1, cmp);
	
	int l = 0, r = 0; //l = 0 r = 0 一开始什么都没有,要更新第一个区间 
	_for(i, 1, m)
	{
		int ll = q[i].l, rr = q[i].r;
		while(l < ll) erase(l++);   //erase先删除再移动 
		while(l > ll) add(--l);    //add先移动再加 因为原来已经操作过了 
		while(r > rr) erase(r--);
		while(r < rr) add(++r);
		ans[q[i].id] = (sum == (rr - ll + 1)); //这样记录答案不用再排序一次 
	}
	_for(i, 1, m) puts(ans[i] ? "Yes" : "No");
	
	return 0;
}

P1972 [SDOI2009]HH的项链(莫队)

这题是之前训练赛的题

和上一题很像

洛谷卡了,黑暗爆炸没卡

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int a[N], ans[N], cnt[N], n, m, sum;

struct query
{
	int l, r, id, bl;
}q[N];

bool cmp(query a, query b)
{
	if(a.bl != b.bl) return a.bl < b.bl;
	if(a.bl & 1) return a.r < b.r;
	return a.r > b.r;
}

inline void add(int x)
{
	if(++cnt[a[x]] == 1) sum++;
}

inline void del(int x)
{
	if(--cnt[a[x]] == 0) sum--;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &a[i]);
	int block = sqrt(n);
	
	scanf("%d", &m);
	_for(i, 1, m)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		q[i] = {l, r, i, l / block};
	}
	sort(q + 1, q + m + 1, cmp);
	
	int l = 0, r = 0;
	_for(i, 1, m)
	{
		int ll = q[i].l, rr = q[i].r;
		while(l < ll) del(l++);
		while(l > ll) add(--l);
		while(r < rr) add(++r);
		while(r > rr) del(r--);
		ans[q[i].id] = sum;
	}
	
	_for(i, 1, m) printf("%d\n", ans[i]);

	return 0;
}

CF86D Powerful array(莫队拓展)

就处理贡献的时候手算一下就好

要开long long WA了一发

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e6 + 10;
int cnt[N], n, m;
ll a[N], ans[N], sum;

struct query
{
	int l, r, id, bl;
}q[N];

bool cmp(query a, query b)
{
	if(a.bl != b.bl) return a.bl < b.bl;
	if(a.bl & 1) return a.r < b.r;
	return a.r > b.r;
}

void add(int x)
{
	sum += a[x] * (2 * cnt[a[x]] + 1);
	cnt[a[x]]++;
}

void del(int x)
{
	cnt[a[x]]--;
	sum -= a[x] * (2 * cnt[a[x]] + 1);
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%lld", &a[i]);
	int block = sqrt(n);
	
	_for(i, 1, m)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		q[i] = {l, r, i, l / block};
	}
	sort(q + 1, q + m + 1, cmp); //记得排序预处理 
	
	int l = 0, r = 0;
	_for(i, 1, m)
	{
		int ll = q[i].l, rr = q[i].r;
		while(l < ll) del(l++);
		while(l > ll) add(--l);
		while(r < rr) add(++r);
		while(r > rr) del(r--);
		ans[q[i].id] = sum;
	}
	_for(i, 1, m) printf("%lld\n", ans[i]);
	
	return 0;
}

HDU 6534(莫队 + 树状数组 + 离散化)

离散化的时候要保留一下相差为k的信息

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int N = 6e4 + 10;
int a[N], ans[N], n, m, sum, k;
int L[N], R[N], f[N];
vector<int> ve;

struct query
{
	int l, r, id, bl;
}q[N];

int lowbit(int x) { return x & (-x); }

void modify(int x, int p)
{
	for(; x < N; x += lowbit(x))
		f[x] += p;
}

int Sum(int x)
{
	int res = 0;
	for(; x; x -= lowbit(x))
		res += f[x];
	return res;
}

bool cmp(query a, query b)
{
	if(a.bl != b.bl) return a.bl < b.bl;
	if(a.bl & 1) return a.r < b.r;
	return a.r > b.r;
}

int ask(int l, int r) { return Sum(r) - Sum(l - 1); }

void add(int x)
{ 
	sum += ask(L[x], R[x] - 1);
	modify(a[x], 1);
}

void del(int x)
{
	modify(a[x], -1);
	sum -= ask(L[x], R[x] - 1);
}

void lsh()
{
	ve.push_back(-1e9); //让最小下标为1 最小值打进去 
	sort(ve.begin(), ve.end());
	ve.erase(unique(ve.begin(), ve.end()), ve.end());  //注意写法 
	
	_for(i, 1, n)
	{
		L[i] = lower_bound(ve.begin(), ve.end(), a[i] - k) - ve.begin();
		R[i] = upper_bound(ve.begin(), ve.end(), a[i] + k) - ve.begin(); //r右端 + 1 
	}
	_for(i, 1, n) a[i] = lower_bound(ve.begin(), ve.end(), a[i]) - ve.begin();
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	_for(i, 1, n) 
	{
		scanf("%d", &a[i]);
		ve.push_back(a[i]);
	}
	int block = sqrt(n);
	
	_for(i, 1, m)
	{
		int l, r; 
		scanf("%d%d", &l, &r);
		q[i] = {l, r, i, l / block};
	}
	
	sort(q + 1, q + m + 1, cmp);
	lsh();
	
	int l = 1, r = 0; //l = 1好一些  l=0会多删一个0 
	_for(i, 1, m)
	{
		int ll = q[i].l, rr = q[i].r;
		while(l < ll) del(l++);
		while(l > ll) add(--l);
		while(r < rr) add(++r);
		while(r > rr) del(r--);
		ans[q[i].id] = sum;
	}
	_for(i, 1, m) printf("%d\n", ans[i]);

	return 0;
}

五一七天就这么搞

训练赛 + 疯狂补题

poj 1039(计算几何基础 + 细节)

这道题卡了好久

有了之前的经验,很快想到枚举端点的直线

但是实现的时候很多细节没考虑到

对于当前的直线

就一段一段判断,看有没有相交,再第一个交点那里停下来,判断是否能更新答案

这里又写坑

(1)x坐标可能为负数,所以ans初始化要为最小值

(2)判相交的时候是不严格相交。这就导致了可能从端点处穿出去而判断错误

这个时候为了避免这种情况

换一种方法,用叉积判断上下两个线段是否在当前直线的同侧。

这种方法就可以避免这种情况

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<vector>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
const int N = 30;
const double eps = 1e-8;
 
struct point
{
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
}p[N][2];
int n;
 
point operator - (point a, point b) { return point(a.x - b.x, a.y - b.y); }
double operator ^ (point a, point b) { return a.x * b.y - a.y * b.x; }
point operator * (point a, double k) { return point(a.x * k, a.y * k); } 
point operator / (point a, double k) { return point(a.x / k, a.y / k); }
 
int dcmp(double x)
{
	if(fabs(x) < eps) return 0;
	if(x > 0) return 1;
	return -1;	
} 
 
bool is_intersect(point a, point b, point c, point d)
{
	return dcmp(((a - c) ^ (b - c)) * ((a - d) ^ (b - d))) < 0;
}
 
point make_point(point a, point b, point c, point d)
{
	double s1 = (a - c) ^ (d - c);
	double s2 = (b - c) ^ (d - c);
	return (b * s1 - a * s2) / (s1 - s2);
} 
 
int main()
{
	while(~scanf("%d", &n) && n)
	{
		_for(i, 1, n)
		{
			scanf("%lf%lf", &p[i][1].x, &p[i][1].y);
			p[i][0] = point(p[i][1].x, p[i][1].y - 1);
		}
 
		double ans = -1e300;  //负数坑 
		bool ok = 0; 
		_for(i, 1, n)
			_for(g, 0, 1)
				_for(j, i + 1, n)
					_for(r, 0, 1)
					{
						if(g == r) continue;
						point a = p[i][g], b = p[j][r];
						int pos = 1;
						
						for(; pos < n; pos++) //这里的条件注意。前后两个端点都要 
						{
							if(((a - b) ^ (p[pos + 1][1] - a)) * ((a - b) ^ (p[pos + 1][0] - a)) > eps) break;
							if(((a - b) ^ (p[pos][1] - a)) * ((a - b) ^ (p[pos][0] - a)) > eps) break;
						}
						
						if(pos == n)
						{
							ok = 1;
							break;
						}
						
						if(pos >= j)
						{
							point t;
							if(is_intersect(a, b, p[pos][1], p[pos + 1][1]))
								t = make_point(a, b, p[pos][1], p[pos + 1][1]);
							else t = make_point(a, b, p[pos][0], p[pos + 1][0]);
							ans = max(ans, t.x);
						}
					}
					
		if(ok) puts("Through all the pipe.");
		else printf("%.2f\n", ans);
	}
 
	return 0;
}

周日 5.2(五一集训)

今天依然训练赛

发挥不好,做得磕磕绊绊的

疯狂补题

A Math Problem(坑)

这是一道签到题,但是我WA了好多次

16的16次方是爆long long的也爆unsigned long long的

然后我用了double 然而我忘记了double的有效位数是有限的,所以当值很大的时候double的值误差挺大的

所以依然wa

最后我是特判了一下,大于15的15次方就输出15

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
ull a[20];

int main()
{
	_for(i, 1, 16)
	{
		a[i] = 1;
		_for(j, 1, i) a[i] *= i;
	}
	
	ull n;
	while(~scanf("%llu", &n))
	{
		if(n >= a[15]) printf("15\n");
		else
		{
			_for(i, 1, 15)
				if(n < a[i + 1])
				{
					printf("%d\n", i);
					break;
				}
		}
	}
	
	return 0;
}

Covering(递推 + 矩阵快速幂)

这道题做了好久

静下心来推公式,化简,最后得出一个递推公式,然后矩阵快速幂就好了

注意构造的矩阵有负数,所以结果有负数,最后输出的时候要特判一下ans < 0就加上p

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int mod = 1000000007;

struct node
{
	ll s[5][5];
	node() { memset(s, 0, sizeof(s)); }
};

node operator * (node a, node b)
{
	node c;
	_for(i, 1, 4)
		_for(j, 1, 4)
			_for(r, 1, 4)
				c.s[i][j] = (c.s[i][j] + (a.s[i][r] * b.s[r][j]) % mod) % mod;	
	return c;
}

node binpow(node a, ll b)
{
	node res; 
	_for(i, 1, 4) res.s[i][i] = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a;
		a = a * a;
	}
	return res;
}

int main()
{
	ll n;
	while(~scanf("%lld", &n))
	{
		node k;
		k.s[1][4] = -1;
		k.s[2][1] = 1; k.s[2][4] = 1;
		k.s[3][2] = 1, k.s[3][4] = 5; 
		k.s[4][3] = 1; k.s[4][4] = 1;

		ll ans = 0, h[5];
		h[0] = 1, h[1] = 1, h[2] = 5, h[3] = 11;
		
		if(n <= 3)
		{
			printf("%lld\n", h[n]);
			continue;
		}
		
		k = binpow(k, n - 3);
		_for(r, 1, 4) ans = (ans + h[r - 1] * k.s[r][4] % mod) % mod;

		if(ans < 0) ans += mod;
		printf("%lld\n", ans);
	}

	return 0;
}

Duizi and Shunzi(思维)

这是一道思维题

但是我考试时就是没有想出来

我第一感觉是尽量多的对子,先取完,然后最后剩下的数看有没有顺子

但是这样过不了最后一个样例,因为有时候选择了一个对子会少掉两个顺子

我不知道该怎么处理

正解的思路是尽可能取对子,这点我想对了,但是顺子的处理方式我没想到

从小到大遍历一遍,都取对子,然后如果当前数的前两个都有1而且当前这个数有的话,就取顺子

这样就把之前多的利用起来了,也避免了最后一个样例的情况

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int cnt[N], n;

int main()
{
	while(~scanf("%d", &n))
	{
		memset(cnt, 0, sizeof(cnt));
		_for(i, 1, n)
		{
			int x; scanf("%d", &x);
			cnt[x]++;
		}
		
		int ans = 0;
		_for(i, 1, n)
		{
			if(i >= 3 && cnt[i - 2] && cnt[i - 1] && cnt[i])
			{
				ans++;
				cnt[i - 2]--;
				cnt[i - 1]--;
				cnt[i]--;
			}
			ans += cnt[i] / 2;
			cnt[i] %= 2;
		}
		printf("%d\n", ans);
	}

	return 0;
}

Query on A Tree(离线 + 字典树合并)

这题看到可以用可持久化Trie做,之后补一下相关知识点,比如主席树

这题首先是离线处理,题目中关于询问的,离线处理是一个重要思想

离线了之后意味着可以dfs一遍,回溯的时候统计答案,这样保证了子树

然后对每一个节点的值建立字典树,之后字典树合并

注意一开始每个点的字典树的根节点就是标号

合并的时候比较骚,要用递归的思想来合并

都有这个节点就继续往下,其中一个没有就返回

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int t[N * 35][2], val[N], ans[N], n, q, cnt;
vector<pair<int, int> > p[N];
vector<int> g[N];

void add(int root, int x)
{
    int p = root;
    for(int i = 31; i >= 0; i--)
    {
        int now = (x >> i) & 1; //取出x的第i位 从第0位到第31位。注意开始是第0位不是第1位
        if(!t[p][now]) t[p][now] = ++cnt;
        p = t[p][now];
    }
}

int query(int root, int x)
{
    int p = root, res = 0;
    for(int i = 31; i >= 0; i--)
    {
        int now = (x >> i) & 1;
        if(t[p][now ^ 1])
        {
            res |= 1 << i;   //注意不要写错  1 << i就是2的i次方,就是第i位
            p = t[p][now ^ 1];
        }
        else p = t[p][now];
    }
    return res;
}

int Union(int u, int v) //这个合并要递归
{
    if(u == 0) return v;
    if(v == 0) return u;
    t[u][0] = Union(t[u][0], t[v][0]);
    t[u][1] = Union(t[u][1], t[v][1]);
    return u;
}

void dfs(int u)
{
    add(u, val[u]);
    for(auto v: g[u])
    {
        dfs(v);
        Union(u, v);
    }
    for(auto x: p[u]) ans[x.first] = query(u, x.second);
}

int main()
{
    while(~scanf("%d%d", &n, &q))
    {
        cnt = n; //关键,每个点的初始节点都已经设置好了
        _for(i, 1, n)
        {
            scanf("%d", &val[i]);
            g[i].clear();
            p[i].clear();
        }
        memset(t, 0, sizeof(t));

        _for(i, 1, n - 1)
        {
            int x; scanf("%d", &x);
            g[x].push_back(i + 1);
        }
        _for(i, 1, q)
        {
            int u, x;
            scanf("%d%d", &u, &x);
            p[u].push_back(make_pair(i, x));
        }

        dfs(1);
        _for(i, 1, q) printf("%d\n", ans[i]);
    }

	return 0;
}
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;


class B //含有纯虚函数的类是抽象类,没有对象。只能用指针
{
public:
    virtual void print() = 0; //纯虚函数
};

class B1: public B
{
public:
    void print() { puts("B1"); }
};

void out2(B *b)
{
    b->print();
}

int main()
{
    B1 b;
    out2(&b);

    B *p; //第二种方式
    p = &b;
    p->print();

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值