牛客周赛 Round 62(期望、DFS、主席树、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 (rx2+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 的父亲为0
	ll 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。

  1. 如果没走到 x = 0 的位置,则任意顺序走即可。
  2. 如果走到 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值