第十届集美大学程序设计竞赛 (B、D)图论、计算几何

B - 小M的游戏 

思路:为了下文方便表示,定义dis_{i , j} 为从 i 走到 j 的最短距离。从1开始走一遍最短路,求出dis_{1 , N}。同时我们需要确定这些最短路径当中能够经过哪些点,因此需要从N再走一遍最短路,若dis_{1 , x} + dis_{N , x} = dis_{1 , N},则代表了x这个点是能够经过的。在得到能经过的点以后,还需要注意的是有些边也是不能走的,只有dis_{1 , x} +dis_{x , y} + dis_{N , y} = dis_{1 , N}时,才表示能从x走到y。

        得到这些可经过的点和边之后,考虑胜负手的转移:已知到达N点时能够让对方输,那么N点就是必输态,此时若点 x 相邻边有一个能够让对方输的点,那么点 x 就是必胜态(谁在x点上操作谁就赢了)。

        那么在逆推胜负态的过程中,对于点x而言,只有将从x出发能到达的所有点的胜负态都判断出来,x的胜负态才能判断,因此想到了用拓扑排序。

        代码:

#include 
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second 
#define endl '\n'
const LL maxn = 4e05+7;
const LL N=1e05+10;
const LL mod=1e09+7;
const LL INF = 1e18;
typedef pairpl;
priority_queue, greater >t;
priority_queue q;
LL gcd(LL a, LL b){
	return b > 0 ? gcd(b , a % b) : a;
}

LL lcm(LL a , LL b){
	return a / gcd(a , b) * b;
}
struct node{
	int num;
	LL dis; 
	bool operator > (const node &t) const
	{
		return dis > t.dis;
	}
}tmp;
int n , m;
vectormp[N];
vectorto[N];
int isv[N];
LL front[N] , rear[N] , isw[N] , in[N] , pos[N];
void dij1()
{
	priority_queue , greater > q;
	memset(isv , 0 ,sizeof isv);
	q.push({1,0});
	front[1] = 0;
	while(!q.empty())
	{
		tmp = q.top();
		int x = tmp.num;
		q.pop();
		if(isv[x] == 1)
			continue;
		isv[x] = 1;
		for(int i = 0 ; i < (int)mp[x].size() ; i ++ )
		{
			node now = mp[x][i];
			int len = mp[x][i].dis;
			int e = mp[x][i].num; 
			if(front[e] > front[x] + len)
			{
				front[e] = front[x] + len;
				q.push({e,front[e]});
			}
		}
	}
}
void dij2()
{
	priority_queue , greater > q;
	memset(isv , 0 ,sizeof isv);
	q.push({n,0});
	rear[n] = 0;
	while(!q.empty())
	{
		tmp = q.top();
		int x = tmp.num;
		q.pop();
		if(isv[x] == 1)
			continue;
		isv[x] = 1;
		for(int i = 0 ; i < (int)mp[x].size() ; i ++ )
		{
			node now = mp[x][i];
			int len = mp[x][i].dis;
			int e = mp[x][i].num; 
			if(rear[e] > rear[x] + len)
			{
				rear[e] = rear[x] + len;
				q.push({e,rear[e]});
			}
		}
	}
}
void solve() 
{
	cin>>n>>m;
	for(int i = 0 ; i <= n ; i ++){
		front[i] = INF;
		rear[i] = INF;
		to[i].clear();
		mp[i].clear();
		in[i] = 0;//入度为0
		isw[i] = 0;//默认为必输态
		pos[i] = 0;//默认不可达
	}
	front[1] = 0 , rear[n] = 0 , isw[n] = 0;//到达n为输
	for(int i = 0 ; i < m ; i ++){
		int u , v , w;
		cin >> u >> v >> w;
		mp[u].pb({v , w});
		mp[v].pb({u , w});
	}
	dij1();
	dij2();
	LL target = front[n];
	for(int i = 1;  i <= n ; i++){
		if(front[i] + rear[i] == target){
			pos[i] = 1;
		}
	}
	//拓扑
	queueq;
	q.push(n);
	memset(isv , 0 , sizeof isv);
	while(!q.empty()){
		int x = q.front();
		q.pop();
		isv[x] = 1;
		for(auto [num , dis] : mp[x]){
			if(pos[num] && front[num] + dis + rear[x] == target){//从 num 走到 x 这条路是可行的
				to[x].pb(num);
				in[num]++;
				if(isv[num] == 0){
					isv[num] = 1;
					q.push(num);
				}
			}
		}
	}
	for(int i = 1 ; i <= n ; i ++){
		if(in[i] == 0){
			q.push(i);
		}
	}
	while(!q.empty()){
		int x = q.front();
		q.pop();
		int f = isw[x];
		for(auto it : to[x]){
			if(!f){//若该点为必输态,则前一个点为必赢
				isw[it] = 1;
			}
			in[it]--;
			if(in[it] == 0){
				q.push(it);
			}
		}
	}
	if(isw[1]){
		cout<<"Little M is the winner.\n";
	}
	else{
		cout<<"Little I is the winner.\n";
	}
}            
int main() 
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cout.precision(10);
    int t=1;
	cin>>t;
    while(t--)
    {
    	solve();
    }
    return 0;
}

