BestCoder Round #89 1002 && HDU 5945 详解(单调队列+DP)加一种错误的方法
2016-11-08 18:23
579 查看
Fxx and game
Accepts: 74Submissions: 1857
Time Limit: 3000/1500 MS (Java/Others)
Memory Limit: 131072/65536 K (Java/Others)
问题描述
青年理论计算机科学家Fxx给的学生设计了一款数字游戏。 一开始你将会得到一个数\:XX,每次游戏将给定两个参数\:k,tk,t, 任意时刻你可以对你的数执行下面两个步骤之一: 1.\:X = X - i(1 <= i <= t)1.X=X−i(1<=i<=t)。 2.\:2.若\:X\:X为\:k\:k的倍数,X = X / kX=X/k。 现在Fxx想要你告诉他最少的运行步骤,使\:X\:X变成\:11。
输入描述
第一行一个整数\:T(1\leq T\leq20)\:T(1≤T≤20)表示数据组数。 接下来\:T\:T行,每行三个数\:X,k,t(0\leq t\leq10^6,1\leq X,k\leq10^6)X,k,t(0≤t≤106,1≤X,k≤106) 数据保证有解。
输出描述
输出共\:T\:T行。 每行一个整数表示答案。
输入样例
2 9 2 1 11 3 3
输出样例
4 3
思路:这题,最开始可能想到的是贪心+BFS,策略就是每次都有两种操作,一个是到减去一个数i,i<t,如果这个数字能被k整除,可以整除。那对整体结果有用的就是x-i,i<t里面可以被k整除的数字跟i-t这个数字,能减掉的最大数字。为什么是这个策略呢,比如你t1 = x-i能被k整除,t2 = x - i不能被k整除,因为是倒着写的,所以他们的操作进行的步数是一样的,都是dp[x]的步数+1,那么t2就是完全没有用的,只是多+了1(除非他直接变成了1),而t1会有用,因为它可以整除k,会变小很多,相同的步数,肯定是前者更有用,然后就是x-t有用了,因为如果非要减一个数字的话,肯定是减去一个最大的数字让他更靠近1。这里为了优化,不能直接枚举t次i,而是直接枚举k的倍数。。。但是这个复杂度是很不稳定的,假设k=1,这样枚举k的倍数其实就相当于枚举i,直接炸掉了,比如:1000000
1 10000,每个队列都枚举1w个数字,因为每个数都是1的倍数啊,只有队列里有10000的时候才能出现1。。
代码:(手写队列,以为这样会快一点)
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6+5; bool book[maxn]; int x, k, t, num; struct node { int cur, s; node() {}; node(int cc, int ss): cur(cc), s(ss) {} }q[maxn]; int bfs(void) { int head = 1, tail = 1; q[tail++] = node(x, 0); while(1) { node u = q[head++]; int cur = u.cur, s = u.s; book[cur] = 1; //book数组是用来去重的 if(cur == 1) return s; int st = cur-t; //最小的数 if(st <= 1) { st = 1; return s+1; } if(!book[st]) q[tail++] = node(st, s+1), book[st] = 1; //把最小的存进去 if(cur%k==0 && !book[cur/k]) q[tail++] = node(cur/k, s+1), book[cur/k] = 1; //如果这个数可以被k整除,加入队列 int i = st/k; //枚举在x-t 到 x-1之间k的倍数 if(st%k) i++; //如果有余数,说明i*k < st,所以要++; for(; i <= cur/k; i++) //不能枚举i这样数据很大,直接枚举k的倍数 { if(i*k != cur && !book[i*k]) { q[tail++] = node(i*k, s+1); book[i*k] = 1; } } } } int main(void) { int T; cin >> T; while(T--) { memset(book, 0, sizeof(book)); num = 0; scanf("%d%d%d", &x, &k, &t); printf("%d\n", bfs()); } return 0; }
接下来就是单调队列写了。。。网上大神说这是个标准单调队列DP,做笔记。。
设\:F_i\:Fi表示将\:i\:i变为\:1\:1的最少步骤,如果\:k|i,F_i=min{F_j,F_{\frac{i}{k}}}k∣i,Fi=min{Fj,Fki},否则\:F_i=min{F_j}Fi=min{Fj},其中\:i-t\leq
j\leq i-1i−t≤j≤i−1。
用单调队列维护一下就好了。
时间复杂度\:O(n)O(n)。
这是BC官方高中生大神给的题解。。。orz。。用一个维护t+1个变化次数的递增单调队列,(因为这个数可以+上t个数,再加上本身一共t+1个)这样每次新进来的都等于队列头+1,因为队列头是他前面t个数变化次数最小的一个。。
理解下这个单调队列dp:这个转移方程就是dp[i] = min{min(dp[i-t],到dp[i-1], dp[i/k])},这个每个数的值本身就是他的下标,用单队记录变化次数,因为维护的范围是t+1个,所以后面的数字可以直接队首+1,因为队首是维护的次数最小的,因为是从前往后统计的,如果这个i|k的话就直接dp[i] = min(dp[i], dp[i/k]+1);
就行。。。。第一次接触,希望以后做这类题可以轻松一点。。
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> using namespace std; const int maxn = 1e6 + 5; struct node { int pos, val; //pos记录下标,val记录多少次数 }q[maxn]; int dp[maxn]; int main() { int x, t, k, T; scanf("%d", &T); while(T--) { scanf("%d%d%d", &x, &k, &t); int head = 1, tail = 1; q[head].pos = 1; //这里提前给优先队列存进去了一个数 q[head].val = 0; dp[1] = 0; //这个是记录每个数的最优解 for(int i = 2; i <= x; i++) { while(head <= tail && i - q[head].pos >= t + 1) head++; //一共t+1个元素 dp[i] = q[head].val + 1; //等于队首+1,因为他在与队首相隔t个范围内,可以直接+到 if(i % k == 0) dp[i] = min(dp[i], dp[i/k]+1); //加一个特判 while(head < tail && dp[i] < q[tail].val) tail--; //常规的单调队列了 tail++; q[tail].pos = i; q[tail].val = dp[i]; } printf("%d\n", dp[x]); } return 0; }
相关文章推荐
- HDU 5945 / BestCoder Round #89 1002 Fxx and game 单调队列优化DP
- hdu 5945 Fxx and game【dp+单调队列】
- HDU 5945 Fxx and game [单调队列+dp]【动态规划】
- HDU 5945 Fxx and game(DP+单调队列)
- 【hdu 5945】Fxx and game(递推|dp+单调队列)
- HDU 5945 维护一个单调队列 dp
- HDU 5945 Fxx and game(dp+单调队列优化)
- HDU_5945_Fxx and game_DP_单调队列
- HDU 5945 (线性dp+单调队列优化)
- hdu 5945 Fxx and game(单调队列,dp)
- hdu 5945 Fxx and Game dp(单调队列优化)
- 【HDU 5945】Fxx and game(DP+单调队列)
- hdu 5945 单调队列优化的DP
- hdu 5945 Fxx and game (dp+单调队列)
- BestCoderRound#89 HDU 5945 Fxx and game【单调队列+DP】
- HDU 5945 Fxx and game (DP+单调队列)
- 【hdu 5945 】 【dp+单调队列优化】Fxx and game【求数x最少经过多少次变换能变为1,(1)如果x%k==0,那么可以x=x/k。(2)x=x-i,(1<=i<=t)】
- hdu 5945 Fxx and game 单调队列优化dp
- HDU 5945 Fxx and game (DP+单调队列优化)
- HDU-5945 Fxx and game (dp+单调队列)