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);
}