D - 划分平面 

        思路:朴素想法:遍历每个村庄,然后遍历另外的村庄作为另一个口岸,然后判断直线两边村庄是否相等,时间为O(N^3),整个过程中发现如果我们在判断两边村庄数量时出现了大量的重复计算,于是想到将点按逆时针顺序来排序(极角排序),用双指针[L,R]来表示在直线一端的村庄,L在逆时针递增的过程中,R必然也是逆时针递增的。因此对于每个村庄而言 L 从1 ~ N - 1递增,再逐步确定R的取值。每个村庄的时间复杂度从O(N^2)变为了O(N) ,整体O(N^2)


#include 

#define endl "\n"
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair PII;
const int N = 2e3 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
typedef long long db;
const double eps = 1e-8;
const double inf = 1e20;
const double pi = acos (-1.0);
const int maxp = 1001; //太大的话拷贝会很慢;
int sgn (db x) {

	return x < -eps ? -1 : x > eps;
}
//square of a db
inline db sqr (db x) { return x * x; }

struct Point {
	db x{}, y{};
	Point () {}
	Point (db _x, db _y) {
		x = _x;
		y = _y;
	}
	void input () {
		//scanf("%lf%lf", &x, &y);
		cin >> x >> y;
	}
	void output () {
		//printf("%.2f %.2f\n", x, y);
		cout << x << " " << y << "\n";
	}
	bool operator== (Point b) const {
		return sgn (x - b.x) == 0 && sgn (y - b.y) == 0;
	}
	bool operator< (Point b) const {
		return sgn (x - b.x) == 0 ? sgn (y - b.y) < 0 : x < b.x;
	}
	Point operator- (const Point& b) const {
		return { x - b.x, y - b.y };
	}
	Point operator+ (const Point& b) const {
		return { x + b.x, y + b.y };
	}
	Point operator* (const db& k) const {
		return { x * k, y * k };
	}
	Point operator/ (const db& k) const {
		return { x / k, y / k };
	}
	//叉积
	db operator^ (const Point& b) const {
		return x * b.y - y * b.x;
	}
	//点积
	db operator* (const Point& b) const {
		return x * b.x + y * b.y;
	}

	//`计算pa  和  pb 的夹角`
	//`就是求这个点看a,b 所成的夹角`
	//`测试 LightOJ1203`
	double rad (Point a, Point b) {
		Point p = *this;
		return fabs (atan2 (fabs ((a - p) ^ (b - p)), (a - p) * (b - p)));
	}

	//`绕着p点逆时针旋转angle`
	Point rotate (Point p, db angle) {
		Point v = (*this) - p;
		db c = cos (angle), s = sin (angle);
		return { p.x + v.x * c - v.y * s, p.y + v.x * s + v.y * c };
	}
	int quad () {
		//double x = a.x, y = a.y;
		return sgn (y) == 1 || (sgn (y) == 0 && sgn (x) >= 0);
	}
};
//`AB X AC`
db cross (Point A, Point B, Point C) {
	return (B - A) ^ (C - A);
}
//`AB*AC`
db dot (Point A, Point B, Point C) {
	return (B - A) * (C - A);
}
Point sta;
int Bool(Point a , Point b){
	a = a - sta , b = b - sta;
	if(a.x * b.x < 0 || a.y * b.y < 0){
		return false;
	}
	else{
		return true;
	}
}
//极角排序
bool cmp (Point a, Point b) {
	a = a - sta, b = b - sta;
	int qa = a.quad (), qb = b.quad ();
	if (qa != qb) return qa < qb;
	else return sgn (a ^ b) > 0;
}

int Relation (Point p, Point e, Point s) {
	int c = sgn ((p - s) ^ (e - s));
	if (c < 0)return 1;
	else if (c > 0)return 2;
	else return 3;
}
Point p[N];

int main () {
	std::ios::sync_with_stdio (false);
	std::cin.tie (nullptr);
	std::cout.tie (nullptr);

	int n;
	cin >> n;
	long long ans = 0;
	for (int i = 0; i < n; i++) {
		p[i].input ();
	}
	for (int _ = 0; _ < n; _++) {
		sta = p[_];
		vector pp;
		for (int i = 0; i < n; i++) {
			if (i == _) continue;
			pp.push_back (p[i]);
		}
		sort (pp.begin (), pp.end (), cmp);
		int l = 0 , r = 0;//起点,终点
		int len = pp.size();
		for(;l < len ; ){
			int st = l + 1;
			int nomean = 2;//选中的两个村庄
			while(st < n && Relation(pp[st] , pp[l] , sta) == 3 && l + len != st && Bool(pp[l] , pp[st % len])){
				st ++;
				nomean++;//另外在直线上的村庄
			}
			r = max(r , st);
			while(Relation(pp[r % len] , pp[l] , sta) == 1 ) r ++;
			int en = r;
			while(Relation(pp[en % len] , pp[l] , sta) == 3 && !Bool(pp[l] , pp[en % len])) {
				en++;
				nomean++;//另外在直线上的村庄
			}
			if( ( r - st ) * 2 == n - nomean){
				ans += st - l;
			}
			l = st;//[L , ST - 1]全一样
		}
	}
	cout<

        

你可能感兴趣的:(算法)