2019ICPC南京区域赛ABCHIJK

视频讲解: www.bilibili.com/video/BV1FY4y1q7x1
题目链接: https://codeforces.com/gym/103466
代码: https://hytidel.lanzoub.com/b030uwgxa

2019ICPC南京题解

A. A Hard Problem

题目大意

求出最小的 k   s . t .   { 1 , 2 , ⋯   , n }    ( 2 ≤ n ≤ 1 e 9 ) k\ s.t.\ \{1,2,\cdots,n\}\ \ (2\leq n\leq 1\mathrm{e}9) k s.t. {1,2,,n}  (2n1e9)的任一有 k k k个元素的子集中,都存在一对 ( u , v )   s . t .   u ∣ v (u,v)\ s.t.\ u\mid v (u,v) s.t. uv.

解I

打表:

n n n 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10
k k k 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 6

猜测通项 k = ⌈ n 2 ⌉ + 1 k=\left\lceil\dfrac{n}{2}\right\rceil+1 k=2n+1.


解II -> 2019ICPC南京-A(推公式)

先考虑连续的情况,即区间 [ 1 , n ] [1,n] [1,n]的任一长度为 k k k的子区间中都存在一对 ( u , v )   s . t .   u ∣ v (u,v)\ s.t.\ u\mid v (u,v) s.t. uv.

忽略 n n n的限制,讨论不同的区间起点对应的 k k k的最小值:

[ 1 , 1 + k − 1 ] [1,1+k-1] [1,1+k1],因 1 1 1 2 ∼ 1 + k − 1 2\sim 1+k-1 21+k1的约数,则 k k k最小值为 2 2 2.

[ 2 , 2 + k − 1 ] [2,2+k-1] [2,2+k1],最小的区间是 [ 2 , 4 ] [2,4] [2,4],即 2 + k − 1 = 4 2+k-1=4 2+k1=4,解得 k = 3 k=3 k=3.

[ 3 , 3 + k − 1 ] [3,3+k-1] [3,3+k1],最小的区间是 [ 3 , 6 ] [3,6] [3,6],即 3 + k − 1 = 6 3+k-1=6 3+k1=6,解得 k = 4 k=4 k=4.

⋮ \vdots

注意找最小的区间的方法:使得区间右端点是区间左端点的两倍.现已限制区间右端点为 n n n,则能选出的最靠右的长度为 k k k的区间左端点为 n − k + 1 n-k+1 nk+1.右端点是左端点的两倍,即 n = 2 ( n − k + 1 ) n=2(n-k+1) n=2(nk+1),整理得 2 k = n + 2 2k=n+2 2k=n+2.

考虑将 2 2 2除过去后 n 2 \dfrac{n}{2} 2n是上取整还是下取整,显然取整的方向不同只影响 n n n为奇数的结果.由样例 n = 3 n=3 n=3时, k = 3 k=3 k=3知: n 2 \dfrac{n}{2} 2n为上取整,故连续的情况的通项是 k = ⌈ n 2 ⌉ + 1 k=\left\lceil\dfrac{n}{2}\right\rceil+1 k=2n+1.

考察任取 k k k个元素的子集的情况.为满足要求,至少应使得该子集的最小元素的两倍也属于该子集,而 [ 1 , n ] [1,n] [1,n]的最大元素为 n n n,则最难满足要求的子集即为以 n n n为右端点的、长度为 k k k的区间,故上述通项是合理的.

考察 k = ⌈ n 2 ⌉ k=\left\lceil\dfrac{n}{2}\right\rceil k=2n能否满足.显然由样例 n = 2 n=2 n=2时, k = 2 k=2 k=2知不满足.故 k k k的最小值为 ⌈ n 2 ⌉ + 1 \left\lceil\dfrac{n}{2}\right\rceil+1 2n+1.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-8;
const int INF = 0x3f3f3f3f;  // 十六进制数,int的无穷大
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	int t; cin >> t;
	while (t--) {
		int n; cin >> n;
		cout << (n + 1) / 2 + 1 << endl;
	}
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解III -> 2019ICPC南京-A(二分)

问题的反面,即求最大的 k   s . t .   { 1 , 2 , ⋯   , n } k\ s.t.\ \{1,2,\cdots,n\} k s.t. {1,2,,n}的任一含 k k k个元素的子集中 ∄ ( u , v )   s . t .   u ∣ v \nexists (u,v)\ s.t.\ u\mid v (u,v) s.t. uv.

考虑连续的情况,设区间左端点为 m m m,则 m m m的两倍不能在区间中,即最大区间为 [ m , 2 m − 1 ] [m,2m-1] [m,2m1],则使得 2 m − 1 ≤ n 2m-1\leq n 2m1n的最大的 m m m再加 1 1 1即为 k k k的最小值,即 ⌊ n + 1 2 ⌋ + 1 \left\lfloor\dfrac{n+1}{2}\right\rfloor+1 2n+1+1.也可二分出使得 2 m − 1 ≤ n 2m-1\leq n 2m1n的最大的 m m m.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-8;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	CaseT{
		int n; cin >> n;
		int ans = -1;

		// 二分出使得2m-1≤n的最大的m
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if ((mid << 1) - 1 <= n) l = mid + 1;
			else r = mid;
		}
		cout << l << endl;
	}
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解IV

1 ∼ n 1\sim n 1n中的每个数都能表示为 a × 2 k    ( k ∈ N ) a\times 2^k\ \ (k\in\mathbb N) a×2k  (kN)的形式,其中 a a a ≤ n \leq n n的奇数.而 1 ∼ n 1\sim n 1n中有 ⌈ n 2 ⌉ \left\lceil\dfrac{n}{2}\right\rceil 2n个奇数,从 1 ∼ n 1\sim n 1n中取 ( ⌈ n 2 ⌉ + 1 ) \left(\left\lceil\dfrac{n}{2}\right\rceil+1\right) (2n+1)个数,将它们表示为上述形式,由抽屉原理知:至少存在两数的 a a a相同,此时一个数是另一个数的约数.

考察 k = ⌈ n 2 ⌉ k=\left\lceil\dfrac{n}{2}\right\rceil k=2n能否满足.显然由样例 n = 2 n=2 n=2时, k = 2 k=2 k=2知不满足.故 k k k的最小值为 ⌈ n 2 ⌉ + 1 \left\lceil\dfrac{n}{2}\right\rceil+1 2n+1.



K. Triangle ( 8   s 8\ s 8 s)

题目大意

给定 △ A B C \triangle ABC ABC的三个顶点 ( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x 3 , y 3 ) (x_1,y_1),(x_2,y_2),(x_3,y_3) (x1,y1),(x2,y2),(x3,y3)和一个点 P ( x p , y p ) P(x_p,y_p) P(xp,yp).若 P P P不在 △ A B C \triangle ABC ABC的边上,则输出 − 1 -1 1,否则求一个点 E ( x e , y e )   s . t .   P E E(x_e,y_e)\ s.t.\ PE E(xe,ye) s.t. PE平分 △ A B C \triangle ABC ABC的面积,输出 E E E的坐标.坐标范围 [ 0 , 1 e 5 ] [0,1\mathrm{e}5] [0,1e5].

解I -> 2019ICPC南京-K(分类讨论二分)

对有解的情况,枚举 P P P位于哪条边、更靠近边的哪个端点,在对应的边上二分答案.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int cmp(double x, double y) {  // 比较浮点数
	if (fabs(x - y) < eps) return 0;  // 相等
	return x < y ? -1 : 1;
}

int sgn(double x) {  // 符号函数
	if (fabs(x) < eps) return 0;  // 0
	return x < 0 ? -1 : 1;
}

struct Point {
	double x, y;

	Point() {}
	Point(double _x, double _y) :x(_x), y(_y) {}

	void read() { scanf("%lf%lf", &x, &y); }

	Point operator+(const Point& b)const { return Point(x + b.x, y + b.y); }
	Point operator-(const Point& b)const { return Point(x - b.x, y - b.y); }
	double operator*(const Point& b)const { return x * b.x + y * b.y; }  // 点积
	double operator^(const Point& b)const { return x * b.y - y * b.x; }  // 叉积
	Point operator*(const double& k)const { return Point(x * k, y * k); }
	bool operator==(const Point& b)const { return !cmp(x, b.x) && !cmp(y, b.y); }

	double get_dis(const Point& b) { return sqrt((x - b.x) * (x - b.x) + (y - b.y) * (y - b.y)); }  // 求两点间的距离
	// double get_dis(const Point& b) { return hypot(x - b.x, y - b.y); }  // 求两点间的距离的平方
	double get_dis2(const Point& b) { return (x - b.x) * (x - b.x) + (y - b.y) * (y - b.y); }  // 求两点间的距离的平方
};  // 点、起点在原点的向量

Point get_mid(Point A, Point B) { return (A + B) * 0.5; }  // 求线段AB中点

double get_area(Point A, Point B, Point C) { return fabs((B - A) ^ (C - A) * 0.5); }

struct Line {
	Point ST, ED;

