动态规划&最短路&最小生成树题单

1.Dijkstra?(CF20C)

题面翻译

题目大意

给出一张图,请输出其中任意一条可行的从点 1 1 1 到点 n n n 的最短路径。

输入输出格式

输入格式

第一行:两个整数n,m,分别表示点数和边数

接下来m行:每行三个整数u,v,w,表示u和v之间连一条边权为w的双向边。

输出格式

一行:一个可行的路径,如果不存在这种路径输出-1

2 × n ≤ 1 0 5 2\times n\leq10^5 2×n105 0 ≤ m ≤ 1 0 5 0\leq m\leq10^5 0m105

样例 #1

样例输入 #1

5 6
1 2 2
2 5 5
2 3 4
1 4 1
4 3 3
3 5 1

样例输出 #1

1 4 3 5

思路

本题几乎为一道 dijkstra 的模板题,要求输出路径,如果从 1 1 1 号点开始存储,不保证该路径是到达 n n n 号节点的路径。

可以考虑反向存储,用数组存储对某节点进行松弛的节点

输出时,从 n n n 通过数组一直查询上一个进行松弛的节点,直到找回 1 1 1 号节点,再反向输出,就是 1 1 1 n n n 的最短路径。

注意事项

  • 记得开 long long,边权可能大于 int 上线。
  • 特判 − 1 -1 1 的情况。

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int head[200010],nxt[200010],to[200010],w[200010],cnt=0,s[100010],ans[100010];
LL dis[100010]; //和可能超过int类型
bool vis[100010];
int n,m;

struct node
{
	LL v,val;
	bool operator < (const node &nxt)const{return val>nxt.val;}
};

void add(int u,int v,int t)
{
	nxt[++cnt]=head[u];
	head[u]=cnt;
	to[cnt]=v;
	w[cnt]=t;
}

void dij(int x)
{
    for(int i=1;i<=n;i++)
        dis[i]=0xffffffffff;
	dis[x]=0;
    s[1]=0;
	priority_queue<node>q;
	q.push((node){x,dis[x]});
	while(!q.empty())
	{
		int u=q.top().v;
		q.pop();
		if(vis[u])
			continue;
		vis[u]=1;
		for(int j=head[u];j!=0;j=nxt[j])
		{
			int v=to[j],t=w[j];
			if(dis[u]+t<dis[v])
			{
				dis[v]=dis[u]+t;
                s[v]=u;
				q.push((node){v,dis[v]});
			}
		}
	}
}

int main()
{
	cin >> n >> m ;
	for(int i=1;i<=m;i++)
	{
		int u,v,z;
		cin >> u >> v >> z ;
		add(u,v,z);
        add(v,u,z);
	}
	dij(1);
	if(dis[n]>=0xffffffffff)
    {
        cout << -1 << endl ;
        return 0 ;
    }
    int sum=0,p=n;
    while(p!=1)
    {
        ans[++sum]=p;
        p=s[p];
    }
    ans[++sum]=1;
    for(int i=sum;i>=1;i--)
        cout << ans[i] << " " ;
	return 0 ;
}

2.Volleyball (CF95C)

题面翻译

给出一个图,双向边,边上有权值代表路的距离,然后每个点上有两个值, t t t, c c c t t t 代表能从这个点最远沿边走 t t t,且不能在半路下来,花费是 c c c
现在告诉你起点终点,问最少的花费
点个数 1000 1000 1000,边个数 1000 1000 1000,边权 1 0 9 10^9 109

By @partychicken

输入格式

