"蔚来杯"2022牛客暑期多校训练营5补题 B, F, G, H, K, D, A
群友的题意https://docs.qq.com/doc/DVXJqWHZrRGtFbWR2
K Headphones 水题
题意:
代码:
#include <iostream> #include <cstring> #include <vector> #include <bits/stdc++.h> #define endl '\n' #define int long long #define pii pair<char, int> #define ull unsigned long long using namespace std; const int N = 1e6 + 10; int n,m; void solve() { cin>>n>>m; int k=n-m; if(k>m) { int ans=k+(m+1); cout<<ans<<endl; } else cout<<-1<<endl; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t = 1; while (t--) solve(); return 0; }
B Watches 二分
题意:
思路:
注意到答案具有单调性,考虑二分答案k
则问题变为判定是否能选择k件物品,总花费不超过M元,直接贪心即可
代码:
#include <bits/stdc++.h> #define endl '\n' #define int long long #define fir first #define sec second #define ins 0x3f3f3f3f #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int N = 1e5 + 10; int a[N], b[N], n, m; bool check(int mid){ for(int i = 1; i <= n; i++){ b[i] = a[i] + mid * i; } sort(b + 1, b + 1 + n); int sum = 0; for(int i = 1; i <= mid; i++){ sum += b[i]; } if(sum > m) return false; return true; } void solve(){ cin >> n >> m; for(int i = 1; i <= n; i++){ cin >> a[i]; } int l = 0, r = n; while(l < r){ int mid = l + r + 1 >> 1; if(check(mid)){ l = mid; } else r = mid - 1; } cout << l << endl; } signed main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); solve(); return 0; }
H Cutting Papers 数学
题意:
思路:
根据不等式画出封闭区域,
直接计算圆形和阴影部分的并集
答案就是 (n/2)^2*(2+pi/2)
代码:
double ans, n; void solve(){ cin >> n; double r = n / 2; cout <<fixed << setprecision(9) << r * r * pi / 2 + r * r * 2; }
G KFC Crazy Thursday 马拉车算法
题意:
给定一个字符串,问有多少个以K或者F或者C结尾的回文子串。
思路:
马拉车算法,求出len。
利用区间加法获得总和即可。
也就是(直接看代码更容易理解)对于新串在i处“+1”,在i+len[i]+1处“-1”。因为这个区间内的字符都有某个以他为结尾的回文串。
思路2
回文自动机模板题,初始化回文自动机之后,直接循环输入字符串统计回文子串的数量
代码:
#include <bits/stdc++.h> #define endl '\n' #define int long long #define pii pair<int, int> #define pll pair<int, int> #define ull unsigned long long using namespace std; const int N = 2e7 + 10; char a[N], b[N]; int p[N]; int n; void init() { int k = 0; b[k++] = '$', b[k++] = '#'; for (int i = 0; i < n; i++) b[k++] = a[i], b[k++] = '#'; b[k++] = '^'; n = k; } void manacher() { int mr = 0, mid; for (int i = 0; i < n; i++) { if (i < mr) p[i] = min(p[mid * 2 - i], mr - i); else p[i] = 1; while (b[i - p[i]] == b[i + p[i]]) p[i]++; if (i + p[i] > mr) { mr = i + p[i]; mid = i; } } } int d[N]; int cnt[128]; void solve() { cin>>n; cin >> a; init(); manacher(); for (int i = 0; i < n; i++) { int L = i, R = i + p[i] - 1; d[L]++; d[R + 1]--; } int pre = 0; for (int i = 0; i <n; i++) { pre += d[i]; cnt[b[i]] += pre; } cout << cnt['k'] << " " << cnt['f'] << " " << cnt['c'] << endl; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t = 1; // cin >> t; while (t--) solve(); return 0; }
F A Stack of CDs计算几何
题意:
思路:
对于只有两个圆的时候,可以用余弦定理计算交点和圆形的连线和水平面的夹角。
对每个圆都搜索上面的所有这样的覆盖,计算每个盖住它的圆形的线段的左右端点,合并起来。
具体来说:
对于一个圆,可以用-pi到pi的弧度表示区间,然后就可以用线段覆盖的方法求出区间被覆盖了多少了。 因此关键是对于两个圆盘i,j,如何求出i被覆盖的区间。
首先对于两个圆的圆心x,y,可以求出向量(y-x)的atan2值,相当于与x轴正方向的夹角α,范围-pi到pi。然后根据余弦定理,得到x的圆心到两圆的一个焦点的射线关于两圆连心线旋转角的余弦,然后用acos得到旋转角β,那么α±β就是对应的区间。
注意边界问题,如果下界<-pi,就变成下界+2pi->pi,上界>pi同理。还有要判断圆相离的情况。
最后计算没有被覆盖到的线段的长度。
代码:下落的圆盘
#include <bits/stdc++.h> using namespace std; const double pi = acos(-1.0); const int N = 2005; int n, cnt; struct Point{ double x, y; Point(){} Point(double x, double y) : x(x), y(y) {} bool operator <(const Point &rhs) const{ return x < rhs.x; } }s[N]; struct Circle{ Point o; double r; }a[N]; double sqr(double x){ return x*x; } double getdis(Point a, Point b){ return sqrt(sqr(b.x - a.x) + sqr(b.y - a.y)); } void ins(double x, double y){ s[++cnt] = Point(x, y); } void getsegment(Circle u, Circle v, double dis){ double t1 = atan2(v.o.y - u.o.y, v.o.x - u.o.x); double t2 = acos((sqr(dis) + sqr(u.r) - sqr(v.r)) / (2*dis*u.r)); Point t; t.x = t1 - t2; t.y = t1 + t2; if(t.x >= -pi && t.y <= pi) ins(t.x, t.y); else if(t.x < -pi){ ins(t.x + 2*pi, pi); ins(-pi, t.y); }else{ ins(t.x, pi); ins(-pi, t.y - 2*pi); } } double cal(){ double ans = 0; double l = -10, r = -10; sort(s + 1, s + cnt + 1); for(int i = 1; i <= cnt; i++){ if(s[i].x > r){ ans += r - l; l = s[i].x; r = s[i].y; } else{ r = max(r, s[i].y); } } ans += (r - l); return 2*pi - ans; } int main(){ double ans = 0; scanf("%d", &n); for(int i = n; i >= 1; i--) scanf("%lf%lf%lf", &a[i].r, &a[i].o.x, &a[i].o.y); for(int i = 1; i <= n; i++){ cnt = 0; int j; for(j = 1; j < i; j++){ double tmp = getdis(a[i].o, a[j].o); if(a[j].r - a[i].r > tmp) break; if(a[j].r + a[i].r > tmp && fabs(a[j].r - a[i].r) < tmp){ getsegment(a[i], a[j], tmp); } } if(j == i) ans += a[i].r * cal(); } printf("%.3f\n", ans); return 0; }
D Birds in the tree 树形dp
题意:
思路:
可以看出1的答案和0的答案是独立的;所以我们先求1的答案,再求0的答案。
用t表示这次我们所求的度数为1的节点的颜色。
我们定义dp[u] 为节点u和其的子树的符合条件的个数。
我们如何转移呢
首先想到的肯定是:
但是因为对于下图
因为每个v的整个树都可以不选择。
就是下面这种情况:
4这棵子树都不要。
因此每个需要+1。
又因为防止所有子树都不选择的情况,就是只剩下u节点。(因为只剩下u节点不一定是成立的,需要单独判断)
所以最后需要-1。
然后再单独判断u可不可以选。
因此
然后就是求
因为ans和dp的含义有点不同:
ans[u]每次需要加:在选择当前节点的前提下,有多少种选法。
dp[u]表示u及其子树有多少种选法。(u节点不一定选)。
大部分情况都是相同的,有一种情况就是
如果并且u和单独一个v形成的树。如下图,很明显这种是错误的
因为u!=t又因为 , 所以u!=x1 ,u!=x2不符合题意。
所以
代码:
#include <bits/stdc++.h> #define endl '\n' #define int long long #define pii pair<int, int> #define pll pair<int, int> #define ull unsigned long long using namespace std; const int N = 3e5+10, M = 1e9+7; int n; int a[N]; int ans=0; int dp[N],t; vector<int> g[N]; void dfs(int u,int fa) { int sum=0,mul=1; for(int v:g[u]){ if(v==fa) continue; dfs(v,u); (sum+=dp[v])%=M; (mul*=(dp[v]+1))%=M; } (dp[u]=(a[u]==t)+mul-1+M)%=M; // cout<<dp[u]<<endl; if(a[u]==t) { (ans+=dp[u])%=M; } else (ans+=dp[u]-sum+M)%=M; } void solve() { cin>>n; string s; cin>>s; for(int i=1;i<=n;i++){ a[i]=s[i-1]-'0'; } for(int i=0;i<n-1;i++){ int x,y;cin>>x>>y; g[x].push_back(y); g[y].push_back(x); } t=0; dfs(1,-1); t=1; memset(dp,0,sizeof dp); dfs(1,-1); cout<<ans%M<<endl; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t = 1; // cin >> t; while (t--) solve(); return 0; }
A Don’t Starve 普通DP
题意:
在二维平面上,有n个点有食物(每个点可以吃多次,每个点包含无限数量的食物,但一次只能取一个,离开后食物会刷新。)。
从原点出发,每次直线前往其他任意有食物的点,对与行走的规则是每一步都必须严格短于上一步。
求最多可以收集到多少食物
思路
每次去到的下一个点的距离都要递减,但是如果每次都选择最长的,可能不是最优解,所以不是贪心,考虑DP。
设表示以i为父节点, j 还能拿走多少食物。
因为当前状态j,只与父节点和子节点有关。
得转移方程 其中,这条路的长度小于的长度
而因为离开一个点后可以返回,所以可以反向dp,考虑倒推。先将所有边按长度从小到大排序,这样在考虑当前的路时,之前的路都是可以走的,
如果有长度相同通往同一个点的路,应该平行处理,因此还需要用记录上一次从出发最远可以走多远。
代码:
#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e3+5; int n; int dp[N][N]; int x[N],y[N]; int s[N]; struct Node{ int u,v; int w; }; vector<Node> vec; bool cmp(Node a,Node b) { return a.w<b.w; } //2点间距离公式 inline int d(int u,int v) { return (x[u]-x[v])*(x[u]-x[v])+(y[u]-y[v])*(y[u]-y[v]); } void solve() { cin >> n; bool flag = false; for(int i=1;i<=n;i++){ cin>>x[i]>>y[i]; if (!x[i] && !y[i]) flag = true; vec.push_back({0,i,d(0,i)}); } for(int i=1;i<n;++i){ for(int j=i+1;j<=n;++j){ vec.push_back({i,j,d(i,j)}); } } //从小到大排序 sort(vec.begin(),vec.end(),cmp); for(int i=0;i<vec.size();i++){ int j=i; //将边权相同的边平行处理,因为题目要求严格递减 while(vec[i].w==vec[j+1].w && j<vec.size()) j++; //终点不能是0节点 for(int k=i;k<=j;k++){ int u=vec[k].u,v=vec[k].v; if(v) dp[u][v]+=s[v]+1; if(u) dp[v][u]+=s[u]+1; } for(int k=i;k<=j;k++){ int u=vec[k].u,v=vec[k].v; if(u) s[u]=max(s[u],dp[u][v]); if(v) s[v]=max(s[v],dp[v][u]);; } i=j; } long long ans = 0; for (int i = 1; i <= n; ++i) ans = max(ans, dp[0][i]); cout << ans + flag << endl; } signed main() { int t = 1; // cin >> t; while (t--) solve(); return 0; }
优化思路
我们可以设 表示从点x出发后能到达的最多食物点。还是倒推,转移方程式为:
代码:
//从小到大排序 sort(vec.begin(), vec.end(), cmp); for (int l = 0, r; l < vec.size(); l = r) { r = l; while (vec[l].w == vec[r].w && r < vec.size()) ++r; for (int j = l; j < r; j++) { int u = vec[j].u, v = vec[j].v; g[u] = dp[u], g[v] = dp[v]; } for (int j = l; j < r; j++) { int u = vec[j].u, v = vec[j].v; g[u] = max(g[u], dp[v] + 1); if (u) //如果是0出发的点就不做反向处理 { swap(u, v); g[u] = max(g[u], dp[v] + 1); } } for (int j = l; j < r; j++) { int u = vec[j].u, v = vec[j].v; dp[u] = g[u], dp[v] = g[v]; } } cout << dp[0] + flag << endl; }
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16547313.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步