	Line() {}
	Line(Point _A, Point _B) :ST(_A), ED(_B) {}
	Line(double a, double b, double c, double d) :ST(a, b), ED(c, d) {}

	bool is_point_on_segment(Point P) {  // P是否在线段AB上
		return sgn((P - ST) ^ (ED - ST)) == 0 && sgn((P - ST) * (P - ED)) <= 0;
	}
};

int main() {
	// IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	CaseT{
		Point A,B,C,P;
		A.read(), B.read(), C.read(), P.read();
		Line AB(A, B), AC(A, C), BC(B, C);

		bool flag1 = AB.is_point_on_segment(P), flag2 = BC.is_point_on_segment(P), flag3 = AC.is_point_on_segment(P);
		if (!flag1 && !flag2 && !flag3) {  // 无解
			cout << -1 << endl;
			continue;
		}

		double area = get_area(A, B, C) * 0.5;  // ABC面积的一半

		if (flag1) {  // P在AB上
			if (P.get_dis2(A) < P.get_dis2(B)) {
				Point L = B, R = C, MID;
				int t = 50;  // 二分次数
				while (t--) {
					MID = get_mid(L, R);
					double s = get_area(P, MID, B);
					int flag = sgn(s - area);
					if (flag == 0) break;
					else if (flag == 1) R = MID;
					else L = MID;
				}
				printf("%.10lf %.10lf\n", MID.x, MID.y);
			}
			else {
				Point L = A, R = C, MID;
				int t = 50;  // 二分次数
				while (t--) {
					MID = get_mid(L, R);
					double s = get_area(P, MID, A);
					int flag = sgn(s - area);
					if (flag == 0) break;
					else if (flag == 1) R = MID;
					else L = MID;
				}
				printf("%.10lf %.10lf\n", MID.x, MID.y);
			}
		}
		else if (flag2) {  // P在BC上
			if (P.get_dis2(B) < P.get_dis2(C)) {
				Point L = C, R = A, MID;
				int t = 50;  // 二分次数
				while (t--) {
					MID = get_mid(L, R);
					double s = get_area(P, MID, C);
					int flag = sgn(s - area);
					if (flag == 0) break;
					else if (flag == 1) R = MID;
					else L = MID;
				}
				printf("%.10lf %.10lf\n", MID.x, MID.y);
			}
			else {
				Point L = B, R = A, MID;
				int t = 50;  // 二分次数
				while (t--) {
					MID = get_mid(L, R);
					double s = get_area(P, MID, B);
					int flag = sgn(s - area);
					if (flag == 0) break;
					else if (flag == 1) R = MID;
					else L = MID;
				}
				printf("%.10lf %.10lf\n", MID.x, MID.y);
			}
		}
		else {  // P在AC上
			if (P.get_dis2(A) < P.get_dis2(C)) {
				Point L = C, R = B, MID;
				int t = 50;  // 二分次数
				while (t--) {
					MID = get_mid(L, R);
					double s = get_area(P, MID, C);
					int flag = sgn(s - area);
					if (flag == 0) break;
					else if (flag == 1) R = MID;
					else L = MID;
				}
				printf("%.10lf %.10lf\n", MID.x, MID.y);
			}
			else {
				Point L = A, R = B, MID;
				int t = 50;  // 二分次数
				while (t--) {
					MID = get_mid(L, R);
					double s = get_area(P, MID, A);
					int flag = sgn(s - area);
					if (flag == 0) break;
					else if (flag == 1) R = MID;
					else L = MID;
				}
				printf("%.10lf %.10lf\n", MID.x, MID.y);
			}
		}
	}
		// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解II -> 2019ICPC南京-K(统一情况二分)

解I的思路,通过交换点,统一为 P ∈ A B ‾ P\in\overline{AB} PAB且更接近 A A A的情况.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int cmp(double x, double y) {  // 比较浮点数
	if (fabs(x - y) < eps) return 0;  // 相等
	return x < y ? -1 : 1;
}

int sgn(double x) {  // 符号函数
	if (fabs(x) < eps) return 0;  // 0
	return x < 0 ? -1 : 1;
}

struct Point {
	double x, y;

	Point() {}
	Point(double _x, double _y) :x(_x), y(_y) {}

	void read() { scanf("%lf%lf", &x, &y); }

	Point operator+(const Point& b)const { return Point(x + b.x, y + b.y); }
	Point operator-(const Point& b)const { return Point(x - b.x, y - b.y); }
	double operator*(const Point& b)const { return x * b.x + y * b.y; }  // 点积
	double operator^(const Point& b)const { return x * b.y - y * b.x; }  // 叉积
	Point operator*(const double& k)const { return Point(x * k, y * k); }
	bool operator==(const Point& b)const { return !cmp(x, b.x) && !cmp(y, b.y); }

	double get_dis(const Point& b) { return sqrt((x - b.x) * (x - b.x) + (y - b.y) * (y - b.y)); }  // 求两点间的距离
	// double get_dis(const Point& b) { return hypot(x - b.x, y - b.y); }  // 求两点间的距离的平方
	double get_dis2(const Point& b) { return (x - b.x) * (x - b.x) + (y - b.y) * (y - b.y); }  // 求两点间的距离的平方
};  // 点、起点在原点的向量

Point get_mid(Point A, Point B) { return (A + B) * 0.5; }  // 求线段AB中点

double get_area(Point A, Point B, Point C) { return fabs((B - A) ^ (C - A) * 0.5); }

struct Line {
	Point ST, ED;

	Line() {}
	Line(Point _A, Point _B) :ST(_A), ED(_B) {}
	Line(double a, double b, double c, double d) :ST(a, b), ED(c, d) {}

	bool is_point_on_segment(Point P) {  // P是否在线段AB上
		return sgn((P - ST) ^ (ED - ST)) == 0 && sgn((P - ST) * (P - ED)) <= 0;
	}
};

int main() {
	// IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	CaseT{
		Point A,B,C,P;
		A.read(), B.read(), C.read(), P.read();
		Line AB(A, B), AC(A, C), BC(B, C);

		bool flag1 = AB.is_point_on_segment(P), flag2 = BC.is_point_on_segment(P), flag3 = AC.is_point_on_segment(P);
		if (!flag1 && !flag2 && !flag3) {  // 无解
			cout << -1 << endl;
			continue;
		}

		double area = get_area(A, B, C) * 0.5;  // ABC面积的一半

		// 统一成P在线段AB上的情况
		if (!flag1) {
			if (flag2) swap(A, C);
			else swap(B, C);
		}
		
		// 统一成P靠近A的情况
		if (P.get_dis2(A) > P.get_dis2(B)) swap(A, B);

		Point L = B, R = C, MID;
		int t = 50;  // 二分次数
		while (t--) {
			MID = get_mid(L, R);
			double s = get_area(P, MID, B);
			int flag = sgn(s - area);
			if (flag == 0) break;
			else if (flag == 1) R = MID;
			else L = MID;
		}
		printf("%.10lf %.10lf\n", MID.x, MID.y);
	}
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解III -> 2019ICPC南京-K(平行线求交点)

解I的思路,通统一为 P ∈ A B ‾ P\in\overline{AB} PAB且更接近 A A A的情况.

在这里插入图片描述

如上图,取 A B AB AB中点 D D D,作 D E / / P C DE//PC DE//PC B C BC BC E E E.因 S △ D E P = S △ D E C S_{\triangle DEP}=S_{\triangle DEC} SDEP=SDEC,

S △ B E P = S △ B E D + S △ D E P = S △ B E D + S △ D E C = S △ B C D = S △ A B C 2 S_{\triangle BEP}=S_{\triangle BED}+S_{\triangle DEP}=S_{\triangle BED}+S_{\triangle DEC}=S_{\triangle BCD}=\dfrac{S_{\triangle ABC}}{2} SBEP=SBED+SDEP=SBED+SDEC=SBCD=2SABC,故点 E E E为所求.

直线 D E DE DE用点 E E E P C → \overrightarrow{PC} PC 表示,直线 B C BC BC用点 B B B B C → \overrightarrow{BC} BC 表示,求交点.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int cmp(double x, double y) {  // 比较浮点数
	if (fabs(x - y) < eps) return 0;  // 相等
	return x < y ? -1 : 1;
}

int sgn(double x) {  // 符号函数
	if (fabs(x) < eps) return 0;  // 0
	return x < 0 ? -1 : 1;
}

struct Point {
	double x, y;

	Point() {}
	Point(double _x, double _y) :x(_x), y(_y) {}

	void read() { scanf("%lf%lf", &x, &y); }

	Point operator+(const Point& b)const { return Point(x + b.x, y + b.y); }
	Point operator-(const Point& b)const { return Point(x - b.x, y - b.y); }
	double operator*(const Point& b)const { return x * b.x + y * b.y; }  // 点积
	double operator^(const Point& b)const { return x * b.y - y * b.x; }  // 叉积
	Point operator*(const double& k)const { return Point(x * k, y * k); }
	bool operator==(const Point& b)const { return !cmp(x, b.x) && !cmp(y, b.y); }

