第四次练习笔记(贪心算法)

贪心算法的定义:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

贪心算法思想:

顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似

解题的一般步骤是:
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的局部最优解合成原来问题的一个解。
原文链接:https://blog.csdn.net/qq_32400847/article/details/51336300

高僧斗法

古时丧葬活动中经常请高僧做法事。仪式结束后,有时会有“高僧斗法”的趣味节目,以舒缓压抑的气氛。

节目大略步骤为:先用粮食(一般是稻米)在地上“画”出若干级台阶(表示N级浮屠)。又有若干小和尚随机地“站”在某个台阶上。最高一级台阶必须站人,其它任意。(如图1所示)

两位参加游戏的法师分别指挥某个小和尚向上走任意多级的台阶,但会被站在高级台阶上的小和尚阻挡,不能越过。两个小和尚也不能站在同一台阶,也不能向低级台阶移动。

两法师轮流发出指令,最后所有小和尚必然会都挤在高段台阶,再也不能向上移动。轮到哪个法师指挥时无法继续移动,则游戏结束,该法师认输。

对于已知的台阶数和小和尚的分布位置,请你计算先发指令的法师该如何决策才能保证胜出。

输入数据:

为一行用空格分开的N个整数,表示小和尚的位置。台阶序号从1算起,所以最后一个小和尚的位置即是台阶的总数。(N<100, 台阶总数<1000)

输出数据:

输出为一行用空格分开的两个整数: A B, 表示把A位置的小和尚移动到B位置。若有多个解,输出A值较小的解,若无解则输出-1。

核心代码:


public class Main {
    public static boolean f(int x[]){
        int sum=0;
        for(int i=0;i<x.length-1;i+=2){
            sum^=(x[i+1]-x[i]-1);
        }
        return sum!=0;
    }
    public static void solve(int x[]){
        for(int i=0;i<x.length-1;i++)
            for(int k=x[i]+1;k<x[i+1];k++){
                int old=x[i];
                try{
                    x[i]=k;//试探
                    if(f(x)==false){
                        System.out.println(old+" "+k);
                        return;
                    }
                }finally{
                    x[i]=old;//回溯
                }
            }
    }
    
}

大臣的旅费

很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。

J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入数据:

输入的第一行包含一个整数n,表示包括首都在内的T王国的城市数

城市从1开始依次编号,1号城市为首都。

接下来n-1行,描述T国的高速路(T国的高速路一定是n-1条)

每行三个整数Pi, Qi, Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。

输出数据:

输出一个整数,表示大臣J最多花费的路费是多少。

核心代码:


void dfs(int edgeNum)
{
      bool flag = false;
      vis[edgeNum] = 1;
      ++occurrences[edge[edgeNum].f];
      ++occurrences[edge[edgeNum].t];
      value += edge[edgeNum].v;
      for(int i = 1; i <= cnt; ++i)
  {
    if((edge[edgeNum].f == edge[i].f || edge[edgeNum].f == edge[i].t || edge[edgeNum].t == edge[i].f || edge[edgeNum].t == edge[i].t) && vis[i] == 0 && occurrences[edge[i].f] < 2 && occurrences[edge[i].t] < 2)
    {
      flag = true; 
      dfs(i);
    }
  }
  if(!flag)
  {
    if(value > maxValue)
    {
      maxValue = value;
      maxValueIndex = edgeNum;
    }
  }
  vis[edgeNum] = 0;
  --occurrences[edge[edgeNum].f];
  --occurrences[edge[edgeNum].t];
  value -= edge[edgeNum].v;
}
}

连号区间数

小明这些天一直在思考这样一个奇怪而有趣的问题:

在1~N的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是:

如果区间[L, R] 里的所有元素(即此排列的第L个到第R个元素)递增排序后能得到一个长度为R-L+1的“连续”数列,则称这个区间连号区间。

当N很小的时候,小明可以很快地算出答案,但是当N变大的时候,问题就不是那么简单了,现在小明需要你的帮助。

输入

第一行是一个正整数N (1 <= N <= 50000), 表示全排列的规模。

第二行是N个不同的数字Pi(1 <= Pi <= N), 表示这N个数字的某一全排列。

输出

输出一个整数,表示不同连号区间的数目。

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    int n;
    int a[50005];
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    int sum=0;
    for(int i=0;i<n;i++)
    {
        int maxx=1;
        int minn=n;
        for(int j=i;j<n;j++)
        {
            if(a[j]>maxx)maxx=a[j];
            if(minn>a[j])minn=a[j];
            if(maxx-minn==j-i)sum++;        
        }
    }
    printf("%d\n",sum);
    return 0;
}

