2022牛客寒假算法基础集训营2 补题题解(未完成)


勉勉强强过了三道题,差距很大,继续努力

比赛传送门


A 小沙的炉石

原题传送门
可以二分也可以思考后打表过
官方题解:
在这里插入图片描述
在这里插入图片描述

二分方式:借鉴一个大佬的代码,主要理解这题的思路,见代码解释

#include<bits/stdc++.h>
using namespace std;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
ll n, m, q, x;
bool check(ll c) {
    return c * (m + 1 + m + c) / 2 >= x;
}
int main(){
  cin >> n >> m >> q;//读入数据 
  n = min(n, m + 1);
  //找出实际能用的攻击魔法牌数,因为初始体力只有一点,所以最多使用m+1张攻击魔法牌
  //如果n大于这个值,则n张牌不能用完,如果n小于这个值,则n张牌可以用完 
  ll mx = n * (m + 1 + m + n) / 2;//根据等差数列求和求出的能对弟弟造成的最大伤害
  //最大伤害序列:m+1,m+2....m+n等差数列求和得上式 
  //最小伤害序列:1,3,5....2*n-1 等差数列求和得伤害值下限:n^2
  //调整任意一张伤害牌和法力牌的出牌顺序,即可让伤害值+-1,所以在伤害上下限区间内的伤害值都是可以达到的 
  while(q--){//读入数据 
    scanf("%lld", &x);
    if(x <= mx) {//如果最大伤害大于弟弟的生命值,代表有可能刚好斩杀弟弟 
        ll l = 1, r = n;//二分用的牌数 	
        //已确定最大伤害值大于弟弟的生命值,所以现在要二分牌数(因为不一定要把所有牌都使出)
		// 
        while(l < r) {
            int mid = l + r >> 1;
            if(check(mid)) r = mid;//检查这个数量的牌能否斩杀弟弟
			//因为伤害区间内的任一数都是可以达到的,所以只要牌数mid对应的伤害值大于等于弟弟的生命值,就将牌数左移,直至最后找到刚好斩杀弟弟的生命值 
            else l = mid + 1; 
        }
        ll mi =  l * l ;
		//用应出的牌数的平方和弟弟的生命值对比,弟弟的生命值需要大于等于牌数的平方,这一步十分不理解 
        if(x >= l * l) {
            puts("YES");
        } else puts("NO");
    }
    else puts("NO");//如果最大伤害都不大于弟弟的生命值,那就不可能斩杀弟弟 
  }
}

思考+打表:出题人沙佬的解题思路,上面官方题解讲的就是这种思路,我也是理解二分的方法之后突然理解了这种方法,之前看着代码百思不得其解,只能说这题出的妙

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

ll n, m, k;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m>>k;
	ll u = min(n, m + 1);//实际能出的牌数
	ll r = (m + 1 + m + u) * u / 2;//最大伤害值
	while(k -- ){
		ll x;
		cin>>x;
		if(m == 1 && n > 1 && (x == 3 || x > 5)){//特殊情况,打表得出 
			cout<<"NO"<<endl;
			continue;
		}
		if(m == 2 && x == 8){//特殊情况,打表得出 
			cout<<"NO"<<endl;
			continue;
		}
		if(r >= x) cout<<"YES"<<endl;//不超过伤害值上限的生命值都可以达到斩杀 
		else cout<<"NO"<<endl; 
	} 
	return 0;
} 

B 小沙的魔法

原题传送门
大佬题解:
请添加图片描述
题解原地址
大佬题解2:
请添加图片描述
题解2原地址

自己的一部分理解手稿:
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

#define int long long
#define pb push_back 

typedef long long ll;

const int N = 5e5 + 10;

struct Node{
	int val, id;
}a[N];

ll res;
int n, m;
int fa[N];
vector<int>g[N];
int q[N];