The first line contains two integers n n n and m m m ( 1 < = n < = 1000 , 0 < = m < = 1000 ) 1<=n<=1000,0<=m<=1000) 1<=n<=1000,0<=m<=1000) . They are the number of junctions and roads in the city correspondingly. The junctions are numbered from 1 1 1 to n n n , inclusive. The next line contains two integers x x x and y y y ( 1 < = x , y < = n 1<=x,y<=n 1<=x,y<=n ). They are the numbers of the initial and final junctions correspondingly. Next m m m lines contain the roads’ description. Each road is described by a group of three integers u i u_{i} ui , v i v_{i} vi , w i w_{i} wi ( 1 < = u i , v i < = n , 1 < = w i < = 1 0 9 1<=u_{i},v_{i}<=n,1<=w_{i}<=10^{9} 1<=ui,vi<=n,1<=wi<=109 ) — they are the numbers of the junctions connected by the road and the length of the road, correspondingly. The next n n n lines contain n n n pairs of integers t i t_{i} ti and c i c_{i} ci ( 1 < = t i , c i < = 1 0 9 1<=t_{i},c_{i}<=10^{9} 1<=ti,ci<=109 ), which describe the taxi driver that waits at the i i i -th junction — the maximum distance he can drive and the drive’s cost. The road can’t connect the junction with itself, but between a pair of junctions there can be more than one road. All consecutive numbers in each line are separated by exactly one space character.

输出格式

If taxis can’t drive Petya to the destination point, print “-1” (without the quotes). Otherwise, print the drive’s minimum cost.

Please do not use the %lld specificator to read or write 64-bit integers in С++. It is preferred to use cin, cout streams or the %I64d specificator.

样例 #1

样例输入 #1

4 4
1 3
1 2 3
1 4 1
2 4 1
2 3 5
2 7
7 2
1 2
7 7

样例输出 #1

9

思路

本题可以使用 dijkstra 算法解决。

观察题目就能发现,这道题与普通的 dijkstra 的区别就在于松弛部分。

假设从 i i i 点松弛,需要对向外扩展的路径长小于 t i t_i ti 的所有点进行松弛,将它们全部设为 i i i 点到原点的距离 + w i +w_i +wi

实现

分为两层 dijkstra 。

第一层

求最小花费, d i s i dis_i disi 存储从原点到 i i i 点的最小花费。

第二层

进行松弛操作,从 i i i 点开始松弛, d i s j dis_j disj 存储 i i i 点到 j j j 点的最短距离,如果 d i s j ≤ t i dis_j\leq t_i disjti,那么就将 j j j 点加入到第一层的队列中。

关于本题是否可以使用 dfs 代替第二层的问题

并不可以使用 dfs 代替第二层

因为 dfs 是相当于从多个不同的起点出发的,可能会向第一层的队列中加入多个重复的点,且不容易标记,会造成 MLE。

dijkstra 每个点仅走一次,所以不用担心空间复杂度。

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int head[2010],nxt[2010],to[2010],w[2010],cnt=0;
int s[1010],c[1010];
LL dis[1010],diss[1010];
bool vis[1010],viss[1010];
LL n,m,b,e,sum,d;

struct node
{
	LL v,val;
	bool operator < (const node &nxt)const{return val>nxt.val;}
};

void add(int u,int v,int t)
{
	nxt[++cnt]=head[u];
	head[u]=cnt;
	to[cnt]=v;
	w[cnt]=t;
}

priority_queue<node>q;

void dij1(int x)
{
	priority_queue<node>p;
	for(int i=1;i<=n;i++)
		diss[i]=1e18;
	diss[x]=0;
	p.push((node){x,diss[x]});
	while(!p.empty())
	{
		int u=p.top().v;
		p.pop();
		if(viss[u])
			continue;
		viss[u]=1;
		for(int j=head[u];j!=0;j=nxt[j])
		{
			LL v=to[j],t=w[j];
			if(!viss[v]&&diss[u]+t<diss[v])
			{
				diss[v]=diss[u]+t;
				if(diss[v]>sum)continue;
				p.push((node){v,diss[v]});
				if(dis[v]>=d)
					dis[v]=d;
				q.push((node){v,d});
			}
		}
	}
}

void dij(int x)
{
    for(int i=1;i<=n;i++)
        dis[i]=1e18;
	dis[x]=0;
	q.push((node){x,dis[x]});
	while(!q.empty())
	{
		int u=q.top().v;
		q.pop();
		if(vis[u])
			continue;
		vis[u]=1;
		sum=s[u];
		d=dis[u]+c[u];
		memset(viss,0,sizeof(viss));
		dij1(u);
	}
}

