河南萌新联赛2024第(五)场:信息工程大学

这几天生病了吃药比较嗜睡,迟来的更新!

AAAAA

#include<bits/stdc++.h>
using namespace std;
int main(){
    int T;
    cin>>T;
    int x,y,z;
    while(T--){
        cin>>x>>y>>z;
        if(((y==9||y==11)&&z==30)||(y+z)%2==0) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
}

疑问解答一:这样的操作是非法的,我们不允许这样做。题目没有对非法之后该怎么办做出解释,

 下面我们来看一张图。

这是有规律可寻得,日期变化也是有规律的,在9,11月这两天总是出错,无论是闰年还是平年,9,11月都是30天,只要保证这两个日期是是正确的,就可以合法继续操作,让Alice 保持“先手”的状态,因为操作的周期偶数2,只要让操作次数为偶数即可保证Alice胜出。

BBBBB

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    ll n;
    cin>>n;
    vector<ll> a(n+1);
    ll i,j;
    ll sum=0;
    for(i=1;i<=n;i++){
        cin>>a[i];
        sum+=a[i];
    }
    ll l,r;
    cin>>l>>r;
    ll sum1=0,sum2=0;
    if(sum>r*n||sum<n*l){
//因为sum=n*所有数的平均数,所以这步的意思就是判断所有a[i]是否都大于r,或者小于l
        cout<<-1;
    }else{
        for(i=1;i<=n;i++){
            if(a[i]<l) sum1+=(l-a[i]);
            else if(a[i]>r) sum2+=(a[i]-r);
//在l,r之间的是不会受到影响
        }
        cout<<max(sum1,sum2);
//sum1,sum2其中最大的那个是因为,下面有详解
        return 0;
    }
}

疑问一:max(sum1,sum2)什么意思呢?

你想啊,一堆数字(a[i])由小到大排成一列,一个数,如果大于l,小于r,不会受到改变,(l-a[i])的同时,最右边的数也在同时减,因为题干中给出的操作是,你在对一个数减1的同时,也要对另一个数加1,sum1,sum2,其实是两个独立的操作结果,sum1本身已经包含了左边的数减,右边的数加,sum2也是,如果你还不明白,现在我们来举个例子:

l=2,r=4

1, 2, 3,  4, 5,   6,sum1操作如下:

1+1,2, 3, 4,  5,   6-1

2     2     3     4     5      5(可以看出此时所有数字并没有完全进入【l,r】)

1,  2,   3,  4, 5,       6,sum2操作如下

1+2, 2+2  3,  4, 5-(6-4) ,6-(6-4)为了让6变成4,6进行两次自减1,同时左边的数进行了两次自加1

2      3      3      4    3         4(此时所有数字全部进入【l,r】)

在上面例子的同时也告诉了我们为什么选择sum1,sum2其中最大的那个数。

疑问二:常规写法可以用双指针while(left<right)然后再求操作次数不就好了?

这种确实是大部分小白可能第一时间想到的,但是其中可能的情况太多了,以至于枚举了很多种情况仍然不能100%通过,而且双层while循环式代码变得冗长低效,多花了时间也没有什么太大的收获,不如换一下思路解题,All roads lead to Rome(条条大路通罗马)。

CCCCC 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll fa[200005];  // 全局数组,用于并查集
struct memory {
	ll a, b, c;
};

// 并查集的查找函数,带路径压缩
ll find(ll x) {
	if (x == fa[x])
		return x;
	else {
		fa[x] = find(fa[x]);
		return fa[x];
	}
}

// 并查集的合并函数
void unionn(ll i, ll j) {
	ll i_fa = find(i);
	ll j_fa = find(j);
	if (i_fa != j_fa) {
		fa[i_fa] = j_fa;
	}
}