	double get_dis(const Point& b) { return sqrt((x - b.x) * (x - b.x) + (y - b.y) * (y - b.y)); }  // 求两点间的距离
	// double get_dis(const Point& b) { return hypot(x - b.x, y - b.y); }  // 求两点间的距离的平方
	double get_dis2(const Point& b) { return (x - b.x) * (x - b.x) + (y - b.y) * (y - b.y); }  // 求两点间的距离的平方
};  // 点、起点在原点的向量

Point get_mid(Point A, Point B) { return (A + B) * 0.5; }  // 求线段AB中点

double get_area(Point A, Point B, Point C) { return fabs((B - A) ^ (C - A) * 0.5); }

struct Line {
	Point ST, ED;

	Line() {}
	Line(Point _A, Point _B) :ST(_A), ED(_B) {}
	Line(double a, double b, double c, double d) :ST(a, b), ED(c, d) {}

	bool is_point_on_segment(Point P) {  // P是否在线段AB上
		return sgn((P - ST) ^ (ED - ST)) == 0 && sgn((P - ST) * (P - ED)) <= 0;
	}
};

Point get_intersection(Point& p, Point& v, Point& q, Point& w) {  // 求直线(点p+向量v)与(点q+向量w)的交点
	if (sgn(v ^ w) == 0) return Point{ INF,INF };  // 两直线平行或重合

	Point u = p - q;
	double k = (w ^ u) / (v ^ w);  // 比例系数
	return p + v * k;
}

int main() {
	// IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	CaseT{
		Point A,B,C,P;
		A.read(), B.read(), C.read(), P.read();
		Line AB(A, B), AC(A, C), BC(B, C);

		bool flag1 = AB.is_point_on_segment(P), flag2 = BC.is_point_on_segment(P), flag3 = AC.is_point_on_segment(P);
		if (!flag1 && !flag2 && !flag3) {  // 无解
			cout << -1 << endl;
			continue;
		}

		double area = get_area(A, B, C) * 0.5;  // ABC面积的一半

		// 统一成P在线段AB上的情况
		if (!flag1) {
			if (flag2) swap(A, C);
			else swap(B, C);
		}
		
		// 统一成P靠近A的情况
		if (P.get_dis2(A) > P.get_dis2(B)) swap(A, B);

		Point D = get_mid(A, B);

		Point vPC = P - C, vBC = B - C;  // 向量PC、向量BC
		Point ans = get_intersection(D, vPC, B, vBC);
		printf("%.10lf %.10lf\n", ans.x, ans.y);
	}
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解IV -> 2019ICPC南京-K(面积导比)

解I的思路,通统一为 P ∈ A B ‾ P\in\overline{AB} PAB且更接近 A A A的情况.

在这里插入图片描述

如上图, 2 S △ B E P = S △ A B C ⇔ 2 B E ⋅ B P ⋅ sin ⁡ B = B C ⋅ B A ⋅ sin ⁡ B 2S_{\triangle BEP}=S_{\triangle ABC}\Leftrightarrow 2BE\cdot BP\cdot \sin B=BC\cdot BA\cdot \sin B 2SBEP=SABC2BEBPsinB=BCBAsinB,则 B E = B C ⋅ B A 2 B P BE=\dfrac{BC\cdot BA}{2 BP} BE=2BPBCBA.

求出 B E B C \dfrac{BE}{BC} BCBE,再利用 B C → \overrightarrow{BC} BC 求出 E E E的坐标.

因精度而WA的写法:

double k = B.get_dis(C) * B.get_dis(A) / B.get_dis(P) * 0.5;  // BE长度
Point vBC = B - C;  // 向量BC
Point E = vBC * k / B.get_dis(C);
Point vBC = B - C;  // 向量BC
Point E = vBC * B.get_dis(A) * 0.5 / B.get_dis(P);

AC写法:

double k = P.get_dis(B) / B.get_dis(A);
k = 0.5 / k;
Point vCB = C - B;
Point E = vCB * k + B;
代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int cmp(double x, double y) {  // 比较浮点数
	if (fabs(x - y) < eps) return 0;  // 相等
	return x < y ? -1 : 1;
}

int sgn(double x) {  // 符号函数
	if (fabs(x) < eps) return 0;  // 0
	return x < 0 ? -1 : 1;
}

struct Point {
	double x, y;

	Point() {}
	Point(double _x, double _y) :x(_x), y(_y) {}

	void read() { scanf("%lf%lf", &x, &y); }

	Point operator+(const Point& b)const { return Point(x + b.x, y + b.y); }
	Point operator-(const Point& b)const { return Point(x - b.x, y - b.y); }
	double operator*(const Point& b)const { return x * b.x + y * b.y; }  // 点积
	double operator^(const Point& b)const { return x * b.y - y * b.x; }  // 叉积
	Point operator*(const double& k)const { return Point(x * k, y * k); }
	Point operator/(const double& k)const { return Point(x / k, y / k); }
	bool operator==(const Point& b)const { return !cmp(x, b.x) && !cmp(y, b.y); }

	double get_dis(const Point& b) { return sqrt((x - b.x) * (x - b.x) + (y - b.y) * (y - b.y)); }  // 求两点间的距离
	// double get_dis(const Point& b) { return hypot(x - b.x, y - b.y); }  // 求两点间的距离的平方
	double get_dis2(const Point& b) { return (x - b.x) * (x - b.x) + (y - b.y) * (y - b.y); }  // 求两点间的距离的平方
};  // 点、起点在原点的向量

Point get_mid(Point A, Point B) { return (A + B) * 0.5; }  // 求线段AB中点

double get_area(Point A, Point B, Point C) { return fabs((B - A) ^ (C - A) * 0.5); }

struct Line {
	Point ST, ED;

	Line() {}
	Line(Point _A, Point _B) :ST(_A), ED(_B) {}
	Line(double a, double b, double c, double d) :ST(a, b), ED(c, d) {}

	bool is_point_on_segment(Point P) {  // P是否在线段AB上
		return sgn((P - ST) ^ (ED - ST)) == 0 && sgn((P - ST) * (P - ED)) <= 0;
	}
};

int main() {
	// IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	CaseT{
		Point A,B,C,P;
		A.read(), B.read(), C.read(), P.read();
		Line AB(A, B), AC(A, C), BC(B, C);

		bool flag1 = AB.is_point_on_segment(P), flag2 = BC.is_point_on_segment(P), flag3 = AC.is_point_on_segment(P);
		if (!flag1 && !flag2 && !flag3) {  // 无解
			cout << -1 << endl;
			continue;
		}

		double area = get_area(A, B, C) * 0.5;  // ABC面积的一半

		// 统一成P在线段AB上的情况
		if (!flag1) {
			if (flag2) swap(A, C);
			else swap(B, C);
		}
		
		// 统一成P靠近A的情况
		if (P.get_dis2(A) > P.get_dis2(B)) swap(A, B);

		double k = P.get_dis(B) / B.get_dis(A);
		k = 0.5 / k;
		Point vCB = C - B;
		Point E = vCB * k + B;
		printf("%.12lf %.12lf\n", E.x, E.y);
	}
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}


H. Prince and Princess

题目大意

n n n个房间,每个房间有一个人,他们知道任一个人在哪个房间,其中公主在某一房间.你可以问任一房间的人下列三个问题之一:①你是谁;②某个房间里的人是谁;③公主在哪个房间.

n n n个人可分为三类,说真话、说假话、可能说真话也可能说假话,分别有 a , b , c    ( 0 < a , b , c < 2 e 5 ) a,b,c\ \ (0<a,b,c<2\mathrm{e}5) a,b,c  (0<a,b,c<2e5)人.至少问几次才能确定公主在哪个房间.若无法确定,输出"NO";若可以确定,输出"YES",下一行输出最小询问次数.

考虑最坏的情况,最初问到的 ( b + c ) (b+c) (b+c)个人都说假话,且都回答同一错误答案 A A A.再问 ( b + c ) (b+c) (b+c)个人,他们都说真话,都回答同一个答案 B B B,此时 A A A B B B选择的人数相同.此时再多问 1 1 1个人,则 A A A B B B至少有一个答案的人数比另一个多,较多者即公主所在位置,故至少要问 b + c + b + c + 1 = 2 ( b + c ) + 1 b+c+b+c+1=2(b+c)+1 b+c+b+c+1=2(b+c)+1次,则总人数 n = a + b + c ≥ 2 ( b + c ) + 1 n=a+b+c\geq 2(b+c)+1 n=a+b+c2(b+c)+1,解得 a ≥ b + c + 1 a\geq b+c+1 ab+c+1,即 a > b + c a>b+c a>b+c.

注意特判 a = 1 , b = c = 0 a=1,b=c=0 a=1,b=c=0,即只有一个人——公主的情况,此时无需询问.

代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	int a, b, c; cin >> a >> b >> c;
	if (a == 1 && b == 0 && c == 0) return cout << "YES" << endl << 0, 0;  // 特判只有一个人,即公主的情况
	else {
		if (a > b + c) cout << "YES" << endl << (b + c << 1 | 1);
		else cout << "NO";
	}
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}