翻硬币

小明正在玩一个“翻硬币”的游戏。

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。

比如,可能情形是:**oo***oooo

如果同时翻转左边的两个硬币,则变为:oooo***oooo

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

我们约定:把翻动相邻的两个硬币叫做一步操作

Input

两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000

Output

一个整数,表示最小操作步数。

#include <stdio.h>
 
int main()
{
    char s1[1000],s2[1000];
    gets(s1);
    gets(s2);
    int l = strlen(s1);
    int i,sum = 0;
    for(i = 0;i < l;i++)
    {
        if(s1[i] != s2[i])
        {
            sum++;
            if(s1[i+1]=='*')
                s1[i+1]='o';
            else
                s1[i+1]='*';
        }
    }
    printf("%d",sum);
    return 0;
}

约数倍数选卡片

闲暇时,福尔摩斯和华生玩一个游戏:

在N张卡片上写有N个整数。两人轮流拿走一张卡片。要求下一个人拿的数字一定是前一个人拿的数字的约数或倍数。例如,某次福尔摩斯拿走的卡片上写着数字“6”,则接下来华生可以拿的数字包括:

1,2,3, 6,12,18,24 ....

当轮到某一方拿卡片时,没有满足要求的卡片可选,则该方为输方。

请你利用计算机的优势计算一下,在已知所有卡片上的数字和可选哪些数字的条件下,怎样选择才能保证必胜!

当选多个数字都可以必胜时,输出其中最小的数字。如果无论如何都会输,则输出-1。

Input

输入数据为2行。第一行是若干空格分开的整数(每个整数介于1~100间),表示当前剩余的所有卡片。

第二行也是若干空格分开的整数,表示可以选的数字。当然,第二行的数字必须完全包含在第一行的数字中。

Output

输出保证必胜的选择方法!!当选多个数字都可以必胜时,输出其中最小的数字。如果无论如何都会输,则输出-1。


bool DFS(int k)
{
    vis[k]=1;
    for(int j=(int)GN[k].size()-1;j>=0;j--)
    {
        int v=GN[k][j];
        if(!vis[v])
        {
            if(DFS(v))
            {
                vis[k]=0;
                return false;
            }
        }
    }
    vis[k]=0;
    return  true;
}
 
int main()
{
    int i=0,j=0;
    memset(vis, 0, sizeof(vis));
    while(~scanf("%d",&a[i]))
    {
        // a[i++]=num;
        for(int k=0;k<i;k++)
        {
            if(a[k]%a[i]==0||a[i]%a[k]==0)
            {
                GN[i].push_back(k);
                GN[k].push_back(i);
            }
        }
        i++;
        if(getchar()=='\n')
            break;
    }
    while(~scanf("%d",&b[j++]))
    {
        if(getchar()=='\n')
            break;
       // b[j++]=num;
    }
    int flag=0;
    
         for(int k=0;k<i;k++)
             for(int l=0;l<j;l++)
        {
            if(b[l]==a[k])
               if(DFS(k))
               {
                   flag=1;
                   ans=min(ans,a[k]);
               }
        }
    if(flag)
        printf("%d\n",ans);
    else
        printf("-1\n");
    return 0;
}

错误票据

某涉密单位下发了某种票据,并要在年终全部收回。

每张票据有唯一的ID号。全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。

因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。

你的任务是通过编程,找出断号的ID和重号的ID。

假设断号不可能发生在最大和最小号。

Input

要求程序首先输入一个整数N(N<100)表示后面数据行数。

接着读入N行数据。

每行数据长度不等,是用空格分开的若干个(不大于100个)正整数(不大于100000),请注意行内和行末可能有多余的空格,你的程序需要能处理这些空格。

每个整数代表一个ID号。

Output

要求程序输出1行,含两个整数m n,用空格分隔。

其中,m表示断号ID,n表示重号ID

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

int convert(string s){
	stringstream str;
	int ex;
	str<<s;
	str>>ex;
	return ex;
}

int main(){ 
	int line; 
	cin>>line;
	vector<int> a;
	getchar();
	
	for(int i=0;i<line;i++){
		string s;
		getline(cin,s); 
		stringstream str(s);
		string s2;
		
		while(getline(str,s2,' ')){
			int ex;
			ex=convert(s2);
			a.push_back(ex);
		}
	}
	
	int result1,result2;
	sort(a.begin(),a.end());
	for(int i=1;i<a.size();i++){
		if(a[i]-a[i-1]==2)
		result1=a[i]-1;
		if(a[i]==a[i-1])
		result2=a[i];
	}
	cout<<result1<<" "<<result2;		
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值