设
d
p
(
i
,
j
)
dp(i,j)
dp(i,j)表示从前
i
i
i个物品中选,目前已用总资金为
j
j
j万元时,可以达到的最大效益。并设
v
i
,
j
v_{i,j}
vi,j表示第
i
i
i个项目投入
j
j
j万元时,可以获得的效益。则有状态转移方程
d
p
i
,
j
=
max
0
≤
k
≤
j
{
d
p
i
−
1
,
j
−
k
+
v
i
,
k
}
dp_{i,j}=\max\limits_{0\le k\le j}\{dp_{i-1,j-k}+v_{i,k}\}
dpi,j=0≤k≤jmax{dpi−1,j−k+vi,k}。
此时,时间复杂度和空间复杂度为
O
(
N
V
)
O(NV)
O(NV),其中
N
N
N为项目的数量,
V
V
V为总资金。
可以用滚动数组优化空间复杂度为
O
(
V
)
O(V)
O(V)。
代码(无滚动数组优化版)
# 每个数据之间用空格隔开
# 输入总项目数和总资金
N,V=[int(e)for e ininput().split()]
v=[[0for j inrange(V+1)]for i inrange(N+1)]
dp=[[0for j inrange(V+1)]for i inrange(N+1)]
# 第 i 行依次输入投入资金为 j 时的收益
for i inrange(1,N+1):
v[i]=[0]+[int(e)for e ininput().split()]
# 动态规划(无滚动数组)
for i inrange(1,N+1):for j inrange(1,V+1):for k inrange(j+1):
dp[i][j]=max(dp[i][j],dp[i-1][j-k]+v[i][k])
# 输出答案
print(dp[N][V])
# 测试用例
'''
input
33152840132943113035
output
45'''
代码(滚动数组优化版)
# 每个数据之间用空格隔开
# 输入总项目数和总资金
N,V=[int(e)for e ininput().split()]
v=[[0for j inrange(V+1)]for i inrange(N+1)]
dp=[0for j inrange(V+1)]
# 第 i 行依次输入投入资金为 j 时的收益
for i inrange(1,N+1):
v[i]=[0]+[int(e)for e ininput().split()]
# 动态规划(滚动数组)
for i inrange(1,N+1):
# 这里 j 一定要倒序遍历
for j inrange(V,0,-1):for k inrange(j+1):
dp[j]=max(dp[j],dp[j-k]+v[i][k])
# 输出答案
print(dp[V])
# 测试用例
'''
input
33152840132943113035
output
45'''
2T. 交通网最短路问题
思路
易发现,所有边权均为正边权,故采用堆优化的dijkstra算法。
对于无向边,我们可以进行在两点之间建两条有向边。例如,假设
u
u
u与
v
v
v之间有一条无向边,则从
u
u
u向
v
v
v建一条有向边,再从
v
v
v与
u
u
u建一条有向边。
对于第(2)问的寻找路径,可以用
f
a
i
fa_i
fai去维护从
V
1
V_1
V1出发到点
V
i
V_i
Vi的最短路径中直接与
i
i
i相邻的节点(即
i
i
i的前继节点)。然后从终点一直向其前继节点回溯,直到回溯到终点为止,最后倒序输出刚才参与回溯的点即可。注:此时仅为寻找一条最短路径,当最短路径有多条时,可以用vector来存
V
i
V_i
Vi所有的前继节点,用深度优先搜索输出所有的解即可。
时间复杂度为
O
(
(
n
+
m
)
log
(
n
+
m
)
)
O((n+m)\log (n+m))
O((n+m)log(n+m)),其中
n
n
n为节点数,
m
m
m为边数。
代码
#include <bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;const int maxn=1e5+100;const int maxm=5e5+100;
int n,m,s;// 链式前向星存图
int head[maxn],cnt;
struct edge
{
int v,next;
double w;}e[maxm<<1];voidadd(int u,int v,double w){
e[++cnt]=(edge){v,head[u],w};
head[u]=cnt;}
struct node
{
int pos;
double dist;
bool operator <(node a)const{return a.dist<dist;}};
priority_queue<node> q,emptyi;
int fa[maxn];bool vis[maxn];
double dis[maxn];voiddijkstra(){// 初始化 fa_i 为 ifor(int i=1;i<=n;i++) fa[i]=i;// dijkstra 算法for(int i=1;i<=n;i++)
dis[i]=2e9;
dis[s]=0; q.push((node){s,0});while(!q.empty()){
int u=q.top().pos; q.pop();if(vis[u])continue;
vis[u]=true;for(int i=head[u];i;i=e[i].next){
int v=e[i].v; double w=e[i].w;if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
fa[v]=u;if(!vis[v]) q.push((node){v,dis[v]});}}}// 从左到右依次输出,起点到编号为 i 的点的最短距离。for(int i=1;i<=n;i++)printf("%lf ",dis[i]);printf("\n");// 输入所要寻找路径的终点
int st[maxn],p,cur_point;scanf("%d",&cur_point);// 若起点到其的最短距离为INF(即无穷大,本题设为 2e9),// 则说明无起点与之不可通达,故输出 No solution.if(dis[cur_point]==2e9)printf("No solution");else{while(cur_point!=fa[cur_point])
st[++p]=cur_point,cur_point=fa[cur_point];
st[++p]=cur_point;// 倒序输出参与回溯的节点。for(int i=p;i>=1;i--)printf("%d ",st[i]);}}
int main(){// 输入总点数,总边数和起点。scanf("%d%d%d",&n,&m,&s);for(int i=1;i<=m;i++){
int u,v,tp; double w;//输入两点的编号,边权,以及边的类型。// 当 tp 为 0 时,为u->v的有向边;tp 为 1 时,为无向边。scanf("%d%d%lf%d",&u,&v,&w,&tp);if(tp==0)add(u,v,w);if(tp==1)add(u,v,w),add(v,u,w);}dijkstra();return0;}// 测试样例,即试卷问题/*
input
9 13 1
1 2 3 0
1 4 4 0
2 3 3 0
2 6 3 0
2 5 2 0
5 6 3 0
4 7 3 0
6 7 1 1
3 9 5 0
6 9 2.5 0
7 9 2 0
7 8 2 0
8 9 4 0
9
output
0.000000 3.000000 6.000000 4.000000 5.000000 6.000000 7.000000 9.000000 8.500000
1 2 6 9
*/
3T. 对伪代码进行分析
# 设 P,W,C 分别为二维数组 w,c,p 存储的矩阵。
# 且满足 W的(i,j) 元与 w[i,j] 相等;C 与 P 同理。
# 该函数主要实现用二维数组 p 存储矩阵 P,并返回 p。其中 P=WC。
MatMult(p[n,n],w[n,n],c[n,n]){
# 矩阵乘法的对应法则。
for i <-0 to n-1dofor j <-0 to n-1do{
p[i,j]=0.0;for k <-0 to n-1do
p[i,j]=p[i,j]+w[i,k]*c[k,j]}return p;}
4T. 蒙特卡罗法估计积分的近似值
思路
蒙特卡罗法估计积分的近似值,主要是借助了几何概率。一个关于几何概率的简单例子:现有一面积为
S
S
S的矩形 A,该矩形完全包含一个面积为
S
′
(
S
≥
S
′
)
S'(S\ge S')
S′(S≥S′)的任意图形 B,则随机向A中投射一个点,该点落在 B 中的概率
p
=
S
′
S
p=\frac{S'}{S}
p=SS′。当
S
S
S与
p
p
p已知时,可知
S
′
=
S
∗
P
S'=S*P
S′=S∗P。
对于该题的积分近似值,可以通过放缩得到一个完全包含
y
=
f
(
x
)
,
x
∈
[
1
,
3
]
y=f(x),x\in[1,3]
y=f(x),x∈[1,3]的图像,即
2
≤
6
x
2
−
4
≤
f
(
x
)
=
6
x
2
−
sin
(
x
)
−
3
≤
6
x
2
−
2
≤
52
,
x
∈
[
1
,
3
]
2\le6x^2-4\le f(x)=6x^2-\sin(x)-3\le6x^2-2\le52,x\in[1,3]
2≤6x2−4≤f(x)=6x2−sin(x)−3≤6x2−2≤52,x∈[1,3]。但由于
f
(
x
)
f(x)
f(x)的最小值不可能小于
2
2
2,而黎曼积分可以看作曲线与
x
x
x轴的正负面积之和,故我们需要把矩形的下边扩大到!
0
0
0。故建立一个矩形,其四条边分别为
y
=
0
,
x
∈
[
1
,
3
]
y=0,x\in[1,3]
y=0,x∈[1,3]、
y
=
52
,
x
∈
[
1
,
3
]
y=52,x\in[1,3]
y=52,x∈[1,3]、
x
=
1
,
y
∈
[
0
,
52
]
x=1,y\in[0,52]
x=1,y∈[0,52]、
x
=
3
,
y
∈
[
0
,
52
]
x=3,y\in[0,52]
x=3,y∈[0,52],进而可知
S
=
104
S=104
S=104。同时,由于
f
(
x
)
≥
0
,
x
∈
[
1
,
3
]
f(x)\ge0,x\in[1,3]
f(x)≥0,x∈[1,3],故不存在负面积,即只需要统计随机投射的
N
N
N个点中位于
x
x
x轴与曲线之间点的数量
M
M
M,则
p
=
M
N
p=\frac{M}{N}
p=NM,进而可求
S
′
S'
S′。
代码
# 投射 1e6 个点,一般民用计算机可以负担,且可快速运行。
N,M,S=10**6,0,104
f=lambda x:6*x**2-sin(x)-3
from math import sin
import numpy.random as nprdm
# 随机生成N个点的坐标(x,y)
x=nprdm.uniform(1,3,size=N)
y=nprdm.uniform(0,52,size=N)for i inrange(N):if y[i]<=f(x[i]):M+=1print(S*M/N)'''
maybeoutput
44.535504'''