I. Space Station ( 3   s 3\ \mathrm{s} 3 s)

题目大意

( n + 1 )    ( 1 ≤ n ≤ 1 e 5 ) (n+1)\ \ (1\leq n\leq 1\mathrm{e}5) (n+1)  (1n1e5)个站,编号 0 ∼ n 0\sim n 0n, i i i号站有能量 a i    ( 0 ≤ a i ≤ 50 ) a_i\ \ (0\leq a_i\leq 50) ai  (0ai50),每访问一个站都可获得该站的所有能量,每次只能访问能量不超过当前自身的能量的站,每个站只能访问一次.初始时在 0 0 0号站,获得其能量 a 0 a_0 a0.求访问完所有站的方案数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.

注意到 0 ≤ a i ≤ 50 0\leq a_i\leq 50 0ai50,则当前能量 ≥ 50 \geq 50 50后剩下的站可以任意顺序访问.注意到 a i a_i ai可能为 0 0 0,而能量为 0 0 0的站可在任意位置访问,设共有 z z z个能量为 0 0 0的站.先求出访问 ( n − z ) (n-z) (nz)个能量非零的站的方案数 x x x,从 n n n个位置选出 ( n − z ) (n-z) (nz)个位置放能量非零的站,剩下的 z z z个位置放 z z z个能量为 0 0 0的站的任一排列(注意能量为 0 0 0的站编号不同计为不同方案),则 a n s = x ⋅ C n n − z ⋅ z ! = x ⋅ n ! ( n − z ) ! ( n − n + z ) ! ⋅ z ! = x ⋅ n ! ( n − z ) ! ans=x\cdot C_{n}^{n-z}\cdot z!=x\cdot\dfrac{n!}{(n-z)!(n-n+z)!}\cdot z!=x\cdot\dfrac{n!}{(n-z)!} ans=xCnnzz!=x(nz)!(nn+z)!n!z!=x(nz)!n!,再对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模即可.

预处理出 1 e 5 1\mathrm{e}5 1e5内的数的阶乘模 1 e 9 + 7 1\mathrm{e}9+7 1e9+7和阶乘模 1 e 9 + 7 1\mathrm{e}9+7 1e9+7下的逆元,两者都可用递推求得,前者显然,时间复杂度 O ( n ) O(n) O(n);考虑后者:不妨设 n ! n! n! ( n + 1 ) ! (n+1)! (n+1)!的逆元分别为 x , y x,y x,y,则 n ! ⋅ x ≡ ( n + 1 ) ! ⋅ y ≡ 1    ( m o d   M O D ) n!\cdot x\equiv (n+1)!\cdot y\equiv 1\ \ (\mathrm{mod}\ MOD) n!x(n+1)!y1  (mod MOD).注意到 ( n + 1 ) ! ⋅ y = n ! ( n + 1 ) ⋅ y = n ! [ y ( n + 1 ) ] = n ! ⋅ y ′ ≡ 1    ( m o d   M O D ) (n+1)!\cdot y=n!(n+1)\cdot y=n![y(n+1)]=n!\cdot y'\equiv 1\ \ (\mathrm{mod}\ MOD) (n+1)!y=n!(n+1)y=n![y(n+1)]=n!y1  (mod MOD),则 x ≡ y ′ = y ( n + 1 )    ( m o d   M O D ) x\equiv y'=y(n+1)\ \ (\mathrm{mod}\ MOD) xy=y(n+1)  (mod MOD).因 M O D = 1 e 9 + 7 MOD=1\mathrm{e}9+7 MOD=1e9+7是素数,可先用快速幂求出 1 e 5 ! 1\mathrm{e}5! 1e5! M O D MOD MOD下的逆元,再递推求得 1 ∼ 1 e 5 1\sim 1\mathrm{e}5 11e5的数的阶乘的逆元,时间复杂度 O ( n + log ⁡ 1 e 5 ) ≈ O ( n ) O(n+\log 1\mathrm{e}5)\approx O(n) O(n+log1e5)O(n).

考虑如何求访问 ( n − z ) (n-z) (nz)个能量非零的站的方案数.初始能量最低为 1 1 1,最多要访问 49 49 49个能量为 1 1 1的站后能量 ≥ 50 \geq 50 50,剩下的站可以任意顺序访问,则搜索的范围是将 50 50 50拆成若干个正整数之和的方案数(其中有一些是不合法的方案),用完全背包易求得至少拆成两个正整数之和的分拆数为 204225 204225 204225,搜索范围不大,可用记搜.

每次只能访问能量不超过当前自身能量的站,则搜索顺序是从小到大枚举站的能量,若有相同能量 p o w e r power power的站则先一起考虑后,再考虑能量为 ( p o w e r + 1 ) (power+1) (power+1)的站,则可开一个 c n t [ ] cnt[] cnt[]来记录能量为 i i i的站的个数 c n t [ i ] cnt[i] cnt[i].

考虑记搜如何记录状态.易想到用二进制数或bool数组记录状态,但这记录的是每个站是否已被访问,与上述搜索顺序不符,考虑用哈希记录状态.每个能量非零的站的能量都是 [ 1 , 50 ] [1,50] [1,50]内的整数,则可将相同能量的站的个数对应到一个 P    ( P > 51 ) P \ \ (P>51) P  (P>51)进制数的数位,用这个 1 e 5 1\mathrm{e}5 1e5位的 P P P进制数模 M O D MOD MOD来记录状态.为降低哈希冲突,可类似于字符串哈希,取经验值 P = 131 P=131 P=131 P = 13331 P=13331 P=13331.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 5e5 + 5, MOD = 1e9 + 7;
int fac[MAXN], invfac[MAXN];  // 阶乘、阶乘模MOD的逆元

int qpow(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % MOD;
		a = (ll)a * a % MOD;
		b >>= 1;
	}
	return res;
}

void init() {  // 预处理出阶乘、阶乘的逆元
	fac[0] = invfac[0] = 1;  // 0!=1
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;  // 阶乘

	invfac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2);  // Fermat小定理求逆元
	for (int i = MAXN - 2; i; i--) invfac[i] = (ll)invfac[i + 1] * (i + 1) % MOD;
}

int cnt[55];  // cnt[i]表示能量为i的站的个数
unordered_map<ull, int> state;  // state[i]=j表示状态为i的方案数模MOD为j
const int P = 131;  // P进制数

int dfs(int cur, int rest) {  // 当前能量、还剩几个能量非零的站未访问
	if (rest == 0) return 1;  // 搜完了
	if (cur >= 50) return fac[rest];  // 当前能量≥50,剩下的站可以任意顺序访问,即全排列

	ull ha = 0;  // 哈希值
	for (int i = 50; i > 0; i--) ha = ha * P + cnt[i];  // 枚举所有能量的站的方案数,记录状态

	if (state.count(ha)) return state[ha];  // 搜过了

	int res = 0;  // 方案数
	for (int i = 1; i <= cur; i++) {  // 枚举能量不超过当前能量的站
		if (!cnt[i]) continue;  // 没有该能量的站未访问

		int tmp = cnt[i];  // 备份
		cnt[i]--;  // 已访问一个该能量的站
		res = (res + (ll)tmp * dfs(cur + i, rest - 1) % MOD) % MOD;  // 任选一个相同能量的站,然后搜下一个
		cnt[i]++;  // 恢复
	}
	state[ha] = res;  // 记录该状态的答案
	return res;
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	init();

	int n, now; cin >> n >> now;  // now为当前能量
	int zero = 0;  // 能量为0的站的个数
	for (int i = 1; i <= n; i++) {
		int power; cin >> power;
		if (!power) zero++;
		else cnt[power]++;  // 更新对应能量的站的个数
	}

	int ans = dfs(now, n - zero);  // 当前能量为now,还有(n-zero)个能量非零的站没有访问
	ans = (ll)ans * fac[n] % MOD * invfac[n - zero] % MOD;
	cout << ans;

	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}


J. Spy

题目大意

给定4个长度为 n    ( 1 ≤ n ≤ 400 ) n\ \ (1\leq n\leq 400) n  (1n400)的数列 a [ ] , p [ ] , b [ ] , c [ ] a[],p[],b[],c[] a[],p[],b[],c[],其中 a [ i ] a[i] a[i]表示对手的第 i i i支队的能量, p [ i ] p[i] p[i]表示击败对手的第 i i i支队能获得的荣誉, b [ ] b[] b[] c [ ] c[] c[]表示我方两种队员的能量.现需将 b [ ] b[] b[] c [ ] c[] c[]以某种方式两两配对组成 n n n支队伍 d [ ] d[] d[],再将 d [ ] d[] d[] a [ ] a[] a[]随机地战斗,每一支队伍都只能战斗一次,共 n ! n! n!种情况.若一次战斗中 d [ i ] > a [ i ] d[i]>a[i] d[i]>a[i],则表示我方的队伍战胜对手的队伍,并获得 p [ i ] p[i] p[i]的荣誉.求可获得的荣誉的最大期望乘 n n n的值,数据保证最大期望乘 n n n后是整数, − 1 e 18 ≤ a i , b i , c i ≤ 1 e 18 , 1 ≤ p i ≤ 1 e 4 -1\mathrm{e}18\leq a_i,b_i,c_i\leq 1\mathrm{e}18,1\leq p_i\leq 1\mathrm{e}4 1e18ai,bi,ci1e18,1pi1e4.

