#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
//根本就不是dp
using namespace std;
const int N = 10010,inf=0x3f3f3f3f;
int n;
int a[N],f[N];
bool is_prime(int x)//质数的判断
{
for (int i = 2; i < x / i; ++ i )
if (x % i == 0)
return false;
return true;
}
int d_prime(int x)//遍历所有因子数
{
for (int i = 2; i <= x; ++ i )
if (x % i == 0 && is_prime(i))//为x的因子同时为质数,由每次于从2开始便得到最小因子
return i;
return 1;
}
int main() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
memset(f,-inf,sizeof(f));//初始化
f[1]=a[1];
for(int i=1;i<=n;i++){
int j=d_prime(n-i);//从当前位置所能跳到的最远位置i——i+1到i+d_prime(n-i)
for(int k=i+1;k<=i+j;k++){ //遍历所有的可能跳到的位置
if(f[k] == -inf) f[k] = a[k] + f[i];//如果之前为经过,便直接f[i]+a[k];
else f[k]=max(f[k],f[i]+a[k]);
}
}
cout<<f[n];
return 0;
}
这段代码是一个动态规划的例子,用于解决跳跃游戏问题。该问题描述为:给定一个长度为n的数组a,每个元素代表从当前位置最多能向后跳的步数,求从第一个位置开始,最少需要跳几次能够跳到最后一个位置。
该问题可以使用动态规划的思想来解决。具体来说,我们可以定义状态f[i]表示从第一个位置跳到第i个位置所需的最少跳跃次数。则状态转移方程为:
f[i] = min{f[j]} + 1 (i-j<=a[j])
其中,j是i之前的位置,a[j]是从j位置最多能向后跳的步数。这个状态转移方程的意思是,我们从i之前的位置j跳到i位置,需要跳跃的次数为f[j]+1。而j的取值范围是i之前的所有位置中,能够跳到i位置的位置。也就是说,如果j位置能够跳到i位置,那么从j位置跳到i位置所需的跳跃次数就是f[j]+1。
在这段代码中,我们可以看到,状态f[i]的定义与上述状态定义略有不同,它表示从第一个位置跳到第i个位置所能获得的最大分数。而状态转移方程也有所不同,它是:
f[k] = max{f[i]+a[k]} (i+1<=k<=i+d_prime(n-i))
其中,d_prime(n-i)表示从第i个位置最多能向后跳的步数。这个状态转移方程的意思是,我们从i位置跳到k位置,所能获得的最大分数是f[i]+a[k]。而k的取值范围是i+1到i+d_prime(n-i),也就是说,我们只能跳到i之后的位置,并且最多只能跳d_prime(n-i)步。
最后,我们输出f[n]即可,它表示从第一个位置跳到最后一个位置所能获得的最大分数。
环境治理
//多源最短路径Floyd算法+二分答案
//核心在于用二分法枚举所有可能的方案(改善天数)
//改善mid天,相当于改善完整的mid/n轮+mid%n天,编号为1~mid%n的结点的灰尘度可以多减1
//填写dist数组后跑一遍Floyd算法验证当前P值是否小于等于Q,继续二分即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=120;
ll n,Q;
ll graph[N][N];//图
ll dist[N][N];//dist[i][j]存储i->j的最短距离
ll limit[N][N];//limit[i][j]存储i->j的灰尘度下限
ll Floyd()//Floyd算法求任意两点间的最短路径,返回所有最短路径的和(当前的P值)
{
ll res=0;
for(int k=1;k<=n;k++)//模板
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)res+=dist[i][j];
}
return res;
}
bool check(ll mid)//验证改善mid天,能否使得灰尘度小于等于Q
{
memcpy(dist,graph,sizeof(graph));//将原图复制给dist(细节,若本次check为false,下次还要使用旧图)
ll circle=mid/n;//改善mid天,相当于改善了完整的circle轮
ll mod=mid%n;//还剩下mod天,给1~mod号点单独改善
for(int i=1;i<=n;i++)//枚举所有的结点对(i,j)
{
for(int j=1;j<=n;j++)
{
if(i==j)continue;//不是道路
//若i与j不重合,存在i->j的边
//若i的编号小于等于mod,说明改善了circle轮后还能单独改善一天,
//故灰尘度为dist[i][j]-circle-1,但不能小于limit[i][j]
if(i<=mod)dist[i][j]=max(limit[i][j],dist[i][j]-circle-1);
//若i的编号超出mod,说明只能恰好改善circle轮,灰尘度为dist[i][j]-circle
else dist[i][j]=max(limit[i][j],dist[i][j]-circle);
dist[j][i]=dist[i][j];//双向道路,j->i的灰尘度也随之改善
}
}
ll d=Floyd();//对于更新后的dist数组,重新跑Floyd算法并求解当前P值
return d<=Q;//P值不大于Q,说明该方案可行
}
int main()
{
scanf("%lld%lld",&n,&Q);
for(int i=1;i<=n;i++)//输入i*j条道路的灰尘度(邻接矩阵存储)
{
for(int j=1;j<=n;j++)
{
scanf("%lld",&graph[i][j]);
}
}
for(int i=1;i<=n;i++)//输入每条道路的灰尘度下限值
{
for(int j=1;j<=n;j++)
{
scanf("%lld",&limit[i][j]);
dist[i][j]=limit[i][j];
}
}
if(Floyd()>Q)//用下限limit先跑一遍Floyd算法,若P值已经大于Q,说明根本无法改善
{
printf("-1\n");
return 0;
}
ll left=0,right=1e9+1;//开始二分
while(left<=right)
{
ll mid=(left+right)>>1;
if(check(mid))right=mid-1;//改善mid天可行,则缩小右边界继续试探
else left=mid+1;//不可行,扩大左边界
}
printf("%lld\n",left);//输出即可
return 0;
}
这是一篇关于多源最短路径Floyd算法和二分答案的题解。Floyd算法是一种求任意两点间最短路径的算法,时间复杂度为O(n3),适用于稠密图。二分答案则是一种常用的解决最优化问题的方法,时间复杂度为O(logn),适用于单调性问题。
这段代码的主要思路是:给定一个图,每条边有一个灰尘度,每条边还有一个灰尘度下限,现在要求改善若干天,每天可以使得编号为11到mid%n的点的灰尘度减11,或者使得所有点的灰尘度减11,问最少需要改善多少天才能使得所有边的灰尘度都不大于其下限。
具体实现上,我们可以用二分法枚举改善天数mid,然后对于每个mid,我们可以用Floyd算法求出改善mid天后的所有点对之间的最短距离,然后判断是否所有边的灰尘度都不大于其下限。如果是,则说明mid天的改善方案可行,我们可以缩小右边界继续试探;否则,我们需要扩大左边界。