int main() {
	ll n, m;
	cin >> n >> m;
	
	memory meo[m];
	for (ll i = 0; i < m; i++) cin >> meo[i].a >> meo[i].b >> meo[i].c;
	
	// 初始化并查集,每个节点的父节点是它自身
	for (ll i = 1; i <= 2 * n; i++) fa[i] = i;
	
	// 使用 std::sort 对结构体数组按 c 成员降序排序
//[](memory &x, memory &y) 是一个 C++ 中的 Lambda 表达式 的一部分。它是用来定义一个匿名函数(即没有名字的函数)
	sort(meo, meo + m, [](memory &x, memory &y) {
		return x.c > y.c;
	});
	
	// 处理每个记忆对之间的冲突
	for (ll i = 0; i < m; i++) {
		ll a = meo[i].a, b = meo[i].b, c = meo[i].c;
		
		if (find(a) == find(b)) {  // 如果a和b的根节点相同,说明冲突发生
			cout << c << endl;
			return 0;
		}
		
		// 将a的对立面与b合并,将b的对立面与a合并
		unionn(a, b + n);
		unionn(b, a + n);
	}
	
	return 0;
}

 这个代码如果写简单点的话就是

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
ll fa[N];
struct memory{
	ll a,b,c;
}meo[N];
bool cmp(memory x,memory y)
{
	return x.c>y.c;
}
ll find(ll x)
{
	return fa[x]==x ? x : (fa[x]=find(fa[x]));
}
int main()
{
	ll n,m;cin>>n>>m;
	for(ll i=1;i<=2*n;i++)	fa[i]=i;
	for(ll i=1;i<=m;i++)
		cin>>meo[i].a>>meo[i].b>>meo[i].c;
	sort(meo+1,meo+m+1,cmp);//按cmp的比较方式,从大到小排序
	for(ll i=1;i<=m;i++)
	{
		ll x=find(meo[i].a);
		ll y=find(meo[i].b);
		if(x==y)
		{
			cout<<meo[i].c;
			break;
		}
		fa[find(meo[i].a+n)]=y;
		fa[find(meo[i].b+n)]=x;
	}
	return 0;
}

这是一道并查集算法题,当然也可以用DFS,下面是DFS写的代码,两种方法其实差异不是很大,自行选择。

这段代码实现了一个通过二分搜索和深度优先搜索来查找使得图无法成为二分图的最小边权值的算法。核心思想是对边按权值降序排序后,逐步增加边的数量,并在每次增加后检查当前的图是否还能保持为二分图。最终找出使得图不再是二分图的那条边的权值,并输出。

 p:定义了数组的大小(即 200010),为程序中各类数组预留了足够的空间。

q[p], w[p], e[p]:这些数组用于存储图的边信息。

q 存储的是边的目标节点,w 存储的是下一条边的索引,e 是邻接表的头指针数组

idx:用于记录边的当前索引,初始值为 1。

zong:一个标志变量,用来标识当前图是否符合题目要求,初始值为 1。

a, s:a 表示节点数,s 表示边数。

u[p]:用于存储每个节点的状态(用于二分图的染色问题)。

struct aqq:定义了一个结构体 aqq,用于存储每条边的信息,包含了三个成员:l(左端点),r(右端点),v(权值)。 

#include<bits/stdc++.h>
using namespace std;
const int p=2e5+10;
int q[p],w[p],e[p],idx=1,zong=1;
int a,s;
int u[p];
struct aqq
{
	int l,r,v;
}t[p];
bool cmp(aqq aa,aqq ss)//aa 和 ss 分别是边的两个端点
{
	return aa.v>ss.v;
}
void aqq(int aa,int ss)//aqq:用于向邻接表中添加边
{
	q[idx]=ss;
	w[idx]=e[aa];
	e[aa]=idx++;
}
void dfs(int xx)//尝试将图染成二分图。xx 是当前节点。
{
	for(int i=e[xx];i;i=w[i])
	{
		int aa=q[i];
		if(u[aa]==0)//表示节点 aa 尚未访问过,将其染成与 xx 相反的颜色,并继续对其进行 DFS。
		{
			u[aa]=3-u[xx];
			dfs(aa);
		}
		else if(u[aa]==u[xx])//说明图中存在一个奇数环,无法将图染成二分图,因此设置 zong = 0 表示失败。
		{
			zong=0;
			return;
		}
	}
}
int check(int xx)//用于检查当前选取的前 xx 条边能否构成一个合法的二分图
{
	zong=1;
	for(int i=1;i<=a;i++)e[i]=0,u[i]=0;
	idx=1;
	for(int i=1;i<=xx;i++)
	{
		aqq(t[i].l,t[i].r);
		aqq(t[i].r,t[i].l);
	}
	for(int i=1;i<=xx;i++)
	{
		if(u[t[i].l]==0)u[t[i].l]=1,dfs(t[i].l);
		if(u[t[i].r]==0)u[t[i].r]=1,dfs(t[i].r);
	}
	return zong;
}
signed main()
{
	cin>>a>>s;
	for(int i=1;i<=s;i++)
	{
		cin>>t[i].l>>t[i].r>>t[i].v;
	}
	sort(t+1,t+s+1,cmp);
	int le=1,ri=s;
	while(le<=ri)//使用二分搜索查找使得图无法染成二分图的最小权值
	{
		int mid=le+ri>>1;
		if(check(mid))le=mid+1;//检查前 mid 条边能否构成一个合法的二分图。
		else ri=mid-1;//如果合法,说明当前图还未出现冲突,移动左边界;否则移动右边界。
	}
	cout<<t[le].v<<endl;
	return 0;
}