很多人(像我一样)认为最后战斗有 n ! ⋅ n ! n!\cdot n! n!n!种情况,即 b [ ] b[] b[] c [ ] c[] c[]先随机配对,产生 n ! n! n! d [ ] d[] d[],再与 a [ ] a[] a[]随机对战.但事实上题干中的 n ! n! n!是对给定的 d [ ] d[] d[]而言的,即要找到一组 d [ ]   s . t .   d [ ] d[]\ s.t.\ d[] d[] s.t. d[] a [ ] a[] a[]随机对战可获得的荣誉的期望乘 n n n最大.

样例解释

样例中 n = 4 n=4 n=4,则 d [ ] d[] d[] a [ ] a[] a[]随机对战有 n ! = 24 n!=24 n!=24种情况, b [ ] b[] b[] c [ ] c[] c[]随机配对有 n ! = 24 n!=24 n!=24种情况,则共 24 × 24 = 576 24\times 24=576 24×24=576种情况,暴搜出所有情况也不方便看出规律.

考虑如何简化该过程,不妨先求出可获得的荣誉的期望乘 n n n的表达式.固定一组 d [ ] d[] d[],让其与 a [ ] a[] a[]随机对战,共 n ! n! n!种情况.对 ∀ d [ j ] \forall d[j] d[j],若 d [ j ] > a [ i ] d[j]>a[i] d[j]>a[i],则可获得 p [ i ] p[i] p[i]的荣誉,则它对答案的贡献是 p [ i ] n ! \dfrac{p[i]}{n!} n!p[i],则所有 d [ ] d[] d[]对答案的贡献为 ∑ i = 1 n p [ i ] n !    ( i f   ∃ d [ j ] > a [ i ] ) \dfrac{\displaystyle \sum_{i=1}^n p[i]}{n!}\ \ (if\ \exists d[j]>a[i]) n!i=1np[i]  (if d[j]>a[i]).

考察任一给定的 d [ j ] d[j] d[j]的数量,不妨设 d [ j ] = b [ x ] + c [ y ] d[j]=b[x]+c[y] d[j]=b[x]+c[y].因 b [ ] b[] b[] c [ ] c[] c[]随机配对有 n ! n! n!种情况,最终产生了 n n n个数 d [ j ]    ( j = 1 , 2 , ⋯   , n ) d[j]\ \ (j=1,2,\cdots,n) d[j]  (j=1,2,,n),则平均每个 d [ j ] d[j] d[j] n ! n \dfrac{n!}{n} nn!个.

最终的期望要乘 n n n,结果为 ∑ i = 1 n p [ i ] n ! ⋅ n ! n ⋅ n = ∑ i = 1 n p [ i ]    ( i f   ∃ d [ j ] > a [ i ] ) \dfrac{\displaystyle \sum_{i=1}^n p[i]}{n!}\cdot\dfrac{n!}{n}\cdot n=\displaystyle \sum_{i=1}^n p[i]\ \ (if\ \exists d[j]>a[i]) n!i=1np[i]nn!n=i=1np[i]  (if d[j]>a[i]),即每个 d [ j ] d[j] d[j]能获得的荣誉之和,接下来即可不用考虑概率.

Input
4
1 2 3 4
1 1 1 1
0 0 1 1
0 1 1 2
Output
3

对上述样例, b [ ] = { 0 , 0 , 1 , 1 } , c [ ] = { 0 , 1 , 1 , 2 } b[]=\{0,0,1,1\},c[]=\{0,1,1,2\} b[]={0,0,1,1},c[]={0,1,1,2},可组成的 d [ j ] ∈ { 0 , 1 , 2 , 3 } d[j]\in\{0,1,2,3\} d[j]{0,1,2,3},其中 d [ j ] = 0 d[j]=0 d[j]=0 d [ j ] = 1 d[j]=1 d[j]=1对答案无贡献; d [ j ] = 2 d[j]=2 d[j]=2可以打败 a [ 1 ] = 1 a[1]=1 a[1]=1,获得 p [ 1 ] = 1 p[1]=1 p[1]=1的荣誉; d [ j ] = 3 d[j]=3 d[j]=3可以打败 a [ 1 ] = 1 a[1]=1 a[1]=1 a [ 2 ] = 1 a[2]=1 a[2]=1,分别获得 p [ 1 ] = 1 p[1]=1 p[1]=1 p [ 2 ] = 1 p[2]=1 p[2]=1的荣誉,故能获得的最大总荣誉为 3 3 3.

解I

对能获得最大期望荣誉的 d [ ] d[] d[],考虑 b [ ] b[] b[] c [ ] c[] c[]如何配对.因 d [ j ] = b [ x ] + c [ y ] d[j]=b[x]+c[y] d[j]=b[x]+c[y],可想到二分图的最大权匹配,下面考虑怎么定义边权.对一组确定的 ( x , y ) (x,y) (x,y),若 b [ x ] + c [ y ] > a [ k ]    ( k = 1 , 2 , ⋯   , n ) b[x]+c[y]>a[k]\ \ (k=1,2,\cdots,n) b[x]+c[y]>a[k]  (k=1,2,,n),则定义 b [ x ] b[x] b[x] c [ y ] c[y] c[y]间的边权为 ∑ p [ k ]    ( i f   d [ i ] = b [ x ] + c [ y ] > a [ k ] ) \displaystyle\sum p[k]\ \ (if\ d[i]=b[x]+c[y]>a[k]) p[k]  (if d[i]=b[x]+c[y]>a[k]).

n n n最大 400 400 400,只能用 O ( n 3 ) O(n^3) O(n3)的BFS版本或DFS版本的KM算法过,不能用 O ( n 4 ) O(n^4) O(n4)的DFS版本或费用流版本的KM算法.

代码I -> 2019ICPC南京-J(BFS版本的KM算法-I)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 405;
int n;  // 组数
ll a[MAXN], b[MAXN], c[MAXN];  // 对手能量、我方两种队员的能量
int p[MAXN];  // 荣誉

template<typename T> struct Hungarian {
	int n;  // 单侧节点数
	vi matchx;  // 左侧匹配
	vi matchy;  // 右侧匹配
	vi pre;  // 前驱
	vector<bool> visx;  // 记录左侧是否已访问
	vector<bool> visy;  // 记录右侧是否已访问
	vector<T> lx;  // lx[i]表示左边第i个点的顶标
	vector<T> ly;  // ly[i]表示右边第i个点的顶标
	vector<vector<T>> graph;  // 存图
	vector<T> slack;
	T inf;  // 该类型下的最大值
	T res;  // 最大权和
	qi que;  // BFS的队列
	int org_n, org_m;  // 初始时两边的节点数

	Hungarian(int _n, int _m) {  // 初始化
		org_n = _n, org_m = _m;  // 记录初始时两边的节点数
		n = max(_n, _m);  // 补成两边节点数相同
		inf = numeric_limits<T>::max();  // 该类型的最大值
		res = 0;
		graph = vector<vector<T>>(n, vector<T>(n));  // 邻接矩阵存图 
		matchx = vi(n, -1), matchy = vi(n, -1);
		pre = vi(n);
		visx = vector<bool>(n), visy = vector<bool>(n);
		lx = vector<T>(n, -inf), ly = vector<T>(n);
		slack = vector<T>(n);
	}

	void addEdge(int u, int v, int w) {  // 建u<->v的权值为w的边
		graph[u][v] = max(w, 0);  // 负权不如不匹配,置为0不影响
	}

	bool check(int v) {  // 检查节点v是否未匹配
		visy[v] = true;
		if (~matchy[v]) {  // 若已匹配
			que.push(matchy[v]);  // 匹配的对象入队
			visx[matchy[v]] = true;  // 标记已访问
			return false;  // 节点v已匹配
		}

		while (~v) {
			matchy[v] = pre[v];
			swap(v, matchx[pre[v]]);
		}
		return true;  // 节点v未匹配
	}

	void bfs(int i) {
		while (que.size()) que.pop();  // 清空队列

		que.push(i);
		visx[i] = true;
		while (1) {
			while (que.size()) {
				int u = que.front(); que.pop();
				for (int v = 0; v < n; v++) {
					if (!visy[v]) {
						T delta = lx[u] + ly[v] - graph[u][v];  // S-T'的最小边权
						if (slack[v] >= delta) {
							pre[v] = u;
							if (delta) slack[v] = delta;
							else if (check(v)) return;
						}
					}
				}
			}

			// 无增广路,修改顶标
			T a = inf;
			for (int j = 0; j < n; j++)
				if (!visy[j]) a = min(a, slack[j]);

			for (int j = 0; j < n; j++) {
				if (visx[j]) lx[j] -= a;  // S
				if (visy[j]) ly[j] += a;  // T
				else slack[j] -= a;  // T'
			}

			for (int j = 0; j < n; j++)
				if (!visy[j] && slack[j] == 0 && check(j)) return;
		}
	}

	T solve() {
		// 初始化一组可行顶标
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++) lx[i] = max(lx[i], graph[i][j]);

		for (int i = 0; i < n; i++) {
			fill(slack.begin(), slack.end(), inf);
			fill(visx.begin(), visx.end(), false);
			fill(visy.begin(), visy.end(), false);
			bfs(i);
		}

		for (int i = 0; i < n; i++) {
			if (graph[i][matchx[i]] > 0) res += graph[i][matchx[i]];
			else matchx[i] = -1;
		}
		return res;
	}
};

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> p[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= n; i++) cin >> c[i];

	Hungarian<ll> solver(n, n);

	// 将边权设为每个d[j]能获得的荣誉之和
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			ll w = 0;
			for (int k = 1; k <= n; k++)
				if (b[i] + c[j] > a[k]) w += p[k];
			solver.addEdge(i - 1, j - 1, w);
		}
	}

	cout<< solver.solve();

	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

