2020牛客寒假算法基础集训营2 题解报告

这篇博客详细介绍了牛客寒假算法基础集训营的题目解法,涵盖概率DP、计算几何等多个方面。解题过程中讨论了如何处理期望DP中的取模问题以及计算几何中避免浮点误差的方法。还提及了其他题目,如建立通道问题,通过构造方法求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A:做游戏

签到

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
	ll a,b,c;ll x,y,z;
	cin>>a>>b>>c>>x>>y>>z;
	cout<<min(a,y)+min(b,z)+min(c,x)<<endl;
}

B:排数字

签到

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=2e5+5;
char a[maxn];
int main()
{
	ll n;cin>>n;
	ll c1=0,c6=0;
	for(ll i=1;i<=n;i++) {cin>>a[i];if(a[i]-'0'==1) c1++;if(a[i]-'0'==6) c6++;}
	if(c6&&c1&&c6>=c1+1) cout<<c1<<endl;
	else if(c6&&c1&&c6<=c1) cout<<c6-1<<endl;
	else cout<<"0"<<endl;
}


C:算概率

tag:数论,期望dp
在这里插入图片描述
Emmm,这题还是有点难搞的,主要是自己第一次接触期望dp,不知道这种取模的含义,这种取模比较另类吧(可能现在还一知半解的),我个人理解是这样字的,比如概率p=a/b,你就用一种类似哈希的思想(纯属口胡,不知道该咋称呼),转换成一个很大的数字q%mod,所以呢(1-p)就可转换成(mod+1-q)%mod了,是一种表示概率的转化思想。弄懂这个了然后再想这个dp问题,状态转移方程,i道题你对了j道的概率就是:

f[i][j]=f[i-1][j]*(1-p[i])+f[i-1][j-1]*p[i];
这个应该很容易懂吧,学过概率论就很清楚

当然啦,我刚刚说过一种类哈希思想的数字转化,p[i]在输入的时候已经转化过了,所以针对1-p[i]你还得亲自操刀转化,p[i]必然大于1的,所以转化应该是(mod+1-p[i])%mod。这样就比较清楚易懂了,这题主要是不知道类哈希转化…呜呜呜

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const ll N=2e3+5;
ll n,p[N],f[N][N];
int main()
{
	cin>>n;
	for(ll i=1;i<=n;i++) cin>>p[i];
	f[0][0]=1;
	for(ll i=1;i<=n;i++){
		f[i][0]=f[i-1][0]*(mod+1-p[i])%mod;
		for(ll j=1;j<=i;j++){
			f[i][j]=(f[i-1][j]*(mod+1-p[i])+f[i-1][j-1]*p[i])%mod;
		}
	}
	for(ll i=0;i<=n;i++) cout<<f[n][i]<<" ";
	return 0;
}


D:数三角

在这里插入图片描述
tag:计算几何
这个题出的很好,因为我本来用余弦定理来做然后成功的被卡了eps(wa了20发长记性了呜呜呜呜),这道题解法很多,比如比较边的平方和啦(我最后用这种方法过的),但是我觉得大部分人可能没想那么多,就直接用的余弦定理来过,Emmm这里我要提一下,如果double的话,不要和0比较,因为浮点数double是无限接近于0的,我们用eps 1e-8来表示
在这里插入图片描述
上这张图更清楚易懂的理解
附上余弦定理解法版本:

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const ll maxn=2e5+5;
struct node{
    double x,y;
}a[502];
int main()
{
    IO;int n;
    scanf("%d",&n);
    for(ll i=1;i<=n;i++){
        scanf("%lf%lf",&a[i].x,&a[i].y);
    }
    int cnt=0;
    for(int i=1;i<=n-2;i++){
        for(int j=i+1;j<=n-1;j++){
            for(int k=j+1;k<=n;k++){
                double z1=sqrt((a[i].y-a[j].y)*(a[i].y-a[j].y)+(a[i].x-a[j].x)*(a[i].x-a[j].x));
                double z2=sqrt((a[i].y-a[k].y)*(a[i].y-a[k].y)+(a[i].x-a[k].x)*(a[i].x-a[k].x));
                double z3=sqrt((a[j].y-a[k].y)*(a[j].y-a[k].y)+(a[j].x-a[k].x)*(a[j].x-a[k].x));
                double c3=(z1*z1+z2*z2-z3*z3)/(2*z1*z2);
                double c2=(z1*z1+z3*z3-z2*z2)/(2*z1*z3);
                double c1=(z3*z3+z2*z2-z1*z1)/(2*z2*z3);
                if(z1+z2==z3||z2+z3==z1||z1+z3==z2) continue;              
                if(c1<-(1e-8)||c2<-(1e-8)||c3<-(1e-8)) cnt++;
            }
        }
    }
    printf("%d\n",cnt);
}

E:做计数

在这里插入图片描述
这个题很有意思

sqrt(i)+sqrt(j)=sqrt(k)
完全平方
i+j+2sqrt(ij)=k
要符合条件,需要i*j为完全平方数

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=2e5+5;
int main()
{
	ll n;cin>>n;
	ll ans=0;
	ll m=sqrt(n);
	for(ll i=1;i<=m;i++){
		for(ll j=1;j<=i;j++){
			if((i*i)%j==0){//判断因数对
				ans++;
				if((i*i)/j!=j) ans++;//如果2个因数不一样可以弄2次
			}
		}
	}
	cout<<ans<<endl;
}


