UPC 2020年春混合个人训练第二十场

17 篇文章 0 订阅
9 篇文章 0 订阅

问题 D: 超级马
时间限制: 2 Sec 内存限制: 128 MB

题目描述
在一个无限的棋盘上有一个超级马,它可以完成各种动作。每一种动作都是通过两个整数来确定——第一个数说明列的数(正数向右,负数向左),第二个数说明行的数(正数向上,负数向下),移动马来完成这个动作。
编写一个程序,输入说明各种超级马的数据库。
对每一个超级马进行确认,是否通过自己的行动可以到达盘面上的每一个区。
输入
第一行中存在一个整数k,它代表数据库的数1≤k≤100。在这个数字后出现K数据库。它们的每一个第一行中会出现整数N,它是马能够完成的各种动作的数,1≤n≤100。接下来数据库的每一个行中包含两个整数P和Q,它们由单个空格分开,说明动作,-100≤p,q≤100。
输出
由K行组成,当第i个数据库的超级马可以到达棋盘面的每一个区,第i行应包含一个词TAK,而另一个词NIE则恰恰相反。
样例输入 Copy
2
3
1 0
0 1
-2 -1
5
3 4
-3 -6
2 -2
5 6
-1 4
样例输出 Copy
TAK
NIE

很明显,这是一条多次操作的 bfs,每次相同操作,判断输出。注意用 memset 清空数组和输出换行

// D.超级马 (bfs)

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

int a[205][205];
int n,k,ans[3],f,nx,ny;
int fx[101],fy[101];
int quex[2000005],quey[2000005],tot,tow;

bool vis[205][205];

bool bfs()
{
    quex[tot=1]=100;
	quey[tow=1]=100;
	vis[100][100]=1;
    while (tot<=tow)
    { 
        for (int i=1;i<=n;++i)
        {
            nx=quex[tot]+fx[i];
            ny=quey[tot]+fy[i];
            if (nx>=0 && nx<=200 && ny>=0 && ny<=200)
            {
            	if (!vis[nx][ny])
                {
                    quex[++tow]=nx;
                    quey[tow]=ny;
                    vis[nx][ny]=1;
                
                    if(vis[100][99] && vis[100][101] && vis[99][100] && vis[101][100])
                        return true;
                }
			}
        }
        ++tot;
    }
    
    if(vis[100][99] && vis[100][101] && vis[99][100] && vis[101][100])  return true;
    else return false;               
}

int main ()
{
	cin>>k; 
    while(k--)
    {
    	cin>>n;
        memset(fx,0,sizeof(fx));
		memset(fy,0,sizeof(fy));
		memset(vis,0,sizeof(vis));
        
        for(int i=1;i<=n;i++) cin>>fx[i]>>fy[i];
        
        if(bfs())  printf("TAK\n");
        else  printf("NIE\n");
        
    }
    return 0;
}

问题 E: 传递游戏
时间限制: 1 Sec 内存限制: 128 MB

题目描述
n个人在做传递物品的游戏,编号为1-n。
游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。
即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;
求当物品经过所有n个人后,整个过程的最小总代价是多少。

输入
第一行为n,表示共有n个人(16>=n>=2);
以下为n*n的矩阵,第i+1行、第j列表示物品从编号为i的人传递到编号为j的人所花费的代价,特别的有第i+1行、第i列为-1(因为物品不能自己传给自己),其他数据均为正整数(<=10000)。

输出
一个数,为最小的代价总和。
样例输入 Copy
2
-1 3070
134 -1
样例输出 Copy
134

题解:这是一条状压dp的入门题。由题可知:每个人只能被传递一次,用一个 n 位二进制数来表示每个人是否被访问过,但这无法知道物品在谁手中,因此加一个状态,来表示物品在谁手中。用 f[i][j] 表示在 i 状态时最后的一个是 j,初始状态为 f[1<<i][i]=0;表示一开始物品在i手中,所求状态为 min( f[(1<<n)-1][j] ); 因此有转移方程:f[i][j] = min ( f [i-(1<<j)] [k] + cost[k][j], f[i][j]) 。k 表示从 k 点转移到了 j 位置,所以要求 j、k 都应该是集合 i 中的元素。

// E.传递游戏/最小总代价 (状压 dp) 

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

int f[1<<17][17]; //(1<<17)<=>pow(2,17)=131072,int就够 
int cost[17][17];