代码II -> 2019ICPC南京-J(BFS版本的KM算法-II)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 405;
int n;  // 组数
ll a[MAXN], b[MAXN], c[MAXN];  // 对手能量、我方两种队员的能量
int p[MAXN];  // 荣誉
ll w[MAXN][MAXN];  // w[i][j]表示左侧节点i与右侧节点j间的边权
ll lx[MAXN], ly[MAXN];  // 左、右侧节点的顶标
bool visx[MAXN], visy[MAXN];  // 记录左、右侧节点是否已被访问,即是否在交错树中
int match[MAXN];  // 右侧节点匹配的左节点
int pre[MAXN];  // 记录节点的前驱
ll slack[MAXN];  // KM算法的slack数组

void bfs(int x) {
	int y = 0, tmpy = 0;
	ll delta = INFF;
	
	// 初始化
	memset(pre, 0, so(pre));
	memset(slack, INFF, so(slack));
	match[y] = x;
	
	// 模拟队列
	do {
		int u = match[y];  // 取出队首元素,即左侧节点
		delta = INFF, visy[y] = true;

		for (int v = 1; v <= n; v++) {  // 枚举右侧节点
			if (!visy[v]) {
				if (slack[v] > lx[u] + ly[v] - w[u][v])
					slack[v] = lx[u] + ly[v] - w[u][v], pre[v] = y;
				if (slack[v] < delta)
					delta = slack[v], tmpy = v;
			}
		}

		for (int v = 0; v <= n; v++) {
			if (visy[v]) lx[match[v]] -= delta, ly[v] += delta;
			else slack[v] -= delta;
		}
		y = tmpy;
	} while (match[y]);

	while (y) match[y] = match[pre[y]], y = pre[y];
}

ll KM() {
	// 初始化
	memset(lx, 0, so(lx));
	memset(ly, 0, so(ly));
	memset(match, 0, so(match));

	for (int i = 1; i <= n; i++) {
		memset(visy, false, so(visy));
		bfs(i);
	}

	ll res = 0;  // 最大权匹配的边权和
	for (int y = 1; y <= n; y++) res += w[match[y]][y];
	return res;
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> p[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= n; i++) cin >> c[i];

	// 将边权设为每个d[j]能获得的荣誉之和
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			ll tmp = 0;
			for (int k = 1; k <= n; k++)
				if (b[i] + c[j] > a[k]) tmp += p[k];
			w[i][j] = tmp;
		}
	}
	cout << KM();

	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

代码III -> 2019ICPC南京-J( O ( n 3 ) O(n^3) O(n3)DFS版本的KM算法)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 405;
int n;  // 组数
ll a[MAXN], b[MAXN], c[MAXN];  // 对手能量、我方两种队员的能量
int p[MAXN];  // 荣誉
ll w[MAXN][MAXN];  // w[i][j]表示左侧节点i与右侧节点j间的边权
ll lx[MAXN], ly[MAXN];  // 左、右侧节点的顶标
bool visx[MAXN], visy[MAXN];  // 记录左、右侧节点是否已被访问,即是否在交错树中
ll slack[MAXN];  // KM算法的slack[]数组
int match[MAXN], pre[MAXN];  // 当前匹配、前驱节点

bool dfs(int u, int fa) {  // 当前节点、前驱节点
	visx[u] = 1;
	for (int v = 1; v <= n; v++) {
		if (!visy[v]) {
			if (lx[u] + ly[v] == w[u][v]) {  // 若为相等子图
				visy[v] = 1;
				pre[v] = fa;  // 记录前驱
				if (!match[v] || dfs(match[v], v)) {  // 若未匹配
					match[v] = u;
					return true;
				}
			}
			else if (slack[v] > lx[u] + ly[v] - w[u][v]) {
				slack[v] = lx[u] + ly[v] - w[u][v];
				pre[v] = fa;
			}
		}
	}
	return false;
}

ll KM() {
	for (int i = 1; i <= n; i++) {
		lx[i] = -INFF, ly[i] = 0;
		for (int j = 1; j <= n; j++) lx[i] = max(lx[i], w[i][j]);  // 初始化一组可行顶标
	}

	for (int i = 1; i <= n; i++) {  // 枚举与0号节点匹配的点
		memset(visx, false, so(visx)), memset(visy, false, so(visy));
		for (int j = 1; j <= n; j++) slack[j] = INFF;

		int pos = 0;  // 记录要匹配的节点
		match[0] = i;
		while (match[pos]) {
			if (dfs(match[pos], pos)) break;  // 匹配完成

			ll delta = INFF;
			for (int j = 1; j <= n; j++) {
				if (!visy[j] && delta > slack[j]) {
					delta = slack[j];
					pos = j;
				}
			}

			for (int j = 1; j <= n; j++) {
				if (visx[j]) lx[j] -= delta;
				if (visy[j]) ly[j] += delta;
				else slack[j] -= delta;
			}
			visy[pos] = true;
		}

		while (pos) {  // 沿着pos的前驱回跳
			match[pos] = match[pre[pos]];
			pos = pre[pos];
		}
	}

	ll res = 0;
	for (int i = 1; i <= n; i++)
		if (match[i]) res += w[match[i]][i];
	return res;
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> p[i];
	for (int i = 1; i <= n; i++) cin >> b[i];
	for (int i = 1; i <= n; i++) cin >> c[i];

	// 将边权设为每个d[j]能获得的荣誉之和
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			ll tmp = 0;
			for (int k = 1; k <= n; k++)
				if (b[i] + c[j] > a[k]) tmp += p[k];
			w[i][j] = tmp;
		}
	}

	cout << KM();

	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解II : 解I的优化

解I的基础上,注意到无需关注 a [ ] a[] a[] d [ ] d[] d[]的具体大小,只需关注相对大小,则可对 a [ ] a[] a[]离散化.因最终答案为 ∑ i = 1 n p [ i ]    ( i f   ∃ d [ j ] > a [ i ] ) \displaystyle \sum_{i=1}^n p[i]\ \ (if\ \exists d[j]>a[i]) i=1np[i]  (if d[j]>a[i]),考虑用前缀和优化.

先将离散化后的 a [ ] a[] a[]升序排列,则对每个 d [ j ] = b [ x ] + c [ y ] d[j]=b[x]+c[y] d[j]=b[x]+c[y],它能打败的是 a [ ] a[] a[]中第一个 ≥ d [ j ] \geq d[j] d[j] a [ k ] a[k] a[k]之前的所有 a [ i ]    ( i = 1 , 2 , ⋯   , k − 1 ) a[i]\ \ (i=1,2,\cdots,k-1) a[i]  (i=1,2,,k1),考虑将相等的 a [ i ] a[i] a[i]对应的 p [ i ] p[i] p[i]累加在一起,用哈希表存储,用前缀和来快速查询.

代码IV -> 2019ICPC南京-J(离散化+前缀和+BFS版本的KM算法)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 405;
int n;  // 组数
ll a[MAXN], b[MAXN], c[MAXN], tmpa[MAXN];  // 对手能量、我方两种队员的能量、a的离散化数组
int p[MAXN];  // 荣誉
unordered_map<ll, int> w;  // w[i]表示a[]中所有能量为i的荣誉之和
ll presum[MAXN];  // w[]的前缀和
ll graph[MAXN][MAXN];  // 存图
ll lx[MAXN], ly[MAXN];  // 左、右侧节点的顶标
int visx[MAXN], visy[MAXN];  // 记录左、右侧节点是否已被访问,即是否在交错树中
int matchx[MAXN];  // 左侧匹配
int pre[MAXN];  // 记录节点的前驱
ll slack[MAXN];  // KM算法的slack数组
int link[MAXN];  // 与右侧节点匹配的左侧节点,若为0表示未匹配
int timestamp = 0;  // 时间戳,用于记录访问时间
int que[MAXN << 1], head, tail;  // BFS的队列