F:拿物品

这个题和CHD第二次排位赛的一道题很有意思(论证方法和国王游戏一样),这道题我一开始想错了,以为上下都从大到小排序,然后取最大标记vis[id],因为我们要考虑问题的时候:自己要赚的多,但是也得想歪招让对方赚的少。所以呢就不是那么简单的贪心思想了。以下给了正解,这个方法很常用。
在这里插入图片描述

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const ll maxn=2e5+5;
struct node{
	ll id,v1,v2;
}a[maxn];
bool cmp(node a,node b){
	return a.v1+a.v2>b.v1+b.v2;
}
int main()
{
	ll n;cin>>n;
	for(ll i=1;i<=n;i++) {cin>>a[i].v1;a[i].id=i;}
	for(ll i=1;i<=n;i++) {cin>>a[i].v2;}
	sort(a+1,a+n+1,cmp);
	vector<ll> aa;vector<ll> bb;	
	ll cnt=0;
	for(ll i=1;i<=n;i++){
		if(cnt%2==0) aa.push_back(a[i].id),cnt++;
		else bb.push_back(a[i].id),cnt++;
	}
	for(ll i=0;i<aa.size();i++) {cout<<aa[i]<<" ";} cout<<endl;
	for(ll i=0;i<bb.size();i++) {cout<<bb[i]<<" ";} cout<<endl;	
}


/*
4
1 8 2 4
2 7 4 3
这个样例可以证明贪心取最大然后标记记录的做法是错误的
*/

G:判正误

签到
tag:快速幂取模

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const ll maxn=2e5+5;
ll qp(ll a,ll b,ll c){
    ll ans = 1;
    ll base = a%c;
    while(b){
        if(b & 1) ans = (ans*base)%c;
        base = (base*base)%c;
        b >>= 1;
    }
    return ans;
}

int main()
{
	ll q;cin>>q;
	while(q--){
		ll a,b,c,d,e,f,g;
		cin>>a>>b>>c>>d>>e>>f>>g;
		ll zy=qp(a,d,mod)+qp(b,e,mod)+qp(c,f,mod);
		if(zy!=g) cout<<"No"<<endl;
		else cout<<"Yes"<<endl;
	}	
}


H:施魔法

tag:dp,dp的优化
这是一道非常好的dp问题(题解如下)
这是题解
后来补题的时候,我写了一个TLE的代码,复杂度是O(n^2):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 7;
int dp[N], pre, a[N], n, k;
int main()
{
	cin>>n>>k;
	for(ll i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+n+1);
	for(ll i=1;i<=n;i++) dp[i]=2e9;
	for(ll i=k;i<=n;i++){
		for(ll j=0;j<=i-k;j++){
			dp[i]=min(dp[i],dp[j]+a[i]-a[j+1]);
		}
	}
	cout<<dp[n]<<endl;
}

其实是跟题解里的式子一样的,第一个等号出来了,关键在于第二个等号,我们发现对于第二个循环枚举j来说,a[i]是多余的,可以提出来,然后剩下dp[j]-a[j+1]。

其实我们枚举第二个循环的目的就找这个最小的dp[j]-a[j+1],但这个式子只与当前的自变量有关,我们可以直接用一个pre变量在算的同时维护掉,省去这个第二重枚举。
在这里插入图片描述
就是dp递推转移式子的化简来实现dp复杂度的优化。

后来补一下AC的正确代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 7;
ll dp[N], pre, a[N], n, k;
int main()
{
	cin>>n>>k;
	for(ll i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+n+1);
	for(ll i=1;i<k;i++) dp[i]=2e9;
	ll pre=-a[1];
	for(ll i=k;i<=n;i++){
		dp[i]=pre+a[i];
		pre=min(pre,dp[i-k+1]-a[i-k+2]);
	}
	cout<<dp[n]<<endl;
}


I:建通道

在这里插入图片描述
这个题出的挺有意思的,我们把vi^vj看成一个数x吧,所以就是求x的lowbit。
所以我们应该想方设法的给x造1。那么我们需要找一个数位k,然后对m个去重后的数字遍历,该k位需要有0和1。那么有0的就可以和1匹配,得到1;有1的就可以和0匹配得到0.
在这里插入图片描述
这样的话该位置k就是1了,所以呢,即(1<<k)*(m-1)就行了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
ll v[maxn];
int main()
{
	ll n;cin>>n;
	for(ll i=0;i<n;i++) cin>>v[i];
	sort(v,v+n);
	ll m=unique(v,v+n)-v;//去重操作
	if(m==1) cout<<"0"<<endl;
	else{
		for(ll i=0;i<=30;i++){
			ll cnt0=0,cnt1=0;
			for(ll j=0;j<m;j++){
				if((v[j]&1<<i)==0) cnt0++;//记录0的个数
				else cnt1++;			  //记录1的个数
			}
			if(cnt0&&cnt1){//如果该位置同时有1和0的话
				cout<<(1<<i)*(m-1)<<endl;//记住是m-1条联通边哦
				break;
			}
		}
	}
	return 0;
}


J:求函数

待补

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值