2024寒假牛客竞赛

文章探讨了图论中的最短路径问题,涉及不同边权计算方法、树上剪枝操作以减少路径长度,以及动态规划在通知问题和子区间和求解中的应用,强调了贪心策略在特定情境下的局限性。
摘要由CSDN通过智能技术生成

Tokitsukaze and Short Path (plus)

本题给了一个图论背景,但是它并不是一个图论题

首先看到有绝对值的式子,一定要把绝对值拆掉

当au > av时, au + av + au - av = 2au

当au < av时,au + av + av - au = 2av 

所以,边权是两个点中较大的权值 * 2

A 到 B 的距离等于 2max(A, B),假如我们绕C到B,也就是A 到 C 到 B的距离为2max(A, C)+ 2max(B,C),所以绕的距离一定比不饶的距离要大。

所有的点权值都是正的,所以最短路就不会发生绕路

最后的ac代码

Tokitsukaze and Short Path (minus)

本题和上题只是边权的计算方式不同,继续拆绝对值

当au > av时, au + av - au + av = 2av

当au < av时,au + av - av + au = 2au

所以,边权是两个点中较小的权值 * 2

A 到 B 的距离等于 2min(A, B),假如我们绕C到B,也就是A 到 C 到 B的距离为2min(A, C)+ 2min(B,C), 如果4C 比 2 min(A,B)小,绕路是划算的(假如我们再绕一个点D,那么就是2min(A, C)+ 2min(C, D) + 2min(B,C),这个值一定比只绕一个点大,所以我们只绕一个点)

最后的ac代码

soyorin的树上剪切

注意:本题对于边(u,v)是采取剪切操作,但是并没有删除!所以边(u,v)依然存在。

以下为本题的解释:

然后我们对 v 节点和 u 节点进行剪切操作

因此,我们可以分为两种情况

        情况一,t 节点与 s 节点不相连,那么我们就一定可以通过一些次数的剪切操作,让 t 和 s 相连,从而减少 t 和 s 之间的路径距离。(也就是以上的情况)

         情况二,当 t 节点和 s 节点相连时,那我们依然可以通过两次剪切操作来改变 t 和 s 之间的路径距离,以下为演示:

先对 s 节点和 x 节点进行剪切操作,把 s 节点作为 u,x 节点作为 v,那么就会变为:

然后对 t 节点和 x 节点进行剪切操作,把 x 节点作为 u,t 节点作为 v,那么就会变为:

通过以上的两次操作,我们就可以改变 s 节点和 t 节点之间的路径距离

综上所述,通过 n 次操作求出 s 节点和 t 节点的最短路径距离:

        首先,通过dis[s,t] - 1 (dis[s, t]表示 s 节点与 t 节点之间边的条数)次操作,让 t 不断靠近 s,直至 s 节点与 t 节点相连。

        然后留下两次操作次数用于换边。

        剩下的次数用于把最小权重的边也移动到与 s 节点相连。

soyorin的通知

本题有个陷阱:就是会想到贪心,ai / bi 算出性价比最高的那个人,然后让他进行不断的通知。

但是如果 n 个人不是bi的倍数的话,那么我们到最后的时候,就会浪费钱去通知空气(这样子性价比就会很低)。

因此不能使用贪心算法。

分析:

初始消息通知可以理解成一个人,代价为ai = p 能通知到一个人(也就是bi = 1)

第一个人肯定要用(p, 1)来通知

其他人就可以被任何一个人通知

而且,只要保证用了一次(p,1),那么其他人的通知方式都可以使用了

因为我们每一次选择的人至少能通知到另外一个人,那么在用了一次(p,1)后,我们就可以选择任意一个人的通知方式,然后我们选择的这个人,也至少可以能通知到一个人,那么我们也可以选择任意一个人的通知方式,不断进行下去。

因此题目就可以变为:

每次可以以 ai 代价通知 bi 个人,问,通知 n-1 个人需要多少代价(完全背包)

设dp[i] 表示通知到 i 个人,最少的代价为dp[i]

递推公式为:dp[i] = min(dp[i - bi] + ai, dp[i]);

代码:

# include <stdio.h>
# include <string.h>