void update(int x) {
	if (!x) return;  // x=0时为空节点,无需更新

	link[x] = pre[x];
	update(matchx[pre[x]]);
	matchx[pre[x]] = x;
}

void bfs(int now) {
	timestamp++;  // 更新时间戳
	ll delta = 0;

	// 初始化
	memset(slack, INFF, so(slack));
	head = tail = 0;
	que[tail++] = now;

	while (1) {
		while (head < tail) {
			int u = que[head++];
			visx[u] = timestamp;  // 记录访问时间
			for (int v = 1; v <= n; v++) {
				if (visy[v] ^ timestamp) {  // 若访问时间不同
					delta = lx[u] + ly[v] - graph[u][v];
					if (!delta) {
						visy[v] = timestamp;  // 记录访问时间
						pre[v] = u;  // 记录v的前驱
						if (!link[v]) {  // 若未匹配
							update(v);
							return;
						}
						que[tail++] = link[v];
					}
					else if (slack[v] > delta) {
						slack[v] = delta;
						pre[v] = u;  // 记录v的前驱
					}
				}
			}
		}

		// 求delta
		delta = INFF;
		for (int v = 1; v <= n; v++) 
			if (visy[v] ^ timestamp && delta > slack[v]) delta = slack[v];
		
		// 修改顶标
		for (int i = 1; i <= n; i++) {
			if (visx[i] == timestamp) lx[i] -= delta;
			if (visy[i] == timestamp) ly[i] += delta;
			else slack[i] -= delta;
		}

		for (int v = 1; v <= n; v++) {
			if (visy[v] ^ timestamp && !slack[v]) {
				visy[v] = timestamp;  // 记录访问时间
				if (!link[v]) {  // 若未匹配
					update(v);
					return;
				}
				que[tail++] = link[v];
			}
		}
	}
}

ll KM() {
	for (int i = 1; i <= n; i++) bfs(i);

	ll res = 0;
	for (int i = 1; i <= n; i++) 
		if (graph[link[i]][i]) res += graph[link[i]][i];
	return res;
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
    cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i], tmpa[i] = a[i];  // 备份一份a[]用于离散化
	for (int i = 1; i <= n; i++) cin >> p[i], w[a[i]] += p[i];  // 相同能量的荣誉累加在一起
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= n; i++) cin >> c[i];

	// 将tmpa[]离散化
	sort(tmpa + 1, tmpa + n + 1);
	int cnt = unique(tmpa + 1, tmpa + n + 1) - (tmpa + 1);

	for (int i = 1; i <= cnt; i++) presum[i] = presum[i - 1] + w[tmpa[i]];  // 求w[]的前缀和

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			ll d = b[i] + c[j];
			// 找到tmpa[]中第一个≥d的位置,之前的a[i]都能被d打败
			int pos = lower_bound(tmpa + 1, tmpa + cnt + 1, d) - (tmpa + 1);
			graph[i][j] = presum[pos];  // 边权设为d能打败的a[]的荣誉之和
			if (graph[i][j] > lx[i]) lx[i] = graph[i][j];  // 更新左侧顶标
		}
	}

	cout << KM();

	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}


B. Chessboard

题目大意

T    ( 1 ≤ T ≤ 1 e 5 ) T\ \ (1\leq T\leq 1\mathrm{e}5) T  (1T1e5)组测试数据.给 n    ( 1 ≤ n ≤ 1 e 6 ) n\ \ (1\leq n\leq 1\mathrm{e}6) n  (1n1e6) m    ( 1 ≤ m ≤ 1 e 6 ) m\ \ (1\leq m\leq 1\mathrm{e}6) m  (1m1e6)列的网格染色,要求染色路径连续且能遍历所有网格,且不经过已染色的网格,求染色方案数模 1 e 9 + 7 1\mathrm{e}9+7 1e9+7.

注意到最多 1 e 5 1\mathrm{e}5 1e5组测试数据,而 n n n m m m最大 1 e 6 1\mathrm{e}6 1e6,用搜索、DP等都不能在 1   s 1\ \mathrm{s} 1 s内通过,考虑直接推公式.

注意到染色只能将一块矩形不断地扩大,即给一个矩形增加一行或增加一列,否则最后会遇到贪吃蛇咬到自己的尾巴的情况,不符合题意.关键:染完一个矩形的最后一个点必在四个角上.

考虑将 1 × 1 1\times 1 1×1的矩形不断染色扩充为 n × m n\times m n×m的矩形,则需扩展 ( n − 1 ) (n-1) (n1)行、 ( m − 1 ) (m-1) (m1)列,共需扩展 n − 1 + m − 1 = n + m − 2 n-1+m-1=n+m-2 n1+m1=n+m2次,里面选 ( n − 1 ) (n-1) (n1)次扩展行(类似于网格图中从一个给定点走到另一给定点的方案数),最后染色的点是矩形的四个角之一,故最终答案 4 C n + m − 2 n − 1 4C_{n+m-2}^{n-1} 4Cn+m2n1.

注意特判 n = 1 n=1 n=1 m = 1 m=1 m=1的情况,即只有一行或一列,只有两种可能,即从两端之一开始染色.

代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 2e6 + 5, MOD = 1e9 + 7;
int fac[MAXN], invfac[MAXN];  // 阶乘、阶乘模MOD的逆元

int qpow(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % MOD;
		a = (ll)a * a % MOD;
		b >>= 1;
	}
	return res;
}

void init() {  // 预处理出阶乘、阶乘的逆元
	fac[0] = invfac[0] = 1;  // 0!=1
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;  // 阶乘

	invfac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2);  // Fermat小定理求逆元
	for (int i = MAXN - 2; i; i--) invfac[i] = (ll)invfac[i + 1] * (i + 1) % MOD;
}

int C(int n, int m) {  // 组合数C(n,m)%MOD
	return (ll)fac[n] * invfac[m] % MOD * invfac[n - m] % MOD;
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	init();

	CaseT{
		int n,m; cin >> n >> m;
		int ans = (n == 1 || m == 1) ? 2 : (ll)4 * C(n + m - 2, n - 1) % MOD;
		cout << ans << endl;
	}
	
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}


C. Digital Path

题目大意

n × m    ( 1 ≤ n , m ≤ 1000 ) n\times m\ \ (1\leq n,m\leq 1000) n×m  (1n,m1000)的矩阵中的元素 a i j ∈ [ − 1 e 7 , 1 e 7 ] a_{ij}\in[-1\mathrm{e}7,1\mathrm{e}7] aij[1e7,1e7],输出其中满足下列四个条件的路径数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模:①路径只能横着或竖着走;②路径是最长的,即无法再向四个方向沿伸;③路径长度至少为 4 4 4;④路径经过的数每次严格 + 1 +1 +1.

解I

d p [ i ] [ j ] [ k ]    ( k = 0 , 1 , 2 , 3 , 4 ) dp[i][j][k]\ \ (k=0,1,2,3,4) dp[i][j][k]  (k=0,1,2,3,4)表示到点 ( i , j ) (i,j) (i,j)的路径长度为 k k k的路径数, k = 4 k=4 k=4时表示路径长度 ≥ 4 \geq 4 4的路径数.

显然入度为 0 0 0的点都是起点,出度为 0 0 0的点都是终点,故可用BFS,一开始先将所有入度为 0 0 0的点入队.但若像常规情况,更新出的下一个可行的点入队,可能会导致路径重复计算,因为更新出的点可能入度不为 0 0 0,即它可能是多条路径经过的点,一种可行的做法是每更新出一个可行的点,将其入度 − 1 -1 1,直至其入度减为 0 0 0时才入队.

从点 ( x , y ) (x,y) (x,y)更新到点 ( c u r x , c u r y ) (curx,cury) (curx,cury)时,注意 d p [ c u r x ] [ c u r y ] [ 4 ] dp[curx][cury][4] dp[curx][cury][4]要用 d p [ c u r x ] [ c u r y ] [ 4 ] dp[curx][cury][4] dp[curx][cury][4] d p [ x ] [ y ] [ 3 ] dp[x][y][3] dp[x][y][3] d p [ x ] [ y ] [ 4 ] dp[x][y][4] dp[x][y][4]更新,

代码I -> 2019ICPC南京-C(DP+BFS)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 1e3 + 5, MOD = 1e9 + 7;
int n, m;  // 地图行数、列数
int graph[MAXN][MAXN];  // 地图
int in[MAXN][MAXN], out[MAXN][MAXN];  // 每个点的入度和出度
bool vis[MAXN][MAXN];  // 记录每个点是否已访问
// dp[i][j][k]表示到点(i,j)的路径长度为k的路径数,其中dp[i][j][4]为长度≥4的路径数
int dp[MAXN][MAXN][5];  
const int dx[4] = { 1,-1,0,0 }, dy[4] = { 0,0,1,-1 };

