贪心算法的定义:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心算法思想:
顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似
解题的一般步骤是:
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;
}