int min(int a,int b)
{
	if (a > b)
		return b;
	else
		return a;
		
}
int main()
{
	int n;
	int p;
	scanf("%d %d", &n, &p);
	int a[n+1][n+1];
	a[0][0] = 1;
	a[0][1] = p;
	for (int i=1; i<=n; ++i)
	{
		scanf("%d %d", &a[i][0], &a[i][1]);
	}
	int dp[n];
	for (int i=0; i<n; ++i)
		dp[i] = 999999;
	dp[0] = 0;
	for (int i=1; i<n; ++i)
		for (int j=0; j<=n; ++j)
		{
			if (i >= a[j][0])
			{
				dp[i] = min(dp[i], dp[i-a[j][0]] + a[j][1]);
			}
		}
	dp[n-1] = dp[n-1] + p;
	printf("%d", dp[n-1]);
	
}

守恒

假设最大公约数为x。

那么数组里的元素都可以由k1*x, k2*x, k3*x, k4*x……组成

因此若x是数组的最大公约数,则数组的每个数都得是x的倍数

执行的操作是给一个数+1,另一个-1,所有数的和不变

所以 x 要作为数组的gcd

必须得是所有数的和的gcd

代码:

# include <stdio.h>

int main()
{
	int n;
	scanf("%d", &n);
	long long a[2000001],  i, j, sum = 0, x = 0;
	for (i=0; i<n; ++i)
	{
		scanf("%d", &a[i]);
		sum = sum + a[i]; //记录所有数的和。 
	}
	for (j=1; j*n<=sum; ++j)//i的初始值不能超过cnt/n
	{
		if (sum % j == 0) 
		{
			x = x + 1;
		}
	}
    if (n == 1)
        x = 1;
	printf("%d", x);
}

时空的交织

我们发现这个数据量很大,那么我们就无法开辟一个数组去存储这个矩阵

因此,我们要找到其中的规律

这个子矩形到底是什么

下图为这个整体的矩阵

这个红色框就是子矩形

这个子矩形的和就是:

a3*b2 + a3*b3 + a3*b4 + a4*b2 + a34*b3 + a4*b4 + a5*b2 + a5*b3 + a5*b4

= a3*(b2 + b3 + b4) + a4*(b2 + b3 + b4) + a5*(b2 + b3 + b4)

=(a3 + a4 + a5)*(b2 + b3 + b4);

因此子矩形的和就是:

(a数组的最大子区间的和) * (b数组的最大子区间的和)

因此题目就变为了求a数组的最大子区间的和与b数组的最大子区间的和

采用dp的方法

dp[i] 表示第i必选的最大区间和

dp[i] = max(dp[i-1] + a[i], a[i]);

因为 ai 和 bi 有可能是负数

所以我们还要求最小区间和的乘积

代码:

# include <stdio.h>
# include <string.h>
int min(int a, int b)
{
	if (a > b)
		return b;
	else
		return a;
}

int max(int a, int b)
{
	if (a > b)
		return a;
	else
		return b;
}

int main()
{
	int n;
	int m;
	scanf("%d %d", &n, &m);
	int a[n];
	int b[m];
	for (int i=0; i<n; ++i)
		scanf("%d", &a[i]);
	for (int i=0; i<m; ++i)
		scanf("%d", &b[i]);
	
	int maxdpa[n];
	int maxdpb[m];
	int mindpa[n];
	int mindpb[m];
	int maxa = -99999;
	int maxb = -99999;
	int mina = 99999;
	int minb = 99999;
	for (int i=0; i<n; ++i)
		mindpa[i] = 99999;
	for (int i=0; i<m; ++i)
		mindpb[i] = 99999;
	maxdpa[0] = a[0];
	mindpa[0] = a[0];
	maxdpb[0] = b[0];
	mindpb[0] = b[0];
	for (int i=1; i<n; ++i)
	{
		maxdpa[i] = max(maxdpa[i-1]+a[i], a[i]);
		mindpa[i] = min(mindpa[i-1]+a[i], a[i]);
		if (maxa <maxdpa[i])
			maxa = maxdpa[i];
		if (mina > mindpa[i])
			mina = mindpa[i];
	}
	for (int i=1; i<m; ++i)
	{
		maxdpb[i] = max(maxdpb[i-1]+b[i], b[i]);
		mindpb[i] = min(maxdpb[i-1]+b[i], b[i]);
		if (maxb <maxdpb[i])
			maxb = maxdpb[i];
		if (minb > mindpb[i])
			minb = mindpb[i];		
	}
	if (maxa*maxb > mina*minb)
		printf("%d", maxa*maxb);
	else
		printf("%d", mina*minb);
	
}

        

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值