int main()
{
    int n,i,j,k;
    cin>>n;
 
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)  cin>>cost[i][j];
 
    /*标识状态初始化*/
    memset(f,127,sizeof(f));
    for(i=0;i<n;i++) f[1<<i][i]=0;
 
    int cnt=(1<<n)-1;
    
    for(i=1;i<=cnt;i++ ) {
        for(j=0;j<n;j++) {
            if(i&(1<<j)) {
                for(k=0;k<n;k++) {
                    if( j!=k && (i&(1<<k)) ) {//k在状态i中不存在
                        int x=(i-(1<<j));
                        f[i][j]=min(f[x][k]+cost[k][j],f[i][j]);
                    }
                }
            }
        }
    }
 
    int minn=INF;
    for(i=0;i<n;i++) minn=min(minn,f[cnt][i]);
    
    cout<<minn<<endl;
    return 0;
}

另一种写法:

// E.传递游戏/最小总代价 (状压 dp) 

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=17;
 
int dp[(1<<maxn)][maxn]; // (1<<maxn)<=>2^maxn,两两传递,保证所有情况考虑在内 
int ma[maxn][maxn];
 
int main()
{
    int n,i,j,k;
    while(~scanf("%d",&n))
    {
        for(i=0;i<n;i++)
            for(j=0;j<n;j++) scanf("%d",&ma[i][j]);
       
        memset(dp,inf,sizeof(dp));
        
        for(i=0;i<n;i++) //初始化,自己到自己是 0
            dp[(1<<i)][i]=0; //状压dp初始化常用方法
        
        for(k=0;k<(1<<n)-1;k++){ //各种状态
		    for(i=0;i<n;i++){ //从哪个人传递
		    	if(k&(1<<i)){ //用到哪个人,看是否用过
		    		for(j=0;j<n;j++){ //传递到哪个人
		    			if((k&(1<<j))==0) //同上一样意思
		    			dp[k|(1<<j)][j]=min(dp[k|(1<<j)][j],dp[k][i]+ma[i][j]);
					}
				}
			} 
		} 
          
        int ans=inf;
        for(i=0;i<n;i++)
            ans=min(ans,dp[(1<<n)-1][i]);
        cout<<ans<<endl;
    }
    return 0;
}

问题 G: 迷宫
时间限制: 1 Sec 内存限制: 128 MB

题目描述
给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问每个方格最多经过1次。在迷宫中移动有上下左右四种方式。保证起点上没有障碍。问:可行的方案的数目【另一问:从起点到终点的最短路径长度以及输出一条长度最短的路径经过的点的坐标。
如果不存在起点到终点的路径则就输出-1。】(tired, only the first one)

输入
第一行N、M和T,N为行,M为列,T为障碍总数。
第二行起点坐标SX,SY,终点坐标FX,FY。
接下来T行,每行为障碍的坐标。

输出
如果存在解答则:输出最短路径的长度K(起点终点也算在步数内)
否则输出-1

样例输入 Copy
2 2 1
1 1 2 2
1 2
样例输出 Copy
3
提示
1<=N*M<=1000

这题也出现好多次了,直接贴了之前求方案数的代码qwq。

//G (找出方案数)

#include<iostream>
using namespace std;
 
int start_x,start_y,end_x,end_y;
int map[101][101]={0};
int sum=0;

void dfs(int x,int y)
{
    if(x==end_x&&y==end_y)//搜索终止条件
    {
        sum++;
        return;
    }
    else//进行回溯
    {
        map[x][y]=0;//保存当前坐标
        
        if(map[x][y-1]!=0)//左方
        {
            dfs(x,y-1);//左方搜索
            map[x][y-1]=1;//还原坐标
        }
        if(map[x][y+1]!=0)//右方
        {
            dfs(x,y+1);//右方搜索
            map[x][y+1]=1;//还原坐标
        }
        if(map[x-1][y]!=0)//上方
        {
            dfs(x-1,y);//上方搜索
         	map[x-1][y]=1;//还原坐标
        }
        if(map[x+1][y]!=0)//下方
        {
            dfs(x+1,y);//下方搜索
            map[x+1][y]=1;//还原坐标
        }
    }
}
 
int main()
{
    int n,m,t;
    int i,j;
    int x,y;
    
    cin>>n>>m>>t;//输入行、列、障碍数
    cin>>start_x>>start_y>>end_x>>end_y;//输入起点坐标、终点坐标
    
    for(i=1;i<=n;i++)//地图初始化,为1时表示可以通过
        for(j=1;j<=m;j++) map[i][j]=1;
 
    for(i=1;i<=t;i++)
    {
        cin>>x>>y;//输入障碍物坐标
        map[x][y]=0;//将障碍物坐标记录在地图上
    }
    
    dfs(start_x,start_y);//从起点处开始搜索
 
    cout<<sum<<endl; //输出方案数
    return 0;
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米莱虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值