DDDDD 

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;

int main() {
    ll n;
    cin >> n;
    vector<ll> a(n);
    for(ll i = 0; i < n; i++) cin >> a[i];
    
    ll q;
    cin >> q;
    
    for(ll i = 0; i < q; i++) {
        int c;
        cin >> c;
        
        if(c == 1) {
            ll L, R, d;
            cin >> L >> R >> d;
            // 注意L和R是1-based index,需要转换为0-based
            for(ll j = L - 1; j <= R - 1; j++) {
                a[j] += d;
            }
            continue;
        } 
        else if(c == 2) {
            ll x;
            cin >> x;
            // 注意x是1-based index,需要转换为0-based
            cout << a[x - 1] << endl;
            continue;
        }
    }
    
    return 0;
}

EEEEE 

 在这里我写的代码使用了埃拉托色尼筛法,其使用原始的判断一个数是否是质数也可以。

#include<bits/stdc++.h>
using namespace std;
const int N=50000;
vector<bool> primes(N + 1, true);  // 初始化一个布尔数组,表示所有数都是质数
void init() {
	primes[0] = primes[1] = false;   // 0 和 1 不是质数
	for (int i = 2; i * i <= N; i++) {   // 遍历到 sqrt(n) 即可
		if (primes[i]) {
			for (int j = i * i; j <= N; j += i) {
				primes[j] = false;     // 将 i 的倍数标记为非质数
			}
		}
	}
}

int main(){
	int t;
	cin >> t;
	init();
	while(t--){
		int n,i,j,k;
		bool found=false;
		cin>>n;
		for(i=2;i<=n;i++){
			for(j=2;j<=n-j;j++){
				if(primes[i]&&primes[j]&&primes[n-i-j]){
					cout<<i<<" "<<j<<" "<<(n-i-j)<<endl;
					found=true;
					break;
				}
			}
			if(found) break;
		}
		if(!found)
			cout<<"-1"<<endl;
	}
	return 0;
}

 等价代码:

#include <bits/stdc++.h>
using namespace std;
bool isprime(int a){
	for(int i=2;i*i<=a;i++){
		if(a%i==0)return false;
	}
	return true;
}
void solve(){
    int a; cin>>a;
    for(int j=2;j<=a;j++){
		for(int i=2;i<=a-i;i++){
    		if(isprime(j) and isprime(i) and isprime(a-i-j)){
    			cout<<j<<" "<<i<<" "<<a-i-j;
                cout<<endl;
				return;	
			}
		}
	}
}
signed main(){
    int t;
    cin>>t;
    while(t--)
        solve();
}

内层循环为什么是i<=a,而不是i<=a-j有以下解释 。

a 是输入的目标奇数,i 是我们要测试的第二个质数。这个循环条件 i <= a - i 的含义是:在保证三个数的和等于 a 的前提下,尽量让 i 和 a - i - j 保持在合理的范围内。

确保组合合理性: 当我们固定 j(第一个质数)时,内层循环遍历 i(第二个质数)。为了让 i 和 a - i - j 在组合上合理,确保 i 不超过 a - i 是很重要的。这样可以保证 a - i - j 不会变得太小,从而确保组合更平衡。

避免冗余: 如果我们只使用 i <= a - j,那么可能会让 i 的范围过大,导致我们生成冗余的组合。例如,i 和 a - i - j 的值可能会对调,产生相同的质数组合。这种重复组合是我们不希望的。

说直接点就是,比如n=15,输出的j=2,i=2,  n-i-j=11,  i<=(11-2),防止i为大于2的质数,因为题干要求了,如果输出的第一个质数最小的情况不唯一时,就要保证输出的第二个质数( 在满足三个质数和为n的情况下)也最小。

FFFFF

#include<bits/stdc++.h>
using namespace std;

using u64 = unsigned long long;
#define int long long

