题目链接:
2020牛客暑期多校训练营(第二场)
题解
B题Boundary
题意
给你n个点,考虑这选一个经过原点的圆,并且这个圆经过这n个点的个数最多,求这个最多的点数。
题解
这个题的做法挺多的,可以枚举圆心或者半径,也可以枚举圆周角(题解的做法)。
时间复杂度:O(n^2logn)
首先我们需要知道一个定理:三个点可以确定一个圆。
做法一:枚举圆心或者半径
现在我们已知一个原点(0,0),然后我们可以枚举两个点,然后求出三个点形成的圆的圆心,并记录圆心的最大出现次数,
最后我们枚举i个点,如果这i个点里面任选两个点和原点形成的圆的个数(即C(i,2))等于圆心的最大出现次数,那么i就是答案,因为从i个点任选两个点就保证了所有任选的两个点和原点所形成的圆心都是相同的。
这种方法比较巧妙,也比较难想。枚举半径的方法类似。
trick1:需要考虑n个点里面出现原点的情况,以及所有点都共线的情况。
trick2:用map维护最大圆心出现次数会超时,所以只能暴力枚举来求。
代码实现:
#include
using namespace std; const double eps = 1e-10; struct Point{ double x,y; Point(){} Point(double _x,double _y):x(_x),y(_y){} }p[2005]; double cx,cy; int flag; void solve(Point a, Point b, Point c) //三点共圆求圆心公式 { double fm1=2 * (a.y - c.y) * (a.x - b.x) - 2 * (a.y - b.y) * (a.x - c.x); double fm2=2 * (a.y - b.y) * (a.x - c.x) - 2 * (a.y - c.y) * (a.x - b.x); if (fm1 == 0 || fm2 == 0){ flag=1; return; } double fz1=a.x * a.x - b.x * b.x + a.y * a.y - b.y * b.y; double fz2=a.x * a.x - c.x * c.x + a.y * a.y - c.y * c.y; cx = (fz1 * (a.y - c.y) - fz2 * (a.y - b.y)) / fm1; cy = (fz1 * (a.x - c.x) - fz2 * (a.x - b.x)) / fm2; } vector >v; int main(){ int n;scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].x,&p[i].y); for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ flag=0; solve(Point{0.0,0.0},p[i],p[j]); if(flag) continue; v.push_back(make_pair(cx,cy)); } } if(v.size()==0){ cout<<1< cur=v[0]; int len=v.size(); for(int i=1;i 做法二:枚举圆周角
同样我们可以枚举两个点,然后求出这两个点和原点形成的圆周角,然后求出圆周角的众数就行了。
trick:角相等(即弧相等)但是属于两个对称圆的情况,如下图所示:
代码实现:
#include
#define m_p make_pair #define p_i pair #define _for(i, a) for(register int i = 0, lennn = (a); i < lennn; ++i) #define _rep(i, a, b) for(register int i = (a), lennn = (b); i <= lennn; ++i) #define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n" #define mem(a, b) memset(a, b, sizeof(a)) #define mem0(a) memset(a, 0, sizeof(a)) #define fil(a, b) fill(a.begin(), a.end(), b); #define scl(x) scanf("%lld", &x) #define sc(x) scanf("%d", &x) #define abs(x) ((x) > 0 ? (x) : -(x)) #define PI acos(-1) #define lowbit(x) (x & (-x)) using namespace std; typedef long long LL; typedef unsigned long long ULL; const int maxn = 2005; const int maxm = 1000005; const int maxp = 30; const int inf = 0x3f3f3f3f; const LL INF = 0x3f3f3f3f3f3f3f3f; const int mod = 1000000007; const double eps = 1e-12; const double E = 2.718281828; typedef struct poi { double x, y; poi() {} poi(double x, double y) :x(x), y(y) {} void inp() { scanf("%lf%lf", &x, &y); } bool operator < (poi tem) { if(fabs(x-tem.x)<=eps) return y < tem.y; else return x 0 ? 1 : -1); } long double X, Y, R; int n; poi a[maxn]; double b[maxn]; int bl; void cal(Vector p12, Vector p13) { R = Dot(p12, p13) / (Length(p12) * Length(p13)); } void sol() { _for(i, n) scanf("%lf%lf",&a[i].x,&a[i].y); int ans = 0; _for(i, n) { bl = 0; _for(j, n) { if(Cross(a[i], a[j]) < 0) { cal(a[j] - a[i], a[j]); b[bl++] = R; } } sort(b, b + bl); for(int i = 0, j = 0; i < bl; i = j) { while(j < bl && fabs(b[j] - b[i]) <= eps) ++j; ans = max(ans, j - i); } } printf("%d\n", ans + 1); } int main() { //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); while(cin>>n) { sol(); } return 0; } C题Cover the Tree
题意:给你一个n-1个节点的无根树,然后求出最小的覆盖所有边的链(路径)的个数,以及这些链。
题解:链的个数就是(叶子节点个数+1)/2,然后直接求出所有叶子节点的dfs序就行了,并且把这些叶子节点按照dfs序排序,最后按照间隔为(叶子节点个数/2)从前往后匹配输出就行了。
具体证明过程如下(来源:官方题解)
代码实现:
#include
using namespace std; const int N = 2e5+7; vector G[N]; vector v; int dfn[N],tot; void dfs(int u,int f){ if(G[u].size()==1) v.push_back(u); dfn[u]=++tot; for(int v:G[u]){ if(v==f) continue; dfs(v,u); } } bool cmp(int a,int b){ return dfn[a]>n; for(int i=1;i<=n-1;i++){ int u,v;cin>>u>>v; G[u].push_back(v); G[v].push_back(u); } dfs(1,-1); // for(int i=1;i<=n;i++) cout< D题Duration
签到题,转换一下直接算就行了。
F题Fake Maxpooling
题意:给你三个数n,m,k,n和m表示矩阵的行数和列数,矩阵的每一个元素等于其行数和列数的最小公倍数,让你求出这个矩阵的k*k的子矩阵的最大值的和。
正解应该是二维单调队列+优化求n*m的矩阵,时间复杂度为O(nm)
当然也可以直接暴力求n*m的矩阵,或者用二维st表维护,时间复杂度为O(nmlogn),常数不大应该不会超时。
优化求n*m的矩阵的方法(类似于线性筛,保证每个lcm(i,j)都只求一次)为
代码实现
#include
#define m_p make_pair #define _for(i, a) for(register int i = 0, lennn = (a); i < lennn; ++i) #define _rep(i, a, b) for(register int i = (a), lennn = (b); i <= lennn; ++i) using namespace std; typedef long long LL; const int maxn = 5005; int n, m, k; int a[maxn][maxn]; int gcd[maxn][maxn]; deque dq; inline void init() { memset(gcd,0,sizeof(gcd)); _rep(i, 1, n) { _rep(j, 1, m) { if(!gcd[i][j]){ for(int k=1;k*i<=n&&k*j<=m;k++){ gcd[k*i][k*j]=k; a[k*i][k*j]=i*j*k; } } } } } void sol() { init(); _rep(i, 1, n) { dq.clear(); _rep(j, 1, m) { while(dq.size() && dq.front() < j - k + 1) dq.pop_front(); while(dq.size() && a[i][dq.back()] <= a[i][j]) dq.pop_back(); dq.push_back(j); a[i][j] = a[i][dq.front()]; } } _rep(j, 1, m) { dq.clear(); _rep(i, 1, n) { while(dq.size() && dq.front() < i - k + 1) dq.pop_front(); while(dq.size() && a[dq.back()][j] <= a[i][j]) dq.pop_back(); dq.push_back(i); a[i][j] = a[dq.front()][j]; } } LL sum = 0; _rep(i, k, n) { _rep(j, k, m) { sum += a[i][j]; } } cout << sum << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); while(cin>>n>>m>>k) { sol(); } return 0; } J题Just Shuffle
题意:给你一个排列a,以及一个整数k,这个排列是某一个排列p置换k次后的结果,现在让你求出这个排列。
首先这个题需要知道对组合数学中的置换有一定了解,不懂的可以参考我的博客(组合数学——群论学习总结)
知道置换的基本概念后这个题就好做了。
根据题意我们可以知道 p ^ k = a (^表示置换)
因此 p = a ^ z ( ( z * k ) % r == 1),即z是k在模r意义下的逆元(因为k为质数,所有逆元一定存在)。
这里r就每个循环的循环长度,因此我们求出z后,把当前循环再置换z次后就是答案。
trick:求逆元直接用费马小定理求会超时(常数比较大),需要用扩展欧几里得求或者直接枚举逆元。
官方题解:
代码实现:
#include
#define ll long long using namespace std; const int N = 1e5+7; int a[N],res[N]; int vis[N]; ll k; vector v; ll quick_pow(ll a,ll b,ll mod){ ll res=1; while(b){ if(b&1) res=(res*a)%mod; b>>=1; a=(a*a)%mod; } return res; } ll inv(ll x,ll mod){return quick_pow(x,mod-2,mod)%mod;} void calc(){ int len=v.size(); ll Inv; // ll Inv=inv(1ll*k,1ll*len)%len; // cout< >n>>k; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ if(!vis[i]){ v.clear(); int x=a[i]; while(!vis[x]){ v.push_back(x); vis[x]=1; x=a[x]; } calc(); } } for(int i=1;i<=n;i++){ if(i==1) cout<
反思和总结
前期不是特别顺利,签到题出的也很坎坷,也就间接导致后面写自己擅长的置换时,所剩时间也不多了,而且还读错了题意。队友b题的精度也被卡的疯狂自闭,然后就崩了,还是太菜了。。。。。