文章目录
- 牛客周赛 Round 62(期望、DFS、主席树、DP、逆推DP)
- A. 小红的字符移动
- B. 小红的数轴移动
- C. 小红的圆移动
- D. 小红的树上移动 (期望、DFS)
- E. F. 小红的中位数查询(主席树)
- G. 小红的数轴移动(二)(DP、逆推DP)
牛客周赛 Round 62(期望、DFS、主席树、DP、逆推DP)
A. 小红的字符移动
交换前两个元素的位置即可。
#include<bits/stdc++.h>
using namespace std;int main() {string s;cin >> s;swap(s[0], s[1]);cout << s << endl;return 0;
}
B. 小红的数轴移动
根据题意模拟即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;int main() {ll n, x;cin >> n >> x;ll res = 0, s;while(n--){if(x == 0) break;cin >> s;res += s;if(x > 0) x -= s;else x += s;}cout << res << endl; return 0;
}
C. 小红的圆移动
对于原点不在圆内的圆,设圆心为(x,y),其移动的代价为: ( r − x 2 + y 2 ) ∗ Π ∗ r 2 (r - \sqrt{x^2+y^2}) * Π * r^2 (r−x2+y2)∗Π∗r2。
(让圆在圆心与原点的连线上移动为最优移动方案)
根据需要依次移除代价最小的圆即可。
#include<bits/stdc++.h>
using namespace std;const double pi = 3.14159265358979324;int main() {int n, k;cin >> n >> k;vector<double> tmp;double x, y, r;for(int i = 1; i <= n; i++){cin >> x >> y >> r;double len = x * x + y * y;if(len < r * r){ // 避免精度问题,比较时不开根号len = (r - sqrt(len)) * pi * r * r;tmp.push_back(len);}}sort(tmp.begin(), tmp.end());double res = 0;for(int i = 0; tmp.size() - i > k; i++) res += tmp[i];printf("%.10lf", res);return 0;
}
D. 小红的树上移动 (期望、DFS)
根据题意,小红只能由根向下走,不能走回头路,到叶子节点上停止。
即,树上每个分支都会对答案有贡献,每个分支的贡献为: 分支高度 ∗ ∏ ( 1 / x s o n ) , x ∈ 分支上结点 , x s o n 表示 x 的孩子数 分支高度 * \prod(1 / x_{son}),x \in 分支上结点, x_{son} 表示 x 的孩子数 分支高度∗∏(1/xson),x∈分支上结点,xson表示x的孩子数。
#include<bits/stdc++.h>
#define ll long long
using namespace std;const ll mod = 1e9 + 7;
const int maxn = 1e5 + 5;vector<int> e[maxn];ll qpow(ll a, ll b){ll res = 1;while(b){if(b & 1) res = res * a % mod;a = a * a % mod;b >>= 1;}return res;
}ll dfs(int f, int u, ll cnt, ll p){if(f != 0 && e[u].size() == 1) return cnt * qpow(p, mod-2) % mod;ll res = 0;for(auto v: e[u]){if(v == f || v == 0) continue; // 0 不参与计算res += dfs(u, v, cnt+1, p*(e[u].size()-1)%mod);res %= mod; }return res;
}int main() {int n;cin >> n;int x, y;for(int i = 2; i <= n; i++){cin >> x >> y;e[x].push_back(y);e[y].push_back(x);}e[1].push_back(0); // 假设 1 的父亲为0ll res = dfs(0, 1, 1, 1);if(n == 1) res = 1;cout << res << endl;return 0;
}
E. F. 小红的中位数查询(主席树)
主席树板子题,求区间(l,r)的中位数,即求区间(l,r)的第 (r - l ) / 2 + 1大。即,主席树求第k大。
这个板子是我随便搜索,大家可自行学习。传送门
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,m,a[N];
struct zxTree {int ls,rs,sum;//左右儿子,区间内元素个数
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define sum(x) tr[x].sum
} tr[N*40];//注意数组大小
int sz = 0;//不同版本的树的总数
int root[N];//root[i]存放第i棵树的树根的编号
int BuildTree(int l,int r) {/*建树,和普通线段树相同*/int rt = ++sz;sum(rt) = 0;if(l == r) return rt;int mid = l+r>>1;ls(rt) = BuildTree(l,mid);rs(rt) = BuildTree(mid+1,r);return rt;
}
int updata(int pre,int l,int r,int x) {/*新建一棵树,其比pre树多一个元素x*/int rt = ++sz;tr[rt] = tr[pre];sum(rt)++;if(l == r) return rt;int mid = l+r>>1;if(x <= mid) ls(rt) = updata(ls(pre),l,mid,x);else rs(rt) = updata(rs(pre),mid+1,r,x);return rt;
}
int ask(int pre,int rt,int l,int r,int k) {/* 依次是:上一个树根,当前树根,区间左右端点,所求区间第k大返回该区间第k大数的下标 */if(l >= r) return l;int res = sum(ls(rt)) - sum(ls(pre));int mid = l+r>>1;if(res >= k) return ask(ls(pre),ls(rt),l,mid,k);else return ask(rs(pre),rs(rt),mid+1,r,k-res);
}
int tmp[N];
int main() {scanf("%d%d",&n,&m);for(int i = 1; i <= n; i++) scanf("%d",a+i),tmp[i] = a[i];//离散化sort(tmp+1,tmp+1+n);int tot = unique(tmp+1,tmp+1+n)-tmp-1;root[0] = BuildTree(1,tot);for(int i = 1; i <= n; i++) {int x = lower_bound(tmp+1,tmp+1+tot,a[i])-tmp;root[i] = updata(root[i-1],1,tot,x);}//利用主席树可以加减原理计算for(int i = 1,l,r,k; i <= m; i++) {scanf("%d%d",&l,&r);k = (r - l + 1) / 2 + 1;int x = ask(root[l-1],root[r],1,tot,k);printf("%d\n", tmp[x]);}return 0;
}
G. 小红的数轴移动(二)(DP、逆推DP)
由于 x 可能由正负,下标整体偏移 10000。
设 d p ( i , M + j ) dp_{(i, M+j)} dp(i,M+j) 表示选择前 i 个物品,到达位置 j 需要的最小代价,其中 M 表示偏移量。
维护好dp数组后,判断是否需要全选,即是否走到了 x = 0。
- 如果没走到 x = 0 的位置,则任意顺序走即可。
- 如果走到 x = 0 的位置,则需要根据dp方程,逆推出转移顺序。
#include<bits/stdc++.h>
using namespace std;const int INF = 1e9;
const int maxn = 1e4 + 5;
const int M = 1e4; // 偏移量 int a[105], dp[105][maxn*3];
vector<int> pos[105];int main() {memset(dp, 0x3f, sizeof(dp)); // 初始化一个较大值
// cout << dp[5][100];int n, x;cin >> n >> x;int sum = 0;set<int> s;for(int i = 1; i <= n; i++) {cin >> a[i];s.insert(i);sum += a[i];pos[a[i]].push_back(i);}dp[0][M+x] = 0; // dp[i][j] 表示前 i 个物品走到 j 需要的代价,注意这时候没有限制移动方向for(int i = 1; i <= n; i++){for(int j = -10000; j <= 10000; j++) dp[i][M+j] = dp[i-1][M+j]; // 不选当前a[i] for(int j = -10000; j <= 10000; j++){if(j + a[i] <= 10000){ // 超过这个范围就走不回来了 dp[i][M + j + a[i]] = min(dp[i][M + j + a[i]], dp[i-1][M + j] + a[i]);}if(j - a[i] >= -10000){dp[i][M + j - a[i]] = min(dp[i][M + j - a[i]], dp[i-1][M + j] + a[i]);}} }if(dp[n][M] == dp[0][0]){ // dp 的值等于初始值,表示没走到原点,即不会停,任意走就行 cout << sum << endl;for(int i = 1; i <= n; i++) cout << i << " ";cout << endl;}else{ // 可以走到原点,倒推DP cout << dp[n][M] << endl;vector<int> to_left, to_right; // 记录都是那些往左走了,那些往右走了int j = 0, val = dp[n][M];for(int i = n; i >= 1; i--){ // 从后往前倒推 if(dp[i-1][M+j] == dp[i][M+j]) continue; // 不选当前 a[i]if(j + a[i] <= 10000 && dp[i-1][M + j + a[i]] == dp[i][M + j] - a[i]){ // (i, M+j) 可以由 (i-1, m+j+a[i]) 得到 to_left.push_back(pos[a[i]].back()); // 从左往右走a[i]s.erase(pos[a[i]].back()); // 这个元素已经用过了pos[a[i]].pop_back();`// 相同值,可能有多个,选一个就行j += a[i];val -= a[i];}if(j - a[i] >= -10000 && dp[i-1][M + j - a[i]] == dp[i][M + j] - a[i]){ // (i, M+j) 可以由 (i-1, m+j-a[i]) 得到 to_right.push_back(pos[a[i]].back()); // 从右往左走a[i]s.erase(pos[a[i]].back());pos[a[i]].pop_back();j -= a[i];val -= a[i];}}vector<int> ans;while(x != 0){if(x < 0){ // 小于零就在 左往右走 中取一个x += a[to_right.back()];ans.push_back(to_right.back());to_right.pop_back();}else{x -= a[to_left.back()];ans.push_back(to_left.back());to_left.pop_back();}}for(auto item : ans) cout << item << " ";for(auto item : s) cout << item << " ";}return 0;
}