视频讲解: 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} (2≤n≤1e9)的任一有 k k k个元素的子集中,都存在一对 ( u , v ) s . t . u ∣ v (u,v)\ s.t.\ u\mid v (u,v) s.t. u∣v.
解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. u∣v.
忽略 n n n的限制,讨论不同的区间起点对应的 k k k的最小值:
[ 1 , 1 + k − 1 ] [1,1+k-1] [1,1+k−1],因 1 1 1是 2 ∼ 1 + k − 1 2\sim 1+k-1 2∼1+k−1的约数,则 k k k最小值为 2 2 2.
[ 2 , 2 + k − 1 ] [2,2+k-1] [2,2+k−1],最小的区间是 [ 2 , 4 ] [2,4] [2,4],即 2 + k − 1 = 4 2+k-1=4 2+k−1=4,解得 k = 3 k=3 k=3.
[ 3 , 3 + k − 1 ] [3,3+k-1] [3,3+k−1],最小的区间是 [ 3 , 6 ] [3,6] [3,6],即 3 + k − 1 = 6 3+k-1=6 3+k−1=6,解得 k = 4 k=4 k=4.
⋮ \vdots ⋮
注意找最小的区间的方法:使得区间右端点是区间左端点的两倍.现已限制区间右端点为 n n n,则能选出的最靠右的长度为 k k k的区间左端点为 n − k + 1 n-k+1 n−k+1.右端点是左端点的两倍,即 n = 2 ( n − k + 1 ) n=2(n-k+1) n=2(n−k+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. u∣v.
考虑连续的情况,设区间左端点为 m m m,则 m m m的两倍不能在区间中,即最大区间为 [ m , 2 m − 1 ] [m,2m-1] [m,2m−1],则使得 2 m − 1 ≤ n 2m-1\leq n 2m−1≤n的最大的 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 2m−1≤n的最大的 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 1∼n中的每个数都能表示为 a × 2 k ( k ∈ N ) a\times 2^k\ \ (k\in\mathbb N) a×2k (k∈N)的形式,其中 a a a是 ≤ n \leq n ≤n的奇数.而 1 ∼ n 1\sim n 1∼n中有 ⌈ n 2 ⌉ \left\lceil\dfrac{n}{2}\right\rceil ⌈2n⌉个奇数,从 1 ∼ n 1\sim n 1∼n中取 ( ⌈ 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} P∈AB且更接近 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} P∈AB且更接近 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} S△DEP=S△DEC,
则 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} S△BEP=S△BED+S△DEP=S△BED+S△DEC=S△BCD=2S△ABC,故点 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} P∈AB且更接近 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 2S△BEP=S△ABC⇔2BE⋅BP⋅sinB=BC⋅BA⋅sinB,则 B E = B C ⋅ B A 2 B P BE=\dfrac{BC\cdot BA}{2 BP} BE=2BPBC⋅BA.
求出 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+c≥2(b+c)+1,解得 a ≥ b + c + 1 a\geq b+c+1 a≥b+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) (1≤n≤1e5)个站,编号 0 ∼ n 0\sim n 0∼n, i i i号站有能量 a i ( 0 ≤ a i ≤ 50 ) a_i\ \ (0\leq a_i\leq 50) ai (0≤ai≤50),每访问一个站都可获得该站的所有能量,每次只能访问能量不超过当前自身的能量的站,每个站只能访问一次.初始时在 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 0≤ai≤50,则当前能量 ≥ 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) (n−z)个能量非零的站的方案数 x x x,从 n n n个位置选出 ( n − z ) (n-z) (n−z)个位置放能量非零的站,剩下的 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=x⋅Cnn−z⋅z!=x⋅(n−z)!(n−n+z)!n!⋅z!=x⋅(n−z)!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)!⋅y≡1 (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!⋅y′≡1 (mod MOD),则 x ≡ y ′ = y ( n + 1 ) ( m o d M O D ) x\equiv y'=y(n+1)\ \ (\mathrm{mod}\ MOD) x≡y′=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 1∼1e5的数的阶乘的逆元,时间复杂度 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) (n−z)个能量非零的站的方案数.初始能量最低为 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 (1≤n≤400)的数列 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 −1e18≤ai,bi,ci≤1e18,1≤pi≤1e4.
注
很多人(像我一样)认为最后战斗有 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=1∑np[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=1∑np[i]⋅nn!⋅n=i=1∑np[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=1∑np[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,⋯,k−1),考虑将相等的 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 (1≤T≤1e5)组测试数据.给 n ( 1 ≤ n ≤ 1 e 6 ) n\ \ (1\leq n\leq 1\mathrm{e}6) n (1≤n≤1e6)行 m ( 1 ≤ m ≤ 1 e 6 ) m\ \ (1\leq m\leq 1\mathrm{e}6) m (1≤m≤1e6)列的网格染色,要求染色路径连续且能遍历所有网格,且不经过已染色的网格,求染色方案数模 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) (n−1)行、 ( m − 1 ) (m-1) (m−1)列,共需扩展 n − 1 + m − 1 = n + m − 2 n-1+m-1=n+m-2 n−1+m−1=n+m−2次,里面选 ( n − 1 ) (n-1) (n−1)次扩展行(类似于网格图中从一个给定点走到另一给定点的方案数),最后染色的点是矩形的四个角之一,故最终答案 4 C n + m − 2 n − 1 4C_{n+m-2}^{n-1} 4Cn+m−2n−1.
注意特判 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 (1≤n,m≤1000)的矩阵中的元素 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 0−1背包压维后内层从后往前枚举,防止一个物品多次被选择),初始时将所有出度为 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;
}