美团 CodeM 资格赛 Round A 题解
初赛A轮题目在线练习地址
身体训练
-
根据期望的线性性质,枚举第 i 个人是第 j 个轮到的,每个事件的概率都是 1/n。 相当于计算:
暴力计算即可。 - 代码:
#include <cstdio> #include <algorithm> #include <cstring> #define rep(i,x,y) for(int i=x;i<=y;++i) #define dep(i,x,y) for(int i=x;i>=y;--i) using namespace std; const int N = 1005; int n; double v, u, c[N], d[N]; int main(){ scanf("%d%lf%lf",&n,&v,&u); rep(i,1,n) scanf("%lf",c+i); rep(i,1,n) scanf("%lf",d+i); double ans = 0; rep(i,1,n) rep(j,1,n) ans+=1.0/(c[i]-(j-1)*d[i]-v); ans*=u; printf("%.3lf\n",ans); }
合并回文子串
- 考虑 c 中的回文子串,既然是子串,就一定可以拆成 a, b 两串的两个子串的 combine。不妨 设是 a[i, j]与 b[k, l]的 combine,则可以考虑动态规划的状态 f[i][j][k][l]表示 a[i, j]与 b[k, l]的 combine 能否组成回文子串。 则可以匹配第一个字符和最后一个字符来转移,根据第一个字符和最后一个字符分别来自 a 还是 b 共有四种转移:
-
边界情况:
- 当 j – i + 1 + l – k + 1 = 0 时答案是 true
- 当 j – i + 1 + l – k + 1 = 1 时答案是 true。
- 代码:
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<iostream> using namespace std; int T; int n,m; char a[55],b[55]; bool f[55][55][55][55]; int main() { for(scanf("%d",&T);T--;) { scanf("%s",a+1);n=strlen(a+1); scanf("%s",b+1);m=strlen(b+1); int ans=0; for(int d1=0;d1<=n;d1++) for(int d2=0;d2<=m;d2++) for(int i=1,j=d1;j<=n;i++,j++) for(int k=1,l=d2;l<=m;k++,l++) { if(d1+d2<=1)f[i][j][k][l]=1; else { f[i][j][k][l]=0; if(d1>1&&a[i]==a[j])f[i][j][k][l]|=f[i+1][j-1][k][l]; if(d1&&d2&&a[i]==b[l])f[i][j][k][l]|=f[i+1][j][k][l-1]; if(d1&&d2&&b[k]==a[j])f[i][j][k][l]|=f[i][j-1][k+1][l]; if(d2>1&&b[k]==b[l])f[i][j][k][l]|=f[i][j][k+1][l-1]; } if(f[i][j][k][l])ans=max(ans,d1+d2); } printf("%d\n",ans); } return 0; }
倒水
大致分三种情况讨论:
- T 大于所有 ti:由于要求温度最大,当然是把所有水都倒完。
-
T 小于等于所有 ti:因为倒水只会把水的温度往 T 靠拢,所以找一个最小的 ti,把其他所
有 tj 都倒水变成 ti。 -
存在 ti < T 且存在 tj > T:显然无解。
代码:
#include<bits/stdc++.h> #define int64 long long #define sqr(x) (x)*(x) #define mk make_pair #define pb push_back #define fi first #define se second #define rep(i,x,y) for(int i=(x);i<=(y);++i) #define VI vector<int> #define VI64 vector<int64> #define VS vector<string> #define PII pair<int,int> #define PDD pair<double,double> #define VPII vector< PII > #define SZ(x) ((int)(x).size()) #define N 120000 using namespace std; int n,t[N],c[N]; int T,C; void P(double x){ printf("Possible\n"); printf("%.4lf\n",x); exit(0); } void fail(){ printf("Impossible\n"); exit(0); } int main(int argv,char **argc){ freopen(argc[1],"r",stdin); freopen(argc[2],"w",stdout); scanf("%d",&n); scanf("%d%d",&T,&C); cerr<<C<<endl; bool f1=0,f2=0; rep(i,1,n){ scanf("%d%d",&t[i],&c[i]); if(t[i]<T)f1=1; if(t[i]>T)f2=1; } if(f1 && f2)fail(); if(!f1 && !f2)P(T); if(f1){ T*=-1; rep(i,1,n)t[i]*=-1; } int minnT=1e9; int64 cc=0; rep(i,1,n)minnT=min(minnT,t[i]); if(minnT==T)fail(); rep(i,1,n)cc+=(minnT*c[i]-c[i]*t[i]); if(T-minnT>0 && cc>(int64)C*(T-minnT))fail(); if(T-minnT<0 && cc<(int64)C*(T-minnT))fail(); cerr<<"lucky"<<endl; if(f1){ double f1=0,f2=0; f1=(double)T*C; f2=C; rep(i,1,n)f1+=t[i]*c[i],f2+=c[i]; P(-f1/f2); }else P(minnT*(f1?-1:1)); return 0; }
最长树链
- 枚举每个约数,保留对应的边,做一次最长路径。因为一个数的约数个数可以保证,所以复杂度符合要求。
- 代码:
#include <cstdio> #include <cstdlib> #include <cstring> #include <ctime> #include <cmath> #include <iostream> #include <algorithm> #include <map> #include <vector> using namespace std; int n, l, c[100001], len, value[100001], visit[100001], V[100001]; bool b[100001]; map< int, vector<int> > factor; struct node{ node *next; int where; } a[200001], *first[100001]; inline void makelist(int x, int y) { a[++l].where = y; a[l].next = first[x]; first[x] = &a[l]; } pair<int, int> bfs(int init, int v, int round) { c[1] = init; visit[init] = 1; int pos = 0, will = 0; int k = 1, l = 1; for (; l <= k; ++l) { int m = c[l]; if (visit[m] > will) will = visit[m], pos = m; for (node *x = first[m]; x; x = x->next) if (!(value[x->where] % v) && !visit[x->where]) { visit[x->where] = visit[m] + 1; c[++k] = x->where; } } if (round == 0) for (int i = 1; i <= k; i++) visit[c[i]] = 0; return make_pair(pos, will); } int calc(int v) { vector<int> idx = factor[v]; int will = 0; for (int i = 0; i < idx.size(); i++) if (!visit[idx[i]]) { will = max(will, bfs(bfs(idx[i], v, 0).first, v, 1).second); } for (int i = 0; i < idx.size(); i++) visit[idx[i]] = 0; return will; } int main() { len = 0; memset(b, false, sizeof(b)); for (int i = 2; i <= 100000; i++) { if (!b[i]) c[++len] = i; for (int j = 1; j <= len; ++j) if (c[j] * i > 100000) break; else { b[c[j] * i] = true; if (!(i % c[j])) break; } } scanf("%d", &n); memset(first, 0, sizeof(first)); l = 0; for (int i = 1; i < n; i++) { int x, y; scanf("%d%d", &x, &y); makelist(x, y); makelist(y, x); } factor.clear(); for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); value[i] = x; for (int j = 1; c[j] * c[j] <= x; ++j) if (!(x % c[j])) { if (factor.find(c[j]) == factor.end()) factor[c[j]].clear(); factor[c[j]].push_back(i); for (; !(x % c[j]); ) x /= c[j]; } if (x != 1) { if (factor.find(x) == factor.end()) factor[x].clear(); factor[x].push_back(i); } } int ans = 0; memset(visit, 0, sizeof(visit)); memset(V, 0, sizeof(V)); for (map< int, vector<int> >::iterator itr = factor.begin(); itr != factor.end(); ++itr) ans = max(ans, calc(itr->first)); printf("%d\n", ans); }
数列互质
- 对于在整个序列中出现次数超过 sqrt(n)的数,最多只有 sqrt(n)种,那么对于这些数,很容易用一个数组做前缀和维护,可以在每次询问中直接求出它们的出现次数并 gcd 判断。剩下的数的出现次数不超过 sqrt(n),那么可以对它出现恰好 w 次的区间左右端点写出一个范围约束,这样的约束只总共只有 nsqrt(n)对,把左端点和右端点看成笛卡尔坐标系上的 x,y坐标,约束就可以看成一个矩形,问题转化为求询问点被多少矩形覆盖到,可以用扫描线来离线对所有询问计算。
- 一个 trick case,k=1 是需要注意 gcd(1,0)=1 的问题。
- 代码:
#include <stdio.h> #include <stdlib.h> #include <vector> #include <math.h> #include <algorithm> #define L 100 using namespace std; int n,m,S,Sn,i,j,k,w,l,r,u,v,ul,vr,c; int a[50005],sum[50000/L+5][50005],ans[50005],t[50005]; int ql[50005],qr[50005],qk[50005]; vector<int> V[100005]; vector<pair<int,int> > Ins[L+5][50005],Del[L+5][50005]; vector<int> query[50005]; int main() { scanf("%d%d",&n,&m);S=min((int)sqrt(n),100); for(i=1;i<=n;++i)scanf("%d",&a[i]),V[a[i]].push_back(i); for(i=1;i<=n;++i) if(V[i].size()>=S) { ++Sn; for(k=V[i].size(),j=0;j<k;++j)++sum[Sn][V[i][j]]; for(j=1;j<=n;++j)sum[Sn][j]+=sum[Sn][j-1]; } else { c=V[i].size(); for(j=0;j<c;++j) { u=V[i][j]; if(j)ul=V[i][j-1]+1;else ul=1; for(k=j,l=1;k<c;++k,++l) { v=V[i][k]; if(k<c-1)vr=V[i][k+1]-1;else vr=n; Ins[l][ul].push_back(make_pair(v,vr)); Del[l][u].push_back(make_pair(v,vr)); } } } for(i=1;i<=m;++i) { scanf("%d%d%d",&ql[i],&qr[i],&qk[i]); l=ql[i];r=qr[i]; query[l].push_back(i); for(j=1;j<=Sn;++j)if(sum[j][r]-sum[j][l-1]>0&&__gcd(sum[j][r]-sum[j][l-1],qk[i])==1)++ans[i]; } for(i=1;i<S;++i) for(j=1;j<=n;++j) { for(k=Ins[i][j].size()-1;k>=0;--k) { l=Ins[i][j][k].first; r=Ins[i][j][k].second; for(w=l;w<=n;w+=w&-w)++t[w]; for(w=r+1;w<=n;w+=w&-w)--t[w]; } for(k=query[j].size()-1;k>=0;--k) { l=query[j][k]; u=qr[l];v=qk[l]; if(__gcd(v,i)!=1)continue; for(w=u;w;w-=w&-w)ans[l]+=t[w]; } for(k=Del[i][j].size()-1;k>=0;--k) { l=Del[i][j][k].first; r=Del[i][j][k].second; for(w=l;w<=n;w+=w&-w)--t[w]; for(w=r+1;w<=n;w+=w&-w)++t[w]; } } for(i=1;i<=m;++i)printf("%d\n",ans[i]); }
二分图染色
- 完全二分图转棋盘模型,即 N ×N 棋盘上放黑白棋子,每个格子至多放一个,同行同列没有相同颜色的棋子。
-
用容斥原理,先假设每个格子可以放多个,再减去不合法的方案。如果每个格子可以放多个,一个不合法的格子所在的行和列都不会再摆放其他棋子,也没有两个不合法的格子同行同列。因此我们有计算式:
-
其中 AN−i 表示在 (N − i) × (N − i)的棋盘上放单色棋子,使得没有棋子同行同列的方案数。 考虑从 (N − 1)
×(N − 1)棋盘递推到 N ×N 棋盘,我们有:
-
第一项是在第 N 行或者第 N 列上放一枚棋子或者不放的方案数,由于放两枚棋子的情况会
被统计两次,最后还需减去摆两枚棋子的方案数,即第三项。
- 代码:
#include <cstdio> #include <cstdlib> #include <iostream> #include <algorithm> const int N = (int) 1e7; const int mod = (int) 1e9 + 7; using namespace std; long long fac[N+10], inv[N+10], a[N+10]; int n; long long ex(int x, int y, int w) { if ((w = (w + y) % y) % x == 0) return w / x; return ((ex(y % x, x, -w) * y) + w) / x; } long long c(int n, int m) { return (long long) fac[n] * inv[m] % mod * inv[n - m] % mod; } int main() { cin >> n; fac[0] = 1; for (int i = 1; i <= n; ++i) fac[i] = fac[i-1] * (long long) i % mod; inv[n] = ex(fac[n], mod, 1); for (int i = n; i >= 1; --i) inv[i-1] = (long long) inv[i] * i % mod; a[0] = 1; for (int i = 1; i <= n; ++i) a[i] = (2LL * i * a[i - 1] % mod - ((long long) (i-1) * (i-1)) % mod * a[i - 2] % mod + mod) % mod; long long ans = 0; for (int i = 0; i <= n; ++i) ans = (ans + ((i & 1 ? mod - 1LL : 1LL) * c(n, i) % mod * c(n, i) % mod * fac[i] % mod * a[n-i] % mod * a[n-i] % mod)) % mod; cout << ans << endl; }