int main()
{
	cin >> n >> m ;
    cin >> b >> e ;
	for(int i=1;i<=m;i++)
	{
		int u,v,z;
		cin >> u >> v >> z ;
		add(u,v,z);
        add(v,u,z);
	}
    for(int i=1;i<=n;i++)
        cin >> s[i] >> c[i] ;
	dij(b);
    if(dis[e]==1e18)
        cout << -1 << endl ;
    else
        cout << dis[e] << endl ;
	return 0 ;
}

3.The least round way(CF2B)

题目描述

给定由非负整数组成的 n × n n \times n n×n 的正方形矩阵,你需要寻找一条路径:

以左上角为起点

每次只能向右或向下走

以右下角为终点
并且,如果我们把沿路遇到的数进行相乘,积应当是最小“round”,换句话说,应当以最小数目的0的结尾.

输入格式

第一行包含一个整数 n ( 2 ≤ n ≤ 1000 2 \leq n \leq 1000 2n1000),n 为矩阵的规模,接下来的n行包含矩阵的元素(不超过10^9的非负整数).

输出格式

第一行应包含最小尾0的个数,第二行打印出相应的路径(译注:D为下,R为右)

样例 #1

样例输入 #1

3
1 2 3
4 5 6
7 8 9

样例输出 #1

0
DDRR

思路

本题想让我们求最小尾零的个数,因为每个数的值可能会到 1 0 9 10^9 109 级别,直接乘是不可行的。

可以 0 0 0 的个数转化为这个数分解后 2 2 2 的个数和 5 5 5 的个数的最小值

例如 120 = 2 3 × 3 × 5 120 = 2^3 \times 3 \times 5 120=23×3×5 其中 2 2 2 的个数为 3 3 3 5 5 5 的个数为 1 1 1 min ⁡ ( 3 , 1 ) = 1 \min(3,1)=1 min(3,1)=1,零的个数也为 1 1 1

现在要求的就是最后结果中 2 2 2 的最小个数和 5 5 5 的最小个数,可以将其分开 dp,因为最后答案仅取决于其中的较小值

例如,如果 2 2 2 的最小个数小于 5 5 5 的最小个数,可以直接取 2 2 2 的最小个数,因为 5 5 5 的最小个数已经比 2 2 2 的最小个数大,与最小的 2 2 2 配对的 5 5 5 的个数一定不比 2 2 2 的小。

实现

dp部分

f q ( i , j ) fq(i,j) fq(i,j) 表示到 i i i j j j 列最小的 2 2 2 的个数。
f p ( i , j ) fp(i,j) fp(i,j) 表示到 i i i j j j 列最小的 5 5 5 的个数。
q ( i , j ) q(i,j) q(i,j) 表示原矩阵 i i i j j j 列的数分解后 2 2 2 的个数。
q ( i , j ) q(i,j) q(i,j) 表示原矩阵 i i i j j j 列的数分解后 5 5 5 的个数。

转移方程:

f q ( i , j ) = min ⁡ ( f q ( i − 1 , j ) + q ( i , j ) , f q ( i , j − 1 ) + q ( i , j ) ) fq(i,j)=\min(fq(i-1,j)+q(i,j),fq(i,j-1)+q(i,j)) fq(i,j)=min(fq(i1,j)+q(i,j),fq(i,j1)+q(i,j))

f p ( i , j ) = min ⁡ ( f p ( i − 1 , j ) + p ( i , j ) , f p ( i , j − 1 ) + p ( i , j ) ) fp(i,j)=\min(fp(i-1,j)+p(i,j),fp(i,j-1)+p(i,j)) fp(i,j)=min(fp(i1,j)+p(i,j),fp(i,j1)+p(i,j))

最后取 f q ( n , n ) fq(n,n) fq(n,n) f p ( n , n ) fp(n,n) fp(n,n) 中的较小值即可。

对 0 的特殊处理

观察数据范围发现原矩阵中可能有 0 0 0 ,一条包含 0 0 0 的路径最后结果一定是 1 1 1 个零。

我们可以把 0 0 0 作为 10 10 10 来处理,这样包含 0 0 0 的路径结果至少就会有 1 1 1 个零。