int find(int x){
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

bool cmp(Node a, Node b){
	return a.val > b.val;
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin>>n>>m;
	
	for(int i = 1; i <= n; ++ i) cin>>a[i].val;
	
	for(int i = 1; i <= n; ++ i){
		a[i].id = i;
		fa[i] = i;
		res += a[i].val;
		q[i] = a[i].val;
	}
	//res记录朴素操作所有点的权值之和
	//id记录对应下标
	sort(a + 1, a + n + 1, cmp);
	
	for(int i = 1; i <= m; ++ i){
		int r1, r2;
		cin>>r1>>r2;
		//将所有连通的城市加入一个集合中 
		g[r1].pb(r2);
		g[r2].pb(r1);
	}
	
	for(int i = 1; i <= n; ++ i){
		for(auto x : g[a[i].id]){//遍历所有和第i个点联通的点 
			int u = find(a[i].id), v = find(x);
			if(q[u] > q[v] || u == v) continue;
			res = res - q[u] - q[v] + max(q[u], q[v]);
			q[u] = q[v] = min(q[u], q[v]);
			fa[u] = v;
		}
	}
	cout<<res<<endl;
	return 0;
}

D 小沙的涂色

这题的涂色思路要好好借鉴
大佬解题思路:大模拟构造题。根据 n % 3 的值分别为 0,1,2 时进行构造(构造方法很多,下面代码用的其中一种)。注意 n % 3 = 0 时无法满足题意,除此之外还需特判 n == 1 时的情况。
大佬题解
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

#define pb push_back 

typedef long long ll;

const int N = 1e3 + 10;

int n;
int idx;
int a[N][N];

void draw1(int x, int y){
	int i = x;
	for(int j = y; j <= n; j += 3){
		a[i][j] = a[i][j + 1] = a[i + 1][j] = ++ idx;
		a[i][j + 2] = a[i + 1][j + 1] = a[i + 1][j + 2] = ++ idx;
	}	
}

void draw2(int x){
	for(int i = x; i <= n; ++ i){
		if(i & 1) a[i - 1][1] = a[i][1] = a[i][2] = ++ idx;
		else a[i - 1][3] = a[i][2] = a[i][3] = ++ idx;
	}
}

void draw3(int x, int y){
	for(int i = x; i <= n; i += 3){
		for(int j = y; j <= n; j += 2){
			a[i][j] = a[i][j + 1] = a[i + 1][j] = ++ idx;
			a[i + 1][j + 1] = a[i + 2][j] = a[i + 2][j + 1] = ++ idx;
		}
	}
}

void Put(){
	cout<<"YES"<<endl;
	for(int i = 1; i <= n; ++ i){
		for(int j = 1; j <= n; ++ j){
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin>>n;
	if(n % 3 == 0) cout<<"NO"<<endl;
	else if(n == 1) cout<<"YES"<<" "<<0<<endl;
	else if(n % 3 == 1){
			a[1][1] = a[1][2] = a[2][1] = 1;
			a[4][4] = a[4][3] = a[3][4] = 2;
			a[3][1] = a[3][2] = a[4][2] = 3;
			a[2][2] = a[2][3] = a[3][3] = 4;
			a[1][4] = a[1][3] = a[2][4] = 5;
			idx = 5;
			draw1(1, 5);
			draw1(3, 5);
			if(n & 1) draw2(5), draw3(5, 4);
			else draw3(5, 1);
			Put();
		}
	else if(n % 3 == 2){
			a[1][1] = a[1][2] = a[2][2] = ++ idx;
			draw1(1, 3);
			if(n & 1) draw2(3), draw3(3, 4);
			else draw3(3, 1);
			Put();
		}		
		
	
	return 0;
}

E 小沙的长路

原题传送门
对于最长路最短的情况:只需要构成一个无环图即可,即最短的最长距离为n-1
对于最长路的情况,就需要走遍每个边,这就需要知道欧拉路径的概念(大佬讲的概念),然后就会发现最大最长距离其实就是欧拉路径,之后针对点的度数分别为偶数/奇数区别运算即可
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

#define int long long

signed main()
{
	int n;
	cin>>n;
	if(n & 1) cout<<n - 1<<" "<<n * (n - 1) / 2<<endl;
	else cout<<n - 1<<" "<<n * (n - 1) / 2 - n / 2 + 1<<endl;
	return 0;
}

F 小沙的算数

原题传送门
官方题解:
请添加图片描述

#include<bits/stdc++.h>

using namespace std;
#define int long long
typedef long long ll;

const int N = 1e6 + 10, mod = 1000000007;

ll gmi(ll a, ll k){//快速幂 
	int res = 1;
	a %= mod;
	while(k){
		if(k & 1) res = res * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return res;
}

ll ni(ll x){//求逆元 
	return gmi(x, mod - 2) % mod;
}

int n, q;
char str[N];
ll ans;//记录和
ll b[N], a[N], ps[N]; 

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin>>n>>q;
	cin>>str + 1;//记录运算符 
	str[n] = '+';//设置边界
	for(int i = 1; i <= n; ++ i){
		cin>>a[i];//计数 
	}
	int pos = 0;//记录位置 
	int res = 1;//记录每一块的乘积 
	memset(ps, -1, sizeof ps);
	for(int i = 1; i <= n; ++ i){
		res = res * a[i] % mod;//以加号为分割点,分隔开各个惩罚块 
		if(str[i] == '+'){
			ps[i] = pos;//记录在第几块内 
			b[pos ++ ] = res;//存在一个块内 
			res = 1;
		}
		else ps[i] = pos;//记录在第几块内 
	} 
	
	for(int i = 0; i < pos; ++ i) ans = (ans + b[i]) % mod;//所有数之和 
	
	while(q -- ){//更改 
		int x, y;
		cin>>x>>y;
		ll pi = ps[x];//先找到x在那个块内 
		ll temp = (b[pi] * y % mod * ni(a[x])) % mod;//更改x对应的块 
		if(temp >= b[pi]) ans = (ans + (temp - b[pi] + mod) % mod) % mod;//如果这一块变大了,那么对应的总和也要变大  
		else ans = (ans - (b[pi] - temp + mod) % mod + mod) % mod;//变小就变小 
		a[x] = y;
		b[pi] = temp;//将新的这一块的值放入 
		cout<<ans<<endl;
	}
	return 0;
}

G 小沙的身法

原题传送门

H 小沙的数数

原题传送门
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e6 + 10, mod = 1e9 + 7;

ll n, m;
ll a, b;
string str;
ll ans;

int qmi(ll a, ll k, ll p){//求a的k次幂mod p的值 
	ll res = 1;//用res记录答案 
	a = a % mod;
	while(k){//当k不为0时就一直循环 
		if(k & 1) res = ((res % mod) * (a % p)) % mod;//当k二进制的最后一位为1时,表示k分解的二的次方数中有这一位,此时进行运算
		k >>= 1;//k右移一位
		a = ((a % mod) * (a % p)) % mod;//a进位 
	} 
	return res;
}

ll get(ll b){//求b由几位二进制数构成 
	ll res = 0;
	while(b){
		if(b & 1) res ++ ;
		b  = b >> 1;
	}
	return res;
}

int main()
{
	scanf("%lld%lld", &n, &m);
	
	ll ci = get(m);
	
	cout<<qmi(n, ci, mod)<<endl;
	
	return 0;
}

I 小沙的构造

原题传送门

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值