金属采集
人类在火星上发现了一种新的金属!这些金属分布在一些奇怪的地方,不妨叫它节点好了。一些节点之间有道路相连,所有的节点和道路形成了一棵树。一共有 n 个节点,这些节点被编号为 1~n 。人类将 k 个机器人送上了火星,目的是采集这些金属。这些机器人都被送到了一个指定的着落点, S 号节点。每个机器人在着落之后,必须沿着道路行走。当机器人到达一个节点时,它会采集这个节点蕴藏的所有金属矿。当机器人完成自己的任务之后,可以从任意一个节点返回地球。当然,回到地球的机器人就无法再到火星去了。我们已经提前测量出了每条道路的信息,包括它的两个端点 x 和 y,以及通过这条道路需要花费的能量 w 。我们想花费尽量少的能量采集所有节点的金属,这个任务就交给你了
利用树状动态规划解题
动态规划就是解决多阶段决策最优化问题的一种思想方法。
阶段:将所给问题的过程,按时间或空间特征分解成若干相互联系的阶段,以便按次序去求每阶段的解。
状态:各阶段开始时的客观条件叫做状态。
决策:当各段的状态取定以后,就可以做出不同的决定,从而确定下一阶段的状态,这种决定称为决策。
策略:由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
状态转移方程:前一阶段的终点就是后一阶段的起点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由k阶段到k+1阶段状态的演变规律,称为状态转移方程。
树的特点与性质:
1、 有n个点,n-1条边的无向图,任意两顶点间可达
2、 无向图中任意两个点间有且只有一条路
3、 一个点至多有一个前趋,但可以有多个后继
4、 无向图中没有环;
思路
看到这个问题,我们首先应该想到的是这道题是否属于动态规划,而这里我们发现,结合问题,如果整棵树的权值最大,必然有左子树的权值最大,右子树的权值也最大,符合最优性原理。所以是动态规划。
而却不是一道树规的题目。因为我们可以用区间动规的模型解决掉:直接定义一个f[i][j]表示从i到j的最大值,则f[i][j]=max(f[i][k-1]*f[k+1][j]+a[k]),枚举k即可。接下来是如何建树的问题,只有把树建好了,才能输出其前序遍历。于是,我们看到了两个关键词:二叉树,中序遍历。有了这两个关键词,加上区间动规,这棵树就能建起来了。根据二叉树的特性来建树(这里不再具体讨论树的详细的构造了,中序遍历和前序遍历不懂得自己百度)。所以这颗树的前序遍历,只需要边动规边记录下root[i][j]=k表示i到j的根为k即可确定树的构造。
核心代码
inline void add(int u,int v,int w)
{
e[tot].next=head[u],e[tot].to=v,e[tot].w=w,head[u]=tot++;
}
inline void dfs(int u,int f)
{
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to==f) continue;
dfs(to,u);
for(int r=k;r>=0;r--)
{//如果顺序的话,前面结果改变会影响后面的情况。
dp[u][r]+=dp[to][0]+2*e[i].w;//特殊情况
for(int j=1;j<=r;j++) dp[u][r]=min(dp[u][r],dp[u][r-j]+dp[to][j]+j*e[i].w);
}
}
}
铺地毯
问题描述:
为了准备一个学生节,组织者在会场的一片矩形区域(可看做是平面直角坐标
系的第一象限)铺上一些矩形地毯。一共有n 张地毯,编号从1 到n。现在将这些地毯按照
编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。
地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形
地毯边界和四个顶点上的点也算被地毯覆盖。
输入:
输入共 n+2 行。
第一行,一个整数 n,表示总共有n 张地毯。
接下来的 n 行中,第i+1 行表示编号i 的地毯的信息,包含四个正整数a,b,g,k,每
两个整数之间用一个空格隔开,分别表示铺设地毯的左下角的坐标(a,b)以及地毯在x
轴和y 轴方向的长度。
第 n+2 行包含两个正整数x 和y,表示所求的地面的点的坐标(x,y)。
HINT:时间限制:1.0s 内存限制:256.0MB
对于 30%的数据,有n≤2;
对于 50%的数据,0≤a, b, g, k≤100;
对于 100%的数据,有0≤n≤10,000,0≤a, b, g, k≤100,000。
1 0 2 3
0 2 3 3
2 1 3 3
4 5
#include<iostream>
using namespace std;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int a[10001],b[10001],g[10001],k[10001];
for(int i=0;i<n;i++)
{
scanf("%d%d%d%d",&a[i],&b[i],&g[i],&k[i]);
}
int x,y;
scanf("%d%d",&x,&y);
int ans=0;
for(int i=0;i<n;i++)
{
if(a[i]<=x&&a[i]+g[i]>=x&&b[i]<=y&&b[i]+k[i]>=y)ans=i+1;
}
if(ans==0)cout<<-1<<endl;
else cout<<ans<<endl;
}
return 0;
}
两条直线
问题描述
给定平面上n个点。
求两条直线,这两条直线互相垂直,而且它们与x轴的夹角为45度,并且n个点中离这两条直线的曼哈顿距离的最大值最小。
两点之间的曼哈顿距离定义为横坐标的差的绝对值与纵坐标的差的绝对值之和,一个点到两条直线的曼哈顿距离是指该点到两条直线上的所有点的曼哈顿距离中的最小值。
输入
第一行包含一个数n。
接下来n行,每行包含两个整数,表示n个点的坐标(横纵坐标的绝对值小于109)。
输出
输出一个值,表示最小的最大曼哈顿距离的值,保留一位小数。
HINT:时间限制:1.0s 内存限制:256.0MB
对于30%的数据,n<=100。
对于另外30%的数据,坐标范的绝对值小于100。
对于100%的数据,n<=105。
参考链接:蓝桥杯进阶两条直线 (二分)_XiaoboAc的博客-CSDN博客_蓝桥杯两条直线
/*
Keep clam Believe youself
*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<stack>
#include<set>
#include<cmath>
#define Xiaobo main
using namespace std;
const int maxn=2e5+7;
const int mod=1e9+7;
const double eps=1e-15;
const double pi=acos(-1);
const int INF=0x3f3f3f;
typedef long long ll;
ll read(){ll c = getchar(),Nig = 1,x = 0;while(!isdigit(c) && c!='-')c = getchar();if(c == '-')Nig = -1,c = getchar();while(isdigit(c))x = ((x<<1) + (x<<3)) + (c^'0'),c = getchar();return Nig*x;}
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b);}
struct st{
double x,y;
}d[maxn];
struct f{
double mx,mi;
}dpl[maxn],dpr[maxn];
bool cmp(st a,st b) {
if(a.x!=b.x) return a.x<b.x;
else return a.y<b.y;
}
double Max(double a,double b){return a>b?a:b;}
double Min(double a,double b){return a>b?b:a;}
bool ok(double mid,int n) {
mid*=2;
int i,j=0;
for(i=0;i<n;i++) {
while(j<n&&d[j].x-d[i].x<=mid) j++;//找出最大的xj使xj-xi<=m,所以在xi到xj的这段区间的范围
//是垂直线的范围之后找出[1,i-1]以及[j+1,n-1]范围内的最大的y和最小的y值,如果最大的y值减去最小的y值<=m,
//则说明这是水平线的范围,则进行下一轮二分找出中点,找出更优解。
double mx1=-1e10;
double mi1=1e10;
if(j!=n) {
mx1=Max(mx1,dpr[j].mx);
mi1=Min(mi1,dpr[j].mi);
}
if(i-1>=0) {
mx1=Max(mx1,dpl[i-1].mx);
mi1=Min(mi1,dpl[i-1].mi);
}
if(mx1-mi1<=mid) return true;
}
return false;
}
void init(int n) {
dpl[0].mi=dpl[0].mx=d[0].y;
for(int i=1;i<n;i++) {
dpl[i].mi=Min(dpl[i-1].mi,d[i].y);
dpl[i].mx=Max(dpl[i-1].mx,d[i].y);
}
dpr[n-1].mi=dpr[n-1].mx=d[n-1].y;
for(int i=n-2;i>=0;i--) {
dpr[i].mi=Min(dpr[i+1].mi,d[i].y);
dpr[i].mx=Max(dpr[i+1].mx,d[i].y);
}
}
int Xiaobo()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) {
int x,y;
x=read();
y=read();
d[i].x=x+y;//坐标转换
d[i].y=x-y;
}
sort(d,d+n,cmp);
init(n);
double l=0.0,r=mod,ans=0.0;
while(r-l>=0.01) {
double mid=(r+l)/2.0;
if(ok(mid,n)) {
r=mid;
ans=mid;
}
else l=mid;
}
printf("%.1f\n",ans);
}
扫雷
问题描述
扫雷游戏你一定玩过吧!现在给你若干个n×m的地雷阵,请你计算出每个矩阵中每个单元格相邻单元格内地雷的个数,每个单元格最多有8个相邻的单元格。 0<n,m<=100
输入
输入包含若干个矩阵,对于每个矩阵,第一行包含两个整数n和m,分别表示这个矩阵的行数和列数。接下来n行每行包含m个字符。安全区域用‘.’表示,有地雷区域用'*'表示。当n=m=0时输入结束。
输出
对于第i个矩阵,首先在单独的一行里打印序号:“Field #i:”,接下来的n行中,读入的'.'应被该位置周围的地雷数所代替。输出的每两个矩阵必须用一个空行隔开。
枚举算法
1,枚举算法的定义:
在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么该结论是可靠 的,这种归纳方法叫做枚举法。
2,枚举算法的思想是:
将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,保留合适的,舍弃不合适的。
3,使用枚举算法解题的基本思路如下:
(1)确定枚举对象、范围和判定条件。
(2)逐一枚举可能的解并验证每个解是否是问题的解。
4,枚举算法步骤:
(1)确定解题的可能范围,不能遗漏任何一个真正解,同时避免重复。
(2)判定是否是真正解的方法。
(3)为了提高解决问题的效率,使可能解的范围将至最小,
//每周一练/2
#include<stdio.h>
#define MAX 100
int fun(char M[MAX][MAX],int n, int m, int i, int j) { //当前点不是雷时,判断当前点周围雷的个数
int num=0;
//当前点周围即 i-1 ~ i+1,j-1 ~ j+1 ,(这里遍历了当前区域的9个点,包含当前点(肯定不是雷))
for(int a=i-1;a<=i+1;a++) {
for(int b=j-1;b<=j+1;b++) {
if(a>=0 && b>=0 && a<n && b<m && M[a][b]=='*') { //注意做边界判断
num++;
}
}
}
return num;
}
int main()
{
int n,m,count=1;
while(scanf("%d %d",&n,&m)==2 && n && m ) {
char Mines[MAX][MAX];
int i,j;
for(i=0;i<n;i++) { //注意一下二维字符数组的输入方式
scanf("%s",Mines[i]);
}
if(count>1) printf("\n"); //添加这一行代码测试才会AC,不加这一行状态会为Presentation erro,不太理解。。。
printf("Field #%d:\n",count);
//遍历二维数组
for(i=0;i<n;i++) {
for(j=0;j<m;j++) {
if(Mines[i][j]=='.') { //如果不是雷,执行fun函数返回当前点周围的雷的个数
printf("%d",fun(Mines,n,m,i,j));
}
if(Mines[i][j]=='*'){ //如果是雷,直接输出*
printf("*");
}
}
printf("\n");
}
count++;
}
return 0;
}
贪吃的大嘴
问题描述
有一只特别贪吃的大嘴,她很喜欢吃一种小蛋糕,而每一个小蛋糕有一个美味度,而大嘴是很傲娇的,一定要吃美味度和刚好为m的小蛋糕,而且大嘴还特别懒,她希望通过吃数量最少的小蛋糕达到这个目的.所以她希望你能设计一个程序帮她决定要吃哪些小蛋糕.
输入
先输入一行包含2个整数m、n,表示大嘴需要吃美味度和为m的小蛋糕,而小蛋糕一共有n种,下面输入n行,每行2个整数,第一个表示该种小蛋糕的美味度,第二个表示蛋糕店中该种小蛋糕的总数。
动态规划
#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int M=1e5;
int amount[M];
int dp[M];
int w[M];
const int INF= 0x7fffffff;
int main()
{
int m,n;
cin>>m>>n;
int count=0;
fill(dp, dp+M, INF);
for(int i =0;i<n;i++)
{
int x,y;
cin>>x>>y;
for(int j=0;j<y;j++)//将每块蛋糕的美味度存到数组w
{
w[count]=x;
count++;
}
}
for(int i =0;i<count;i++)
{
for(int j =m;j>=1;j--)//至于为什么要倒叙可参考0-1背包问题
{
if(j==w[i])//如果蛋糕美味度恰好为当前美味度则只需要一块蛋糕
dp[j]=1;
if(dp[j-w[i]]!=INF&&(j-w[i]>=0))
dp[j]=min(dp[j],dp[j-w[i]]+1);
}
}
if(dp[m]!=INF)
cout<<dp[m];
else
cout<<"><";
}