const int N=1e6+10;
vector<pair<int,int>> a[N],b[N];
//a[N]正向图邻接表,b[N],反向图邻接表
int n,m;
int vis[N],dis[N];
int ans=0,tot=2;
//ans,记录最小距离,tot,用来标记不同的BFS,下面有详解
void bfs1(){//从节点1开始,按照最小花费的路径扩展所有可达节点。
	priority_queue<pair<int,int>> q;
	q.push({0,1});
	while(q.size()){
		auto [d,u] = q.top();
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		ans+=-d;
		for(auto [v,di]:a[u]){
			q.push({d-di,v});
		}
	}
}
void bfs2(){//bfs1() 类似,但是是在反向图上进行的。
	priority_queue<pair<int,int>> q;
	q.push({0,1});
	while(q.size()){
		auto [d,u] = q.top();//结构化绑定
//相当于 auto p=q.top();
//d=p.first();u=p.second();
		q.pop();
		if(vis[u]==tot) continue;
		vis[u]=tot;
		ans+=-d;//下面有详解
		for(auto [v,di]:b[u]){
			q.push({d-di,v});
		}
	}
}
void solve(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,d;
		cin>>u>>v>>d;
		a[u].push_back({v,d});
		b[v].push_back({u,d});
	}
	
	bfs1();
	bfs2();
	cout<<ans<<'\n';
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	
	int T=1;
	// cin>>T;
	
	while(T--){
		solve();
	}
	
	return 0;
}

1.只可以用优先队列吗?

可以不用优先队列,改用基础的 queue 来实现 BFS (广度优先搜索) 版本的最短路径算法。但是要注意,传统的 BFS 适用于无权图或者所有边的权重相同的图(即每条边的权重为1)。对于一般的有权图,如果边权不同,Dijkstra 算法的使用是必要的,并且优先队列是实现该算法的关键。 不过,如果你坚持使用 queue 来实现一个非优先级的 BFS,只能处理边权相同(如全为1)的情形,或者将问题简化为无权图。但是在计算最短路径时,从起点 1 到节点 u 的最短距离。由于这里的 BFS 是顺序搜索,所以不能保证得到的是最短路径。

2.ans+=-d,为什么是-d? 

3.tot的用处 。

tot 用于标记节点是否已经在第二次 BFS 中被访问过。

详细解释 在代码中,vis[u] 是一个访问标记数组,vis[u] 等于 1 表示节点 u 在第一次 BFS 中已经被访问过了。 tot 的初始值是 2,这意味着在第二次 BFS 中,如果 vis[u] 等于 2,那么这个节点在第二次 BFS 中已经被访问过。tot 主要起到区分两次 BFS 的作用。第一次 BFS 标记访问过的节点为 1,第二次 BFS 则标记访问过的节点为 2。这样可以确保两次 BFS 的标记不会混淆,从而正确计算两次不同方向的最小路径。

这个题也可以用DP思路求解:

基于 Floyd-Warshall 算法的最短路径计算,用于在一个有向图中计算从起点节点 1 出发,访问所有其他节点并返回的最小路径总和。

#include<bits/stdc++.h>
using namespace std;
const int maxx=1e6;
int dp[1005][1005];//二维数组 dp,用于存储节点之间的最短路径距离
void floyd(int n){
	for(int t=1;t<=n;t++)//t:枚举中间节点,尝试通过节点 t 来更新节点 i 到节点 j 之间的最短路径。
		for(int i=1;i<=n;i++){
	//i 和 j:遍历所有节点对 (i, j),检查是否通过 t 作为中间节点可以更新当前的最短路径 dp[i][j]。
			for(int j=1;j<=n;j++){
				dp[i][j]=min(dp[i][j],dp[i][t]+dp[t][j]);
			}
		}
	
}
int main(){
	memset(dp,0x3f,sizeof(dp));
//memset(dp,0x3f,sizeof(dp));:将 dp 数组初始化为一个非常大的值(实际上是 0x3f3f3f3f,即 1061109567)
	//,代表节点之间的初始距离是无穷大(假设不可达)
	int n,m;
	cin>>n>>m;
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		dp[a][b]=c;
	}
	floyd(n);
	long long int sum=0;
	for(int i=2;i<=n;i++){//i=1 是起点,不需要计算它到自身的距离。
		sum+=dp[1][i]+dp[i][1];
	//dp[1][i] 表示从节点 1 到节点 i 的最短路径,dp[i][1] 表示从节点 i 返回到节点 1 的最短路径。
	}
	cout<<sum<<endl;
}

