文具订购
【题目描述】
小明的班上共有 n 元班费,同学们准备使用班费集体购买 3 种物品:
1.圆规,每个 7 元。
2.笔,每支 4 元。
3.笔记本,每本 3 元。
小明负责订购文具,设圆规,笔,笔记本的订购数量分别为 a,b,c,他订购的原则依次如下:
1.n 元钱必须正好用光,即 7a+4b+3c=n。
2.在满足以上条件情况下,成套的数量尽可能大,即 a,b,c 中的最小值尽可能大。
3.在满足以上条件情况下,物品的总数尽可能大,即 a+b+c 尽可能大。
请你帮助小明求出满足条件的最优方案。可以证明若存在方案,则最优方案唯一。
【输入格式】
从文件 order.in 中读入数据。
仅一行一个整数 n 表示班费数量。
【输出格式】
输出到文件 order.out 中。
若方案不存在则输出 -1。否则输出一行三个用空格分隔的非负整数 a,b,c 表示答案。
【样例1输入】
1
【样例1输出】
-1
【样例2输入】
14
【样例2输出】
1 1 1
【样例3输入】
33
【样例3输出】
1 2 6
【样例3解释】
a=2,b=4,c=1 也是满足条件 1,2 的方案,但对于条件 3,该方案只买了 7 个物品,不如 a=1,b=2,c=6 的方案。
【数据范围与提示】
对于测试点 1 ∼ 6:n ≤ 14。
对于测试点 7 ∼ 12:n 是 14 的倍数。
对于测试点 13 ∼ 18:n ≤ 100。
对于所有测试点:0 ≤ n ≤ 105。
【时间限制】
1.0s
【空间限制】
256MB
【上传文件】
上传c, cpp或pas语言源程序,文件名应依次为order.c, order.cpp, order.pas。
【解析1】
模拟。三重循环。但这不是简单的三重循环。具体看代码。
【代码1】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=105;
int n;
int main() {
//freopen("order.in","r",stdin);
//freopen("order.out","w",stdout);
scanf("%d",&n);
if(n==0) {
printf("0 0 0\n");
//fclose(stdin);
//fclose(stdout);
return 0;
} else {
for(int i=n/14; i>=0; i--) { //枚举a
for(int j=i; j<=n/4; j++) //枚举b
for(int k=i; k<=n/3; k++) //枚举c
if(i*7+j*4+k*3==n) { //判断条件是否满足
printf("%d %d %d\n",i,j,k);
//fclose(stdin);
//fclose(stdout);
return 0;
}
}
}
printf("-1\n");
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析2】
模拟。当然,暴力的三重循环可以优化到两重。这种方法的时间比较费。有点悬,所以开了快读。
【代码2】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=105;
int n,vis;
int ansa,ansb,ansc,ansmin,anstot;
inline void read(int &x){
x=0;
char c=0;
int w=0;
while (!isdigit(c)) w|=c=='-',c=getchar();
while (isdigit(c)) x=x*10+(c^48),c=getchar();
if(w) x=-x;
}
inline int min3(int a,int b,int c) {
return MIN(MIN(a,b),c);
}
int main() {
//freopen("order.in","r",stdin);
//freopen("order.out","w",stdout);
read(n); //读入
for(int a=0; a*7<=n; a++) {
int bb=n-7*a; //bb为b的上限
for(int b=0; b*4<=bb; b++) {
int c=n-7*a-4*b;
if(c%3) continue;
vis=1; //标记有解
c=c/3;
int nowmin=min3(a,b,c);
int nowtot=a+b+c;
if(nowmin>ansmin) { //判断条件1
ansa=a,ansb=b,ansc=c;
ansmin=nowmin,anstot=nowtot;
} else if(nowmin==ansmin) { //判断条件2
if(nowtot>anstot) {
ansa=a,ansb=b,ansc=c;
ansmin=nowmin,anstot=nowtot;
}
}
}
}
if(vis) printf("%d %d %d\n",ansa,ansb,ansc);
else printf("-1\n");
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析3】
这个算法有点难以置信,但是对的。时间复杂度O(1)。
我们一个一个条件地分析:
1 要花光所有的钱:
首先,三种文具的价格是 3,4,73,4,7 ,通过证明,发现除了 n=1 ,n=2 , n=5 以外,任何的价格 n 都可以被花光,所以这个条件并没有什么太大的约束力;
2 尽量配成更多套
一套的价钱是 1414 元,所以这个规定就是要我们尽可能地买更多的整 1414 元;
3 尽量买更多的物品 :
这个规定其实就是在我们买了尽可能多的整 14 元之后,剩余的钱再尽量拆分成 3 元或 44 元(因为 7=3+4 ,这样买的物品数量就会减少,所以不买 7 元的东西)
好了,题目分析完了,我们归纳一下:
ans=n/14;
n%=14;
if(n==?) {
cout<<ans<<" "<<ans+?<<" "<<ans+?;
return 0;
}
if(n==?)
·
·
·
·
(注:由于是伪代码,所以?处是代表一个数字)
看见了吗?其实这道题就是一个除法,再加上好多个 ifif 就解决了。。。
但是细心的朋友可能会发现,这个算法有一个漏洞:
如果买完尽量多的套数以后,剩下的是 1,2,51,2,5 元呢?这样不就不满足第一个条件了吗?
不用担心,这个问题很简单,我们只需要少配一套物品,再拿出14元来,不就可以花完钱了吗?
【代码3】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=105;
int n,ans;
//ans记录套数
int main() {
//freopen("order.in","r",stdin);
//freopen("order.out","w",stdout);
scanf("%d",&n);
if(n==0) {
printf("0 0 0\n");
//fclose(stdin);
//fclose(stdout);
return 0;
}
if(n<6 && n!=4 && n!=3) { ////若n无法分完,则直接输出答案
printf("-1\n");
//fclose(stdin);
//fclose(stdout);
return 0;
}
ans=n/14;
n=n%14;
if(n<6 && n!=4 && n!=3 && n!=0)
ans--,n=n+14; //若在套数最多时n分不完,套数-1
if(n==0) { //若正好分完,直接输出三个套数
printf("%d %d %d\n",ans,ans,ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}
if(n==3) printf("%d %d %d\n",ans,ans,ans+1);
else if(n==4) printf("%d %d %d\n",ans,ans+1,ans);
else if(n==6) printf("%d %d %d\n",ans,ans,ans+2);
else if(n==7) printf("%d %d %d\n",ans,ans+1,ans+1);
else if(n==8) printf("%d %d %d",ans,ans+2,ans);
else if(n==9) printf("%d %d %d\n",ans,ans,ans+3);
else if(n==10) printf("%d %d %d\n",ans,ans+1,ans+2);
else if(n==11) printf("%d %d %d\n",ans,ans+2,ans+1);
else if(n==12) printf("%d %d %d\n",ans,ans,ans+4);
else if(n==13) printf("%d %d %d\n",ans,ans+1,ans+3);
else if(n==15) printf("%d %d %d\n",ans,ans,ans+5);
else if(n==16) printf("%d %d %d\n",ans,ans+1,ans+4);
else if(n==19) printf("%d %d %d\n",ans,ans+1,ans+5);
//fclose(stdin);
//fclose(stdout);
return 0;
}
if-else也可以替换为switch-case:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=105;
int n,ans;
//ans记录套数
int main() {
//freopen("order.in","r",stdin);
//freopen("order.out","w",stdout);
scanf("%d",&n);
if(n==0) {
printf("0 0 0\n");
//fclose(stdin);
//fclose(stdout);
return 0;
}
if(n<6 && n!=4 && n!=3) { ////若n无法分完,则直接输出答案
printf("-1\n");
//fclose(stdin);
//fclose(stdout);
return 0;
}
ans=n/14;
n=n%14;
if(n<6 && n!=4 && n!=3 && n!=0)
ans--,n=n+14; //若在套数最多时n分不完,套数-1
if(n==0) { //若正好分完,直接输出三个套数
printf("%d %d %d\n",ans,ans,ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}
switch(n) {
case 3: printf("%d %d %d\n",ans,ans,ans+1); break;
case 4: printf("%d %d %d\n",ans,ans+1,ans); break;
case 6: printf("%d %d %d\n",ans,ans,ans+2); break;
case 7: printf("%d %d %d\n",ans,ans+1,ans+1); break;
case 8: printf("%d %d %d",ans,ans+2,ans); break;
case 9: printf("%d %d %d\n",ans,ans,ans+3); break;
case 10: printf("%d %d %d\n",ans,ans+1,ans+2); break;
case 11: printf("%d %d %d\n",ans,ans+2,ans+1); break;
case 12: printf("%d %d %d\n",ans,ans,ans+4); break;
case 13: printf("%d %d %d\n",ans,ans+1,ans+3); break;
case 15: printf("%d %d %d\n",ans,ans,ans+5); break;
case 16: printf("%d %d %d\n",ans,ans+1,ans+4); break;
case 19: printf("%d %d %d\n",ans,ans+1,ans+5); break;
}
//fclose(stdin);
//fclose(stdout);
return 0;
}
跑步
【题目描述】
小 H 是一个热爱运动的孩子,某天他想给自己制定一个跑步计划。小 H 计划跑 n 米,其中第 i(i ≥ 1) 分钟要跑 xi米(xi 是正整数),但没有确定好总时长。由于随着跑步时间增加,小 H 会越来越累,所以小 H 的计划必须满足对于任意 i(i>1) 有 xi ≤ xi-1。现在小 H 想知道一共有多少个不同的满足条件的计划,请你帮助他。两个计划不同当且仅当跑步的总时长不同,或者存在一个 i,使得两个计划中 xi 不相同。由于最后的答案可能很大,你只需要求出答案对 p 取模的结果。
【输入格式】
从文件 running.in 中读入数据。
仅一行两个整数 n,p 表示跑步距离与模数。
【输出格式】
输出到文件 running.out 中。
仅一行一个整数,表示答案模 p 的值。
【样例1输入】
4 44
【样例1输出】
5
【样例1解释】
五个不同的计划分别是:{1,1,1,1},{2,1,1},{3,1},{2,2},{4}。
【样例2输入】
66 666666
【样例2输出】
323522
【样例3输入】
66666 66666666
【样例3输出】
45183149
【数据范围与提示】
对于所有测试点:1 ≤ n ≤ 105, 1 ≤ p < 230。
每个测试点限制具体如下:
| 测试点编号 | n ≤ |
|---|---|
| 1 | 5 |
| 2 | 10 |
| 3 | 50 |
| 4 | 100 |
| 5 | 500 |
| 6 | 2000 |
| 7 | 5000 |
| 8 | 20000 |
| 9 | 50000 |
| 10 | 100000 |
【时间限制】
2.0s
【空间限制】
256MB
【上传文件】
上传c, cpp或pas语言源程序,文件名应依次为running.c, running.cpp, running.pas。
【解析1】
70分的做法。
题目的实质是整数拆分。
球盒问题。
n个相同球放到m个相同的盒子里(无空箱)。
(球盒问题总结:点击这里)
d
p
[
n
]
[
m
]
=
d
p
[
n
]
[
m
−
1
]
+
d
p
[
n
−
m
]
[
m
]
(
n
>
=
m
)
dp[n][m]=dp[n][m−1]+dp[n−m][m] (n>=m)
dp[n][m]=dp[n][m−1]+dp[n−m][m](n>=m)
d
p
[
n
]
[
m
]
=
d
p
[
n
]
[
m
−
1
]
(
n
<
m
)
dp[n][m]=dp[n][m−1](n<m)
dp[n][m]=dp[n][m−1](n<m)
边
界
:
d
p
[
k
]
[
1
]
=
1
边界:dp[k][1]=1
边界:dp[k][1]=1
a
n
s
=
S
i
g
m
a
f
[
n
]
[
i
]
(
i
=
1
,
i
<
=
n
)
ans=Sigma f[n][i] (i=1,i<=n)
ans=Sigmaf[n][i](i=1,i<=n)
【代码1】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=5005;
int n,MOD,ans;
int f[N][N];
void fun(int n) {
ms(0,f);
for(int i=1; i<=n; i++) f[i][1]=1;
for(int i=2; i<=n; i++) for(int j=2; j<=i; j++) {
f[i][j]=(f[i-1][j-1]+f[i-j][j])%MOD;
}
}
int main() {
//freopen("running.in","r",stdin);
//freopen("running.out","w",stdout);
scanf("%d%d",&n,&MOD);
fun(n);
for(int i=1; i<=n; i++) ans=ans+f[n][i],ans=ans%MOD;
printf("%d\n",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析2】
题解在此:整数拆分
完。
【代码2】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=100005;
int n,MOD,ans;
int f[N];
inline int fun(int x) {
int ans=(3*x*x-x)>>1;
return ans;
}
int main() {
//freopen("running.in","r",stdin);
//freopen("running.out","w",stdout);
scanf("%d%d",&n,&MOD);
f[0]=1;
for(int i=1; i<=n; i++) {
for(int j=1; ; j++) {
int x=fun(j),y=fun(-j);
if(x<=i) f[i]=((f[i]+(j&1 ? 1:-1)*f[i-x])%MOD+MOD)%MOD;
if(y<=i) f[i]=((f[i]+(j&1 ? 1:-1)*f[i-y])%MOD+MOD)%MOD;
if(x>i || y>i) break;
}
}
printf("%d\n",f[n]);
//fclose(stdin);
//fclose(stdout);
return 0;
}
魔法
【题目描述】
C 国由 n 座城市与 m 条有向道路组成,城市与道路都从 1 开始编号,经过 i 号道路需要 ti 的费用。
现在你要从 1 号城市出发去 n 号城市,你可以施展最多 K 次魔法,使得通过下一条道路时,需要的费用变为原来的相反数,即费用从 ti 变为 -ti。请你算一算,你至少要花费多少费用才能完成这次旅程。注意:使用魔法只是改变一次的花费,而不改变一条道路自身的 ti;最终的费用可以为负,并且一个城市可以经过多次(包括 n 号城市)。
【输入格式】
从文件 magic.in 中读入数据。
第一行三个整数 n, m, K,表示城市数、道路数、魔法次数限制。
接下来 m 行每行三个整数 ui,vi,ti,第 i 行描述 i 号道路,表示一条从 ui 到 vi 的有向道路,经过它需要花费 ti。
【输出格式】
输出到文件 magic.out 中。
仅一行一个整数表示答案。
【样例1输入】
4 3 2
1 2 5
2 3 4
3 4 1
【样例1输出】
-8
【样例1解释】
依次经过 1 号道路、2 号道路、3 号道路,并在经过 1、2 号道路前使用魔法。
【样例2输入】
2 2 2
1 2 10
2 1 1
【样例2输出】
-19
【样例2解释】
依次经过 1 号道路、2 号道路、1 号道路,并在两次经过 1 号道路前都使用魔法。
【数据范围与提示】
对于所有测试点和样例满足:
1 ≤ n ≤ 100,1 ≤ m ≤ 2500,0 ≤ K ≤ 106,1 ≤ ui,vi ≤ n,1 ≤ ti ≤ 109。
数据保证图中无自环,无重边,至少存在一条从 1 号城市到达 n 号城市的路径。
每个测试点的具体限制见下表。
| 测试点编号 | n ≤ | m ≤ | K ≤ | 特殊限制 |
|---|---|---|---|---|
| 1 ∼ 2 | 5 | 20 | 0 | 无 |
| 3 ∼ 4 | 10 | 20 | 50 | 无 |
| 5 ∼ 6 | 10 | 20 | 0 | 无 |
| 7 ∼ 8 | 20 | 200 | 50 | 图中无环 |
| 9 ∼ 10 | 20 | 200 | 0 | 无 |
| 11 ∼ 12 | 100 | 200 | 50 | 图中无环 |
| 13 ∼ 14 | 100 | 200 | 50 | 无 |
| 15 ∼ 18 | 100 | 2500 | 1000 | 无 |
| 19 ∼ 20 | 100 | 2500 | 106 | 无 |
【时间限制】
1.0s
【空间限制】
256MB
【上传文件】
上传c, cpp或pas语言源程序,文件名应依次为magic.c, magic.cpp, magic.pas。
【解析1】
30分的做法。
看到这么复杂的题目和超大的数据,不会?
没有关系。
静下心来,仔细看一下数据。
你会发现有6组数据k=0。
先用Floyd骗个30分吧。
【代码1】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
LL const inf=1e16;
int const N=105;
int n,m,k;
LL f[N][N];
int main() {
//freopen("magic.in","r",stdin);
//freopen("magic.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=100; i++) for(int j=1; j<=100; j++)
f[i][j]=inf;
for(int i=1; i<=m; i++) {
int t,u,v;
scanf("%d%d%d",&u,&v,&t);
f[u][v]=t;
}
for(int k2=1; k2<=n; k2++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
f[i][j]=MIN(f[i][j],f[i][k2]+f[k2][j]);
printf("%lld\n",f[1][n]);
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析2】
70分的做法。
spfa。
【代码2】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
typedef pair<int,int> P;
int const N=105;
int const M=1e6+5;
int const K=2e5+5;
LL const inf=1e16;
int n,m,k;
int h[N];
int vis[N][K];
LL d[N][K];
queue<P> q;
struct Edge {
int to,nt,len;
} e[M];
void spfa() {
for(int i=1; i<=n; i++) for(int j=0; j<=k; j++) d[i][j]=inf;
d[1][0]=0;
q.push(P(1,0));
vis[1][0]=1;
while(!q.empty()) {
int a=q.front().first;
int x=q.front().second;
q.pop();
vis[a][x]=0;
for(int i=h[a]; i; i=e[i].nt) {
int y=e[i].to,z=e[i].len;
if(d[y][x]>d[a][x]+z) {
d[y][x]=d[a][x]+z;
if(!vis[y][x]) {
q.push(P(y,x));
vis[y][x]=1;
}
}
if(x<k && d[y][x+1]>d[a][x]-z) {
d[y][x+1]=d[a][x]-z;
if(!vis[y][x+1]) {
q.push(P(y,x+1));
vis[y][x+1]=1;
}
}
}
}
}
int main() {
//freopen("magic.in","r",stdin);
//freopen("magic.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=m; i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[i]=(Edge){y,h[x],z};
h[x]=i;
}
spfa();
LL ans=inf;
for(int i=0; i<=k; i++) ans=MIN(ans,d[n][i]);
printf("%lld\n",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析3】
90分的做法。
最后两个数据点会RE。
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示用了
j
j
j步魔法走到
i
i
i,那么:
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
]
,
(
k
−
>
i
有
边
?
(
f
[
k
]
[
j
−
1
]
−
t
(
k
,
i
)
:
无
穷
大
)
)
f[i][j]=min(f[i][j],(k->i有边 ? (f[k][j-1]-t(k,i):无穷大))
f[i][j]=min(f[i][j],(k−>i有边?(f[k][j−1]−t(k,i):无穷大))
t
(
k
,
i
)
t(k,i)
t(k,i)是原始的边长。
【代码3】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
LL const inf=1e16;
int const N=105;
int n,m,k;
LL ans=inf;
LL a[N][N],f[N][1005],d[N][N];
int main() {
//freopen("magic.in","r",stdin);
//freopen("magic.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
d[i][j]=inf;
for(int i=1; i<=n; i++) d[i][i]=a[i][i]=0;
for(int i=1; i<=m; i++) {
int t,u,v;
scanf("%d%d%d",&u,&v,&t);
a[u][v]=d[u][v]=t;
}
for(int k2=1; k2<=n; k2++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
d[i][j]=MIN(d[i][j],d[i][k2]+d[k2][j]);
for(int i=1; i<=n; i++) for(int j=1; j<=k; j++) f[i][j]=inf;
for(int i=1; i<=n; i++) f[i][0]=d[1][i];
for(int j=1; j<=k; j++) {
for(int i=1; i<=n; i++) for(int k3=1; k3<=n; k3++)
f[i][j]=MIN(f[i][j],(a[k3][i]>0 ? f[k3][j-1]-a[k3][i] : inf));
for(int i=1; i<=n; i++) for(int k3=1; k3<=n; k3++)
f[i][j]=MIN(f[i][j],f[k3][j]+d[k3][i]);
}
for(int i=0; i<=k; i++) ans=MIN(ans,f[n][i]);
printf("%lld\n",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析4】
100分的做法1。
解析4是对解析3的优化。
时间复杂度
O
(
n
2
t
+
n
2
k
/
t
)
O(n^2t+n^2k/t)
O(n2t+n2k/t),
t
=
s
q
r
t
(
k
)
t = sqrt(k)
t=sqrt(k),所以约等于
O
(
n
2
s
q
r
t
(
k
)
)
O(n^2sqrt(k))
O(n2sqrt(k)).
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示从i走到k层后的j的最小代价,按照解析3的方法dp。
因为前t层一定存在一种走法,然后发现最后只要剩下够n个魔法就一定不会停止。
t
=
m
a
x
(
n
,
s
q
r
t
(
k
)
)
t=max(n,sqrt(k))
t=max(n,sqrt(k))
【代码4】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=105;
int n,m,t,x,y,z;
LL a[N][N],d[N<<1][N<<1],ans[N][N],f[21][N][N];
int main() {
//freopen("magic.in","r",stdin);
//freopen("magic.out","w",stdout);
scanf("%d%d%d",&n,&m,&t);
memset(d,0x3f,sizeof(d));
memset(ans,0x3f,sizeof(ans));
memset(f,0x3f,sizeof(f));
for(int i=1; i<=n; i++) d[i][i]=ans[i][i]=d[i+n][i+n]=0;
d[n][n<<1]=0;
for(int i=1; i<=m; i++){
scanf("%d%d%d",&x,&y,&z);
d[x][y]=d[x+n][y+n]=z;
d[x][y+n]=-z;
}
for(int i=1; i<=(n<<1); i++) for(int j=1; j<=2*n; j++) for(int k=1; k<=(n<<1); k++)
d[j][k]=MIN(d[j][k],d[j][i]+d[i][k]);
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
f[0][i][j]=d[i][j+n];
if(!t) {
printf("%lld",d[1][n]);
return 0;
}
if(t&1) {
t--;
memcpy(ans,f[0],sizeof(ans));
}
for(int ii=1; t; ii++) {
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) for(int k=1; k<=n; k++)
f[ii][i][j]=MIN(f[ii][i][j],f[ii-1][i][k]+f[ii-1][k][j]);
if(t&(1<<ii)) {
t-=(1<<ii);
memcpy(a,ans,sizeof(a));
memset(ans,0x3f,sizeof(ans));
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) for(int k=1;k<=n;k++)
ans[i][j]=MIN(ans[i][j],a[i][k]+f[ii][k][j]);
}
}
printf("%lld\n",ans[1][n]);
//fclose(stdin);
//fclose(stdout);
return 0;
}
【解析5】
100分的做法2。
矩阵乘法+快速幂。
这题思路本身其实没那么难,但是想出来真的有点难。
首先,定义任意两点之间至多使用一次魔法的最短路为
f
k
,
i
,
j
f_{k, i, j}
fk,i,j
假设我们已经求出了任意两点之间 至多 使用一次魔法的最短路,即为
f
1
,
i
,
j
f_{1, i, j}
f1,i,j
后面再讲怎么求这个东西。
Q: 为什么要求这个?
A: k太大了,转移次数必须小于线性。考虑把它优化至矩阵乘法。
转移方程:
f
k
,
i
,
j
=
min
t
∈
[
1
,
n
]
f
k
−
1
,
i
,
t
+
f
1
,
t
,
j
f_{k, i, j} = \min_{t \in [1, n]} f_{k - 1, i, t} + f_{1, t, j}
fk,i,j=t∈[1,n]minfk−1,i,t+f1,t,j
人话翻译:选择一个中转点 t ,就像求最短路时一样,前面用 k - 1 次魔法,后面用 1 次魔法,这些路径当中求最小值。
Q: 为什么限定后半段只能用一次?
A: 这个转移是一个连续的过程,假如后半段用 p 次魔法,那么你完全可以在后半段转移 p 次之后再来更新全局答案。
Q: 如果我有一段路不想用魔法呢?
A: 如果允许使用魔法 ( k > 0 ),那么任意两点间最短路必须使用魔法,因为原边权都>0
还有,令 t ∈ { i , j } t \in \{i, j\} t∈{i,j}也就相当于没转移。
然后发现一个惊奇的事情:如果把每个fk 看成一个矩阵,那么一次转移就是f1的一次自乘,把矩阵乘法中的加号换成 m i n min min 。
Q: 怎么证明这个可以快速幂呢?
A: 只需证明 两个矩阵相乘,加号换成 m i n min min 这个新运算 满足结合律,即 ( A opt B ) opt C = A opt ( B opt C ) (A \operatorname{opt} B) \operatorname{opt} C = A \operatorname{opt} (B \operatorname{opt} C) (AoptB)optC=Aopt(BoptC)无论怎么结合,答案矩阵里面每一个元素都是由 原来的三个矩阵中相同的那几个元素 取 m i n min min得到的。
接下来求
f
1
,
i
,
j
f_{1, i, j}
f1,i,j
f 0 , i , j f_{0, i, j} f0,i,j 肯定都会求吧,由于点数非常小,一遍 Floyd 完事。只需要管强制使用魔法的情况,两者取 m i n min min 就可以了。
强制使用魔法也很简单,枚举每一条边 (a, b)(a,b) ,强制它使用魔法,且任意两点之间都要通过这条边进行转移,取 m i n min min 。 f 1 , i , j = min ( f 0 , i , j , f 0 , i , a + f 0 , b , j − w i , j ) f_{1, i, j} = \min(f_{0, i, j}, f_{0, i, a} + f_{0, b, j} - w_{i, j}) f1,i,j=min(f0,i,j,f0,i,a+f0,b,j−wi,j)
做完了。
代码其实不难写,一个 Floyd 板子,加上f数组的转移,再加上魔改过后的矩乘板子就搞完了。但是好多数组都要赋初值为正无穷,不要漏了,并且要特判 k = 0 的情况。
【代码5】
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
LL const inf=1e16;
int const N=105;
int const M=2505;
int n,m,k;
int u[M],v[M];
LL a[N][N],d[N][N];
struct matrix {
LL m[N][N];
} f,e;
matrix multiply(matrix x,matrix y) {
matrix c;
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
c.m[i][j]=inf;
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) for(int k2=1; k2<=n; k2++)
c.m[i][j]=MIN(c.m[i][j],x.m[i][k2]+y.m[k2][j]);
return c;
}
matrix qpower(matrix x,int y) {
if(!y) return e;
if(y==1) return x;
matrix ret=qpower(x,y>>1);
if(y&1) return multiply(multiply(ret,x),ret);
else return multiply(ret,ret);
}
int main() {
//freopen("magic.in","r",stdin);
//freopen("magic.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
a[i][j]=f.m[i][j]=inf;
for(int i=1; i<=n; i++) a[i][i]=0;
for(int i=1; i<=n; i++) e.m[i][i]=1;
for(int i=1; i<=m; i++) {
int t;
scanf("%d%d%d",&u[i],&v[i],&t);
a[u[i]][v[i]]=t;
}
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
d[i][j]=a[i][j];
for(int k3=1; k3<=n; k3++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
d[i][j]=MIN(d[i][j],d[i][k3]+d[k3][j]);
if(!k) { //特判k是否等于0
printf("%d\n",d[1][n]);
//fclose(stdin);
//fclose(stdout);
return 0;
}
for(int k4=1; k4<=m; k4++) {
LL t=a[u[k4]][v[k4]];
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
f.m[i][j]=MIN(f.m[i][j],MIN(d[i][j],d[i][u[k4]]+d[v[k4]][j]-t));
}
matrix ans=qpower(f,k);
printf("%lld\n",ans.m[1][n]);
//fclose(stdin);
//fclose(stdout);
return 0;
}

本文深入解析了NOIOnline平台上的三道经典算法题目:文具订购、跑步计划与魔法之旅,涵盖模拟、动态规划与图论算法,提供多种解题思路及代码实现。

3071

被折叠的 条评论
为什么被折叠?