void init() {  // 预处理出每个点的入度和出度
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) {
			for (int k = 0; k < 4; k++) {
				int curx = x + dx[k], cury = y + dy[k];
				if (curx<1 || curx>n || cury<1 || cury>m) continue;
				if (graph[x][y] == graph[curx][cury] + 1) in[x][y]++;
				if (graph[x][y] == graph[curx][cury] - 1) out[x][y]++;
			}
		}
	}
}

void bfs() {
	queue<pii> que;  // 队列元素是坐标
	// 将入度为0的点(起点)的dp[][][1]置为1并入队
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) {
			if (!in[x][y]) {  
				dp[x][y][1] = 1;
				que.push(make_pair(x, y));
			}
		}
	}

	while (que.size()) {
		int x = que.front().first, y = que.front().second; que.pop();
		for (int k = 0; k < 4; k++) {
			int curx = x + dx[k], cury = y + dy[k];
			if (curx<1 || curx>n || cury<1 || cury>m) continue;

			if (graph[x][y] + 1 == graph[curx][cury]) {
				dp[curx][cury][2] = (dp[curx][cury][2] + dp[x][y][1]) % MOD;
				dp[curx][cury][3] = (dp[curx][cury][3] + dp[x][y][2]) % MOD;
				dp[curx][cury][4] = (dp[curx][cury][4] + dp[x][y][3] + dp[x][y][4]) % MOD;
				if (--in[curx][cury] == 0)  // 入度减为0才入队,防止路径重复计数
					que.push(make_pair(curx, cury)); 
			}
		}
	}
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	cin >> n >> m;
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) cin >> graph[i][j];
	
	init();

	bfs();

	int ans = 0;
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++)
			if (!out[x][y])  // 只对出度为0的点(终点)统计答案
				ans = (ans + dp[x][y][4]) % MOD;
	}
	cout << ans;
	
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解II

避免搜索过程中路径被重复计算的另一可行方法是从终点开始DFS(类似于 0 − 1 0-1 01背包压维后内层从后往前枚举,防止一个物品多次被选择),初始时将所有出度为 0 0 0的点 ( x , y ) (x,y) (x,y) d p [ x ] [ y ] [ 1 ] dp[x][y][1] dp[x][y][1]置为 1 1 1,最后只对入度为 0 0 0的点(起点)统计答案.

代码II -> 2019ICPC南京-C(DP+DFS)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 1e3 + 5, MOD = 1e9 + 7;
int n, m;  // 地图行数、列数
int graph[MAXN][MAXN];  // 地图
int in[MAXN][MAXN], out[MAXN][MAXN];  // 每个点的入度和出度
bool vis[MAXN][MAXN];  // 记录每个点是否已访问
// dp[i][j][k]表示到点(i,j)的路径长度为k的路径数,其中dp[i][j][4]为长度≥4的路径数
int dp[MAXN][MAXN][5];  
const int dx[4] = { 1,-1,0,0 }, dy[4] = { 0,0,1,-1 };

void init() {  // 预处理出每个点的入度和出度
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) {
			for (int k = 0; k < 4; k++) {
				int curx = x + dx[k], cury = y + dy[k];
				if (curx<1 || curx>n || cury<1 || cury>m) continue;
				if (graph[x][y] == graph[curx][cury] + 1) in[x][y]++;
				if (graph[x][y] == graph[curx][cury] - 1) out[x][y]++;
			}
		}
	}
}

void dfs(int x, int y) {
	if (vis[x][y]) return;  // 搜过了

	vis[x][y] = true;
	if (!out[x][y]) dp[x][y][1] = 1;  // 将终点的dp[][][1]置为1

	for (int k = 0; k < 4; k++) {
		int curx = x + dx[k], cury = y + dy[k];
		if (curx<1 || curx>n || cury<1 || cury>m) continue;

		if (graph[x][y] + 1 == graph[curx][cury]) {
			dfs(curx, cury);  // 继续搜下一个点

			dp[x][y][2] = (dp[x][y][2] + dp[curx][cury][1]) % MOD;
			dp[x][y][3] = (dp[x][y][3] + dp[curx][cury][2]) % MOD;
			dp[x][y][4] = (dp[x][y][4] + dp[curx][cury][3] + dp[curx][cury][4]) % MOD;
		}
	}
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	cin >> n >> m;
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) cin >> graph[i][j];
	
	init();

	for (int x = 1; x <= n; x++) 
		for (int y = 1; y <= m; y++) dfs(x, y);
	

	int ans = 0;
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++)
			if (!in[x][y])  // 只对出度为0的点(终点)统计答案
				ans = (ans + dp[x][y][4]) % MOD;
	}
	cout << ans;
	
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}

解III

d p 1 [ i ] [ j ] dp1[i][j] dp1[i][j]表示以点 ( i , j ) (i,j) (i,j)为起点的最长路径长度, d p 2 [ i ] [ j ] dp2[i][j] dp2[i][j]表示到点 ( i , j ) (i,j) (i,j)的路径数,用两个DFS分别求出两数组.

代码III -> 2019ICPC南京-C(记搜+DFS)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
#include<sstream>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef queue<int> qi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
#define umap unordered_map
#define IOS ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define so sizeof
#define pb push_back
#define npt nullptr
const double eps = 1e-7;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;

const int MAXN = 1e3 + 5, MOD = 1e9 + 7;
int n, m;  // 地图行数、列数
int graph[MAXN][MAXN];  // 地图
ll dp1[MAXN][MAXN];  // dp1[i][j]表示以(i,j)为起点的最长路径长度
ll dp2[MAXN][MAXN];  // dp2[i][j]表示到点(i,j)的路径数
const int dx[4] = { 1,-1,0,0 }, dy[4] = { 0,0,1,-1 };

void init() {  // 预处理出每个点的入度和出度
	memset(dp1, -1, so(dp1));
	memset(dp2, -1, so(dp2));
}

bool check(int x, int y) {  // 判断是否有经过点(x,y)的合法路径
	if (dp1[x][y] < 4) return false;  // 没有从该点出发的长度≥4的路径

	for (int k = 0; k < 4; k++) {
		int curx = x + dx[k], cury = y + dy[k];
		if (curx<1 || curx>n || cury<1 || cury>m) continue;

		if (dp1[curx][cury] == dp1[x][y] + 1) return false;
	}
	return true;
}

ll dfs1(int x, int y) {  // 用于求dp1[][]的DFS
	ll res = 1;
	for (int k = 0; k < 4; k++) {
		int curx = x + dx[k], cury = y + dy[k];
		if (curx<1 || curx>n || cury<1 || cury>m) continue;

		if (graph[curx][cury] == graph[x][y] - 1) {
			if (dp1[curx][cury] == -1) dp1[curx][cury] = dfs1(curx, cury);  // 搜下一个没有搜过的点
			res = max(res, 1 + dp1[curx][cury]);
		}
	}
	return res;
}

ll dfs2(int x, int y) {  // 用于求dp2[][]的DFS
	ll res = 0;
	for (int k = 0; k < 4; k++) {
		int curx = x + dx[k], cury = y + dy[k];
		if (curx<1 || curx>n || cury<1 || cury>m) continue;

		if (graph[curx][cury] == graph[x][y] - 1) {
			if (dp2[curx][cury] == -1) dp2[curx][cury] = dfs2(curx, cury);  // 搜下一个没有搜过的点
			res = (res + dp2[curx][cury]) % MOD;
		}
	}
	return res;
}

int main() {
	IOS;
#ifndef ONLINE_JUDGE
	clock_t my_clock = clock();
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	// ----------------------------------------------------------------
	init();

	cin >> n >> m;
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++) cin >> graph[i][j];
	
	ll maxlength = 0;  // 记录最长路径长度
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) {
			if (dp1[x][y] == -1) dp1[x][y] = dfs1(x, y);  // 更新未被更新过的点的dp1[][]
			maxlength = max(maxlength, dp1[x][y]);
		}
	}
	
	if (maxlength < 4) return cout << 0, 0;  // 无合法解

	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) 
			if (dp1[x][y] == 1) dp2[x][y] = 1;  // 将所有终点的dp2[][]置为1
	}

	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) 
			if (dp2[x][y] == -1) dp2[x][y] = dfs2(x, y);  // 更新未被更新过的点的dp2[][]
	}

	ll ans = 0;
	for (int x = 1; x <= n; x++) {
		for (int y = 1; y <= m; y++) {
			if (check(x, y)) {
				for (int k = 0; k < 4; k++) {
					int curx = x + dx[k], cury = y + dy[k]; 
					if (curx<1 || curx>n || cury<1 || cury>m) continue;

					if ((graph[x][y] == graph[curx][cury] + 1) && dp1[curx][cury] >= 3)
						ans = (ans + dp2[curx][cury]) % MOD;
				}
			}
		}
	}
	cout << ans;
	
	// ----------------------------------------------------------------
#ifndef ONLINE_JUDGE
	cout << endl << "Time used " << clock() - my_clock << " ms." << endl;
#endif
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值