如果最后结果大于等于 1 1 1,那么就说明没有一条不包含 2 2 2 5 5 5 的路径(也就是没有比含 0 0 0 路径更优的路径),那么就输出任意一条含 0 0 0 的路径(前提有 0 0 0 )。

路径输出

对于每一次 2 2 2 5 5 5 的转移,分别记录 2 2 2 的最优个数是从上面还是左面来的, 5 5 5 的最优个数是从上面还是左面来的。

  • 如果最后是取的 2 2 2 的最小值,那么就不断寻找这个点的 2 2 2 的最优个数是从哪里来的,直到找回左上角的点,记录寻找路径,反向输出。

  • 如果最后是取的 5 5 5 的最小值,那么就不断寻找这个点的 5 5 5 的最优个数是从哪里来的,直到找回左上角的点,记录寻找路径,反向输出。

代码

#include<bits/stdc++.h>
using namespace std;

int fq[1010][1010],fp[1010][1010],q[1010][1010],p[1010][1010];
int da[1010][1010],db[1010][1010],ans[2010];
int n,xx,yy,flag,sum;

int p2(int x)
{
    int cnt=0;
    while(x%2==0)
    {
        x/=2;
        cnt++;
    }
    return cnt;
}

int p5(int x)
{
    int cnt=0;
    while(x%5==0)
    {
        x/=5;
        cnt++;
    }
    return cnt;
}

void find2()
{
	int i=n,j=n,s=da[n][n];
    while(s!=0)
    {
        if(s==1)
        {
            j=j-1;
            s=da[i][j];
            ans[++sum]=1;
        }
        else
        {
            i=i-1;
            s=da[i][j];
            ans[++sum]=2;
        }
    }
}

void find5()
{
	int i=n,j=n,s=db[n][n];
    while(s!=0)
    {
        if(s==1)
        {
            j=j-1;
            s=db[i][j];
            ans[++sum]=1;
        }
        else
        {
            i=i-1;
            s=db[i][j];
            ans[++sum]=2;
        }
    }
}

int main()
{
    cin >> n ;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            int s ;
            cin >> s ;
            if(s==0)
            {
                flag=1;
                xx=i;
                yy=j;
                s=10;
            }
            q[i][j]=p2(s);
            p[i][j]=p5(s);
        }
    }
    fq[1][1]=q[1][1];
    fp[1][1]=p[1][1];
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==1&&j==1)continue;
            if(i==1)
            {
                fq[i][j]=fq[i][j-1]+q[i][j];
                fp[i][j]=fp[i][j-1]+p[i][j];
                da[i][j]=1;
                db[i][j]=1;
            }
            else if(j==1)
            {
                fq[i][j]=fq[i-1][j]+q[i][j];
                fp[i][j]=fp[i-1][j]+p[i][j];
                da[i][j]=2;
                db[i][j]=2;
            }
            else
            {
                if(fq[i][j-1]+q[i][j]>=fq[i-1][j]+q[i][j])
                {
                	fq[i][j]=fq[i-1][j]+q[i][j];
                	da[i][j]=2;
				}
				else
				{
					fq[i][j]=fq[i][j-1]+q[i][j];
					da[i][j]=1;
				}
				if(fp[i][j-1]+p[i][j]>=fp[i-1][j]+p[i][j])
				{
					fp[i][j]=fp[i-1][j]+p[i][j];
					db[i][j]=2;
				}
				else
				{
					fp[i][j]=fp[i][j-1]+p[i][j];
					db[i][j]=1;
				}
            }
        }
    }
    if(min(fq[n][n],fp[n][n])>1&&flag)
    {
        cout << 1 << endl ;
        for(int i=1;i<yy;i++)
            cout << "R" ;
        for(int j=1;j<n;j++)
            cout << "D" ;
        for(int i=yy;i<n;i++)
            cout << "R" ;
        return 0 ;
    }
    if(fq[n][n]<=fp[n][n])
    {
    	cout << fq[n][n] << endl ;
    	find2();
	}
	else
	{
		cout << fp[n][n] << endl ;
		find5();
	}
    for(int k=sum;k>=1;k--)
    {
        if(ans[k]==1)
            cout << "R" ;
        else
            cout << "D" ;
    }
    return 0 ;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值