T1 小奇挖矿
题目背景
小奇要开采一些矿物,它驾驶着一台带有钻头(初始能力值w)的飞船,按既定路线依次飞过喵星系的n个星球。
问题描述
星球分为2类:资源型和维修型。(p为钻头当前能力值)
- 资源型:含矿物质量 ,若选择开采,则得到 的金钱,之后钻头损耗 ,即 。
- 维修型:维护费用 ,若选择维修,则支付 的金钱,之后钻头修复 ,即 。
注:维修后钻头的能力值可以超过初始值。
请你帮它决策最大化这个收入。
输入格式
第一行4个整数
。
以下n行,每行2个整数
。
为1则代表其为资源型星球,
为其矿物质含量
;
为2则代表其为维修型星球,
为其维护费用
;
输出格式
输出一行一个实数(保留两位小数),表示要求的结果。
样例输入
5 50 50 10
1 10
1 20
2 10
2 20
1 30
样例输出
375.00
数据范围
对于30%的数据
对于50%的数据
对于100%的数据
保证答案不超过
。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
double n,k,c,w,ans;
int type[MAXN],x;
int a[MAXN],b[MAXN];
void init()
{
scanf("%lf%lf%lf%lf",&n,&k,&c,&w);
for (int i = 1;i <= n;++i)
{
scanf("%d%d",&type[i],&x);
if (type[i] == 1) a[i] = x;
else b[i] = x;
}
k = 1 - 0.01 * k;
c = 1 + 0.01 * c;
}
void work()
{
for (int i = n;i >= 1;--i)
{
if (type[i] == 1) ans = max(ans,ans * k + a[i]);
else ans = max(ans,ans * c - b[i]);
}
printf("%.2lf",ans * w);
}
int main()
{
init();
work();
return 0;
}
分析
分析主要来源,作者进行修改补充。(感觉改的不成样子了 )
-
这题最难理解的就是为什么要倒着来dp,原因是当前的解是有后效性的。
我们知道,动态规划的状态必须满足无后效性,而在本题中,假设有这几个点: A[i]、A[i+1]、 A[i+3]、A[n]。
扫描二维码关注公众号,回复: 5563129 查看本文章假设现在开采A[i]。开采之后,钻头能力值会下降。我们知道收益是 ,所以能力值越高,收益也就越高。如果A[i]为当前开采所得到收益,开采后能力值下降,那么往后A[i+1] A[i+2]…A[n]的收益都会下降。
我们将降低后的收益+A[i]所得到的收益与没降低之前所得到的收益取最大值, 得到的就是当前最优解。
-
其次,我们先把w个钻头分成w个小钻头,也就是说我们只看用1个耐久度所能获得的最大收益,最后ans*w就是答案。
为什么这样是正确的呢?因为当前钻头的能力值和以后的收益是成正比的,假设我们每次只有1的耐久度,这样我们找到了一种最优方案,那么当耐久度为w时,最优方案是不变的。
就像用线段树维护区间最大值,当区间全都加上或乘上某个值后,最大值(是哪个数)不变一样。
-
第三,搞清楚ans的定义 :ans代表经过星球i~n的最大收益。转移方程:
然而这些东西都基于for循环是由 的,我们要倒着来dp,这样我们就能把A[i]后面的最优解算出来与A[i]的解比较。
T2 小奇的数列
问题描述
给定一个长度为n的数列,以及m次询问,每次给出三个数l,r和P,询问 的最小值。其中 ,即模意义下的区间子串和最小值。
输入格式
第一行包含两个正整数n和m,表示数列的长度和询问的个数。
第二行为n个整数,为a[1]…a[n]。
接下来m行,每行三个数l,r和P,代表一次询问。
输出格式
对于每次询问,输出一行一个整数表示要求的结果。
样例输入
4 2
8 15 9 9
1 3 10
1 4 17
样例输出
2
1
数据范围
对于20%的数据 n<=100,m<=100,p<=200
对于40%的数据 n<=200,m<=1000,p<=500
对于70%的数据 n<=100000,m<=10000,p<=200
对于100%的数据 n<=500000,m<=10000,p<=500,1<=a[i]<=10^9
参考代码(90pts)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 500005;
int n,m,li,ri,pi;
long long ans;
long long seq[MAXN],pre[MAXN];
//1e9加两次就爆int了,怎么可能不开long long嘛......
template<class T> inline void qread(T &sum)
{
sum = 0;
register int sym = 1;
register char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-') sym = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
sum = (sum << 1) + (sum << 3) + ch - '0';
ch = getchar();
}
sum *= sym;
}
void init()
{
scanf("%d%d",&n,&m);
for (int i = 1;i <= n;++i) qread(seq[i]);
for (int i = 1;i <= n;++i)
pre[i] = (pre[i - 1] + seq[i]);
}
void work()
{
qread(li);
qread(ri);
qread(pi);
if (ri - li + 1 > pi) //抽屉原理。
{
printf("0\n");
return;
}
ans = LLONG_MAX;
for (int i = li;i <= ri;++i)
for (int j = i;j <= ri;++j)
{
ans = min(ans,(pre[j] - pre[i - 1]) % pi);
if (ans == 0) //剪枝。
{
printf("0\n");
return;
}
}
printf("%lld\n",ans);
}
int main()
{
init();
while (m--) work();
return 0;
}
分析
-
基本思想:暴力枚举所有区间,求和,取模。
-
要求区间和,就应该想到用前缀和,枚举左右端点显然是不现实的。
-
但是这样还不够快,需要剪枝:
假设我们对10取模,显然,如果某个区间长度大于等于10,必然有两个数取模后结果是一样的,这就是抽屉原理。那么这两个数相减后,必然是10的倍数,取模后结果为0,而0一定是最小的,直接输出即可。取模数为 时同理。
同样的,如果在枚举时枚举出了0,直接输出即可。
-
考虑到每个数最大为 ,三个数相加就会爆int,所以必须开long long。
正解Splay见upd。
T3 小奇回地球
问题描述
有标号为1的星球到标号为n的星球,某一些星球之间有航线。由于超时空隧道的存在,从一个星球到另一个星球时间可能会倒流,而且,从星球a到b耗费的时间和星球b到a耗费的时间不一定相同。
宇宙法规定:“禁止在出发时间前到达目的地。”
每艘飞船上都有速度调节装置,可以调节飞行的时间。其功能可以使得整次航程中所有两星球间的飞行时间增加或减少相同的整数值。你的任务是调整速度调节器,找出一条最短时间到达目的地的路径。
输入格式
输入文件包含多组数据,第1个数为T,表示数据组数。
对于每组数据,输入第1行为两个正整数n,m,为星球的个数和星球间的路线数。接下来m行,每行三个整数i,j和t,表示由星球i到星球j飞行的时间为t。由i到j最多只会有一条飞行线路。
输出格式
输出文件共T行,每组数据输出一行。
如果可以通过调节速度调节器完成任务,则输出一个非负整数,表示由星球1到星球n的最短时间。(注意最短时间要大于或者等于0)。
如果不能由星球1到达星球n,则输出-1。
样例输入
1
4 5
1 2 1
1 3 1
2 3 -3
3 1 1
3 4 1
样例输出
2
样例解释
把速度控制器的值设为1,相当于每个时间值加1,得到的最短路径为1→2→3→4,所需时间为2+(-2)+2=2。
数据范围
1,2号测试点,保证所有星球出度不超过1
3,4号测试点,n<=10
5,6号测试点,-100<=t<=100
对于100%的数据T<=10,n<=100,m<=n*(n-1),-100000<=t<=100000
数据随机和构造结合生成。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105,MAXM = 100005;
struct edge
{
int to,nxt,wgt;
}info[MAXM];
int n,m,e,t,ui,vi,wi;
int head[MAXN],dis[MAXN],vis[MAXN],cnt[MAXN],avaliable[MAXN];
queue<int> Q;
void addedge(int from,int to,int wgt)
{
info[++e].to = to;
info[e].wgt = wgt;
info[e].nxt = head[from];
head[from] = e;
}
void init()
{
qread(n);
qread(m);
memset(head,0,sizeof(head));
e = 0;
for (int i = 1;i <= m;++i)
{
qread(ui);
qread(vi);
qread(wi);
addedge(ui,vi,wi);
addedge(vi,ui,wi);
}
}
bool spfa(int limit)
{
memset(dis,0x3f,sizeof(dis));
memset(cnt,0,sizeof(cnt));
memset(vis,false,sizeof(vis));
Q.push(1);
vis[1] = true;
dis[1] = 0;
while (!Q.empty())
{
int u = Q.front();
vis[u] = false;
Q.pop();
for (int i = head[u];i;i = info[i].nxt)
{
if (i % 2 == 0) continue;
int v = info[i].to;
if (avaliable[v] == false) continue;
if (dis[v] > dis[u] + info[i].wgt + limit)
{
cnt[v]++;
if (cnt[v] > n) return false;
dis[v] = dis[u] + info[i].wgt + limit;
if (!vis[v])
{
Q.push(v);
vis[v] = true;
}
}
}
}
return dis[n] >= 0 && dis[n] != 0x3f3f3f3f;
}
void prepare(int u,int fa)
{
avaliable[u] = true;
vis[u] = true;
for (int i = head[u];i;i = info[i].nxt)
{
if (i % 2 == 1) continue;
int v = info[i].to;
if (v == fa || vis[v]) continue;
prepare(v,u);
}
}
void work()
{
int l = -100000;
int r = 100000;
int mid = (l + r) >> 1;
int ans = 0x3f3f3f3f;
while (l <= r)
{
if (spfa(mid))
{
ans = dis[n];
r = mid - 1;
}
else l = mid + 1;
mid = (l + r) >> 1;
}
printf("%d\n",ans == 0x3f3f3f3f ? -1 : ans);
}
int main()
{
scanf("%d",&t);
while (t--)
{
init();
prepare(n,0);
if (!spfa(100000))
{
printf("-1\n");
continue;
}
work();
}
return 0;
}
分析
-
cnt[MAXN]
:统计每个节点被松弛的次数,用来判负环。若某个节点被松弛超过n(n为节点数量)次,则必定存在负环。 -
avaliable[MAXN]
:记录每个节点是否可用。 -
朴素的方法是:二分答案+SPFA,如果求得出最短路,则将速度控制器的值减小,以求最小时间花费。
但是,若速度控制器的值太小,以至于出现了负环而无法求得最短路,就要将值调大一些。
如果图不联通,自然输出-1了。
-
但是你以为这样就完了吗?看看这个 :
答案显然是5,但是按照上面的方法,我们会得出105的结果,这是因为存在负环,程序不断的向上调节速度控制器的值,直到没有负环为止。
可是我们发现,本组数据中的负环并不会对1到4的最短路造成影响,因为负环不存在于1到4的最短路上,为了避免其对计算造成影响,我们可以预先建立反向边,从目标节点开始DFS,将目标节点无法走到的节点剔除,因为这些节点一定不存在1到它的最短路上。
然后就可以SPFA了