GGGGG

#include<bits/stdc++.h>
using namespace std;
int n;
long long dp[1000086]={0};
int main()
{
	cin>>n;
	dp[1]=1;
	dp[2]=2;
	dp[3]=4;
	for(int i=4;i<=n;i++){
		dp[i]=(dp[i-1]+dp[i-2]+dp[i-3])%1000000007;
	}
	cout<<dp[n]%1000000007;
}

 HHHHH

这道题很容易写超时,由于数据规模较大,需要使用高效的算法来处理,这里通过创建一个稀疏表(Sparse Table)来解决。

 

#include<bits/stdc++.h>
using namespace std;
int a[1000005];
int f[1000005][30];
int main() {
	ios::sync_with_stdio(0);cout.tie(0),cin.tie(0);
	int n,q;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>f[i][0];
	for(int j=1;j<=30;j++) {
		for(int i=1;i+(1<<j)-1<=n;i++) {
			f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
		}
	}
	cin>>q;
	while(q--) {
		int l,r;
		cin>>l>>r;
		int k=log(r-l+1)/log(2);
		cout<<max(f[l][k],f[r-(1<<k)+1][k])<<"\n";
	}
	return 0;
}

 1.为什么j<=30?

2.如何创建一个稀疏表 

 

 

3.k的创建什么意思 

IIIII 

 这题解题关键就是找中位数就行了。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;

int main() {
    ll n;
    cin >> n;
    vector<ll> c(n);
    ll sum=0;
    for (ll i = 0; i < n; i++) {
        cin >> c[i];
        sum+=c[i];
    }

    // 先排序,找到中位数
    sort(c.begin(), c.end());
    ll a = c[n / 2];

    // 计算使所有分数都变为 a 所需的最小魔力值
    ll total_magic_power = 0;
    for (ll i = 0; i < n; i++) {
        total_magic_power += abs(c[i] - a);
    }
    total_magic_power+=1;
    if(sum>total_magic_power)
    cout << total_magic_power << endl;
    else cout<<sum<<endl;
    return 0;
}

 JJJJJ

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int main(){
    ll t;
    cin>>t;
    ll i;
    for(i=1;i<=t;i++){
        ll n;
        cin>>n;
        ll a;
        a=(ll)sqrt(n);
        if((a-(ll)a)>=0&&(a-(ll)a)<0.5)
        cout<<a<<endl;
        else if((a-(ll)a)>=0.5)
            cout<<(a+1)<<endl;
    }
    return 0;
}

KKKKK  

 

#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, m, start, target;
vector<pair<int, int>> g[N];
int vi[N];//记录从起点到该节点的路径上遇到的最大波浪伤害值
int ans = INT_MAX;
//INT_MAX 是一个常量,定义在 C++ 的标准库中,表示 int 类型能够表示的最大整数值
//它的值通常是 2147483647,这是 32 位有符号整数能表示的最大值。
void DFS(int u, int mx) {
	if(u == target) {
		ans = min(ans, mx);
		return;
	}
	if(vi[u] <= mx) return;
	vi[u] = mx;
	for(auto [next_node, weight] : g[u]) {
		DFS(next_node, max(weight, mx));
	}
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; i++) vi[i] = INT_MAX;
	while(m--) {
		int u, v, a;
		cin >> u >> v >> a;
		g[u].push_back({v, a});
		g[v].push_back({u, a});
	}
	cin >> start >> target;
	int mx = 0;
	DFS(start, mx);
	cout << ans;
	return 0;
}

1.vi[N]的作用 

vi[i] 的主要作用是:

1.记录路径中最大的波浪伤害值:vi[i] 保存的是从起始节点 p 到达节点 i 的某条路径上,所有波浪伤害值中的最大值。

2. 防止重复访问和剪枝:在深度优先搜索(DFS)的过程中,如果一个节点 i 被访问过,并且在该路径上的最大波浪伤害值已经小于或等于当前访问路径的最大波浪伤害值,则不需要继续沿这条路径搜索(即剪枝操作)。

3.作用总结 :vi[10010] 是一种辅助数组,用于优化 DFS 搜索的效率,通过记录每个节点在路径中的最大波浪伤害值,避免重复计算不必要的路径,从而减少时间复杂度,提升算法效率。具体来说,它帮助实现了剪枝操作,使得搜索更加高效。

Thank you for your watching ! Good night !

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值