目录
translation:
给定时、分、秒,要求你求出时针和分针之间的角度。
problem:
- 时分秒也要实时匹配,也就是说,分钟走一点,也是会影响是针的角度的。同理,秒针也是。
- 当时卡在了角度要保证在【0,180】之间。假如时针走的很多,分针走的很少,那他们之间的角度比如为30度,但如果以圆点到 0 点位起始轴计算角度的话,那计算出来的结果是330,此时我想的是对180取模,可这样是得到的是150呀,那30和150之间还有转换,这.... 反正对角度的问题很菜就是了..
当角度k大于180时, k = 360 - k
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
- int main(){
- int t; scanf("%d", &t);
- while(t--){
- double h, m, s; scanf("%lf %lf %lf", &h, &m, &s);
- if(h >= 12)h -= 12;
-
- double k1 = (h + m / 60 + s / 3600) * 360 / 12;
- double k2 = (m + s / 60) * 360 / 60;
-
- double ans = fabs(k1 - k2);
- if(ans > 180)ans = 360 - ans;
- int res = ans;
- printf("%d\n", res);
- }
-
- return 0;
- }
汉诺塔4是在3的基础上添加了额外条件,在此先介绍一下汉诺塔3
A B C
—— —— ——
做递归的题,找对递归表达的状态是最关键的一步。
比如这题的。
设 solve(n) 表示 : 将A盘上的n个盘子借助 B 盘 放到 C盘 (其中AC盘是可以互换的)
那么问题的求解可以表示为: solve(n) = solve(n - 1) + 1 + solve(n - 1) + 1 + solve(n - 1)
其中的 + 1 操作是移动最底下的那个盘子。
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
-
- ll solve(int n){
- ll ans = 0;
- if(n == 1)return 2;
- ll k = solve(n - 1);
- ans += k;
- ans++;
- ans += k;
- ans++;
- ans += k;
- return ans;
- }
-
- int main(){
- int n;
- while(~scanf("%d", &n)){
- printf("%lld\n", solve(n));
- }
-
- return 0;
- }
-
汉诺塔3会做,汉诺塔4多了个条件,就不会了(ಥ﹏ಥ)
translation:
多了个条件,允许最大的盘子放在最上面。这里的题意(是指n个里最大,还是那一堆里最大的可以放最上面???,反正但是是挺疑惑)
problem:
这里只有第n个盘子能放在其他盘子上面, 比如n=5 , 有一堆是 1 3 2 那3是不能放在最上面的! 所以我们移动上面的盘子时,还是要用 汉诺塔3的代码。
用递归其实复杂度是不高的,也是O(n)的时间复杂度。而且还好理解很多,只不过代码篇幅可能较长 ( 那也只是我一步一步写,完全可以把代码合并
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
-
-
- ll solve1(int n){
- if(n == 1)return 2;
- ll ans = 0;
- ll k = solve1(n - 1);
- ans += k;
- ans++;
- ans += k;
- ans++;
- ans += k;
- return ans;
- }
-
- ll solve2(int n){
-
- if(n == 1)return 2;
- if(n == 2)return 4;
- ll ans = 0;
- ll k = solve1(n - 2);
- ans += k;
- ans += 2;
- ans += k;
- ans += 2;
- ans += k;
- return ans;
- }
- int main(){
- int t; scanf("%d", &t);
- while(t--){
- int n; scanf("%d", &n);
- printf("%lld\n", solve2(n));
- }
- return 0;
- }
problem:
搜索、背包? 感觉偏背包的可能性较大
solve:多重背包问题,但又有点特殊 要先枚举背包容量,否则会重复
先给出AC代码:
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
-
- int f[45];
- int main(){
- int t; scanf("%d", &t);
- while(t--) {
- int n, k; scanf("%d %d", &n, &k);
- memset(f, 0, sizeof(f));
- f[0] = 1;
- while(k--) {
- int a, b; scanf("%d %d", &a, &b);
- for(int j = n; j >= a; --j)
- for(int m = 1; m <= b && j >= m * a; ++m)
- f[j] += f[j - a * m];
-
- }
- cout << f[n] << endl;
- }
-
- return 0;
- }
为什么说特殊呢?因为这题不能用二进制拆分优化!
- for (int i = 1; i <= n; i++)
- for (int j = V; j >= w[i]; j--)
- for (int k = 1; k <= s[i] && k * w[i] <= j; k++)
- f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);
先枚举背包的容量,然后每种情况都放一遍 可以达到多重背包的效果。
先看看二进制拆分的代码
- for(int i = 1; i <= n; ++i){
- int num = min(s[i], V / w[i]);
- for(int k = 1; num; k <<= 1){
- if(k > num)k = num;
- num -= k;
- for(int j = V; j >= k * w[i]; --j)
- f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
- }
- }
这里是先枚举二进制数,再遍历背包的容量。V这个背包先放入1个, 下一轮2个... ,最终1+2+...+2^n + k 达到背包存放的最大个数(num)。 为什么二进制数会叠加?因为较小的背包已经放入了物品。
但我们“选课时间”这个题目是要先枚举背包的,要不然会有重复计算。
那我们可以写先遍历背包,再枚举数量的二进制优化吗?答案是不行的。
- for(int i = 1; i <= n; ++i){
- for(int j = V; j >= w[i]; --j){
- int num = min(s[i], j / w[i]);
- for(int k = 1; num; k <<= 1){
- if(k > num)k = num;
- num -= k;
- f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
- }
- }
- }
这份代码达到的是 V 这个背包容量 放1个能不能最大, 2 个 …… 2^n 个,能不能最大。但单个的2^n 又不能组成 num 个, 所以是不行的。
problem: 一开始的想法是贪心,但要怎么贪呢?很关键的一点就是到达一个充电站之后,可以选中充电,或者选中不充电。当时没有去做,毕竟连考题的是贪心还是DP都没分清。
solve: 本题用DP解决。关键点还是充电站要不要充电问题。还是感觉很巧妙吧!
设 f[i] 为到第i个站点所花费的最少时间。接下来要确定状态转移方程;到第i个点,那肯定也经过了之前的点,那么 f[i] = f[j] + time(i,j); 可是这怎么体现充电站要不要充电问题?
j --> i ,中间可能有站,所以中间的站就是不充电的站,j是充电的(除了起始点)。
分析完也不难,关键就是:
- 先清楚这是个DP题;
- f[i] 代表什么意思;
- 列出状态转移方程。
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
-
- int l, n, c, t, p[105];
- double vr, vt1, vt2;
- double f[105];
- int main(){
- while(~scanf("%d", &l)) {
- scanf("%d %d %d", &n, &c, &t);
- scanf("%lf %lf %lf", &vr, &vt1, &vt2);
- for(int i = 1; i <= n; ++i)
- scanf("%d", &p[i]);
- p[n + 1] = l;
- memset(f, 0, sizeof(f));
- f[1] = 0;
- for(int i = 2; i <= n + 1; ++i){
- double minn = 1 << 30;
- for(int j = 1; j < i; ++j){
- double time = f[j];
- if(j != 1)
- time += t;
- int len = p[i] - p[j];
- if(len > c)
- time += c * 1.0 / vt1 + (len - c) * 1.0 / vt2;
- else
- time += len * 1.0 / vt1;
- minn = min(minn, time);
- }
- f[i] = minn;
- }
- double res = l * 1.0 / vr;
- if(res < f[n + 1])puts("Good job,rabbit!");
- else puts("What a pity rabbit!");
- }
-
- return 0;
- }
problem: 只能说是毫无头绪
solve:涉及什么生成函数,直接记住公式了。 4^(n-1) + 2^(n-1)
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
- const int p = 100;
- // ans=4^(n-1)+2^(n-1)
- ll rp(ll now, ll k){
- ll res = 1;
- for(; k; k >>= 1, now *= now, now %= p)
- if(k & 1)
- res *= now, res %= p;
- return res;
- }
- int t;
- int main(){
- while(~scanf("%d", &t) && t) {
- for(int i = 1; i <= t; ++i){
- ll n; scanf("%lld", &n);
- printf("Case %d: %lld\n", i,(rp(4, n - 1) + rp(2, n - 1)) % p);
- }
- putchar(10);
- }
- return 0;
- }
这是个多起点,多重点的问题,而且,按照题目的样例,家离起点的距离都是0,那么我们就可以设置一个超级起点,这个起点到普通起点连一条权重为0的边,以超级起点为dijkstra搜索的起点即可。就算家里各个起点的距离不为0,甚至各不相同,也可以采用这个方法。(先假设距离都是0,再将最终算得的距离加上家离起点最短的距离即可。)
problem:写代码时, memset(head, 0, sizeof(127)); 导致超时多次,
其次,还有一种题型是 在地图上找一个点,求到 p1、p2、p3...pn 的最短路径和
要对每个pi 做一次dijkstra
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
- int t, s, d;
- struct edge{
- int to, w, next;
- }e[100005];
- int cnt, head[1005], dis[1005];
- inline void makelist(int u, int v, int w){
- e[++cnt].to = v;
- e[cnt].next = head[u];
- e[cnt].w = w;
- head[u] = cnt;
- }
- struct node{
- int id, _dis;
- bool operator < (const node & a) const {
- return _dis > a._dis;
- }
- };
- bool vis[1005];
- void dijkstra(int _s){
- dis[_s] = 0;
- priority_queue
q; - q.push({_s, dis[_s]});
- while(!q.empty()){
- node k = q.top();
- q.pop();
- int id = k.id;
- if(vis[id])continue;
- vis[id] = true;
- for(int x = head[id]; x; x = e[x].next){
- int to = e[x].to;
- int w = e[x].w;
- if(vis[to])continue;
- if(w + dis[id] < dis[to]){
- dis[to] = w + dis[id];
- q.push({to, dis[to]});
- }
- }
- }
- }
- void init(){
- memset(vis, false, sizeof(vis));
- memset(dis, 127, sizeof(dis));
- memset(head, 0, sizeof(head));
- cnt = 0;
-
- }
- int main(){
- while(~scanf("%d %d %d", &t, &s, &d)){
- init();
-
- for(int i = 1; i <= t; ++i){
- int x, y, time; scanf("%d %d %d", &x, &y, &time);
- makelist(x, y, time);
- makelist(y, x, time);
- }
-
- for(int i = 1; i <= s; ++i){
- int x; scanf("%d", &x);
- makelist(1001, x, 0);
- }
-
- int dd[1005];
- for(int i = 1; i <= d; ++i)
- scanf("%d", &dd[i]);
-
- int ans = 1 << 30;
-
- dijkstra(1001);
- for(int i = 1; i <= d; ++i)
- ans = min(ans, dis[dd[i]]);
-
- printf("%d\n", ans);
- }
-
- return 0;
- }
problem: 本身问题是不难的,甚至还有点简单,但就是样例给出的数据输出有点大病。
姑且认为是考察观察能力了。 output: 第几个测试样例 棋盘大小 答案
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
- int n;
- ll b[40][40];
- ll dfs(int x, int y){
- if(b[x][y])return b[x][y];
- if(x == n || y == n)return 1;
- ll ans = 0;
-
- ans += dfs(x + 1, y);
- if(y < x)
- ans += dfs(x, y + 1);
- return b[x][y] = ans;
- }
- int main(){
- int c = 1;
- while(~scanf("%d", &n)){
- memset(b, 0, sizeof(b));
- if(n == -1)break;
- printf("%d %d %lld\n",c++, n, 2 * dfs(0, 0));
- }
-
- return 0;
- }
problem:怎么说呢,这题错细节上了,有两个地方没有注意到
- factorial[21] 就会爆 long long 排列组合时,要先除
- m个人选对,那么就有n-m个人全错排,但是给写成了 m个人全错排
solve:
需要注意,选对n - 1个人 和 选对 n 个人是同一种情况,所以我们直接舍弃这两种情况,最后加1即可。
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
- ll f[30];
- ll factorial[30];
-
- void solve(){
- // 阶乘
- factorial[0] = 1;
- for(int i = 1; i <= 25; ++i)
- factorial[i] = factorial[i - 1] * i;
-
- // 全错排公式
- f[1] = 0, f[2] = 1;
- for(int i = 3; i <= 25; ++i)
- f[i] = (i - 1) * (f[i - 1] + f[i - 2]);
- }
- int main(){
- int n;
- solve();
- while(~scanf("%d", &n) && n){
- int k;
- if(n & 1) k = (n+1)/2;
- else k = n / 2;
- ll ans = 0;
-
- for(int m = k; m <= n - 2; ++m){
- // ans += factorial(n)/factorial(n - m)/factorial(m) * f[n - m];
- ll c = 1;
- for(int i = m + 1; i <= n; ++i)
- c *= i;
- // c = factorial(n) / factorial(m) ---> (m + 1) ... n 各位相乘
- ans += c / factorial[n - m] * f[n - m];
- }
- ans++;
- printf("%lld\n", ans);
- }
- return 0;
- }
题目大概就是跟找零钱花费最少的硬币差不多, 不过这里的题意是要求你求出有多少种凑够零钱的方式,并且所花费的硬币数量不能超过100个。
没看到题目wa了的代码:
- void solve(){
- f[0] = 1;
- for(int i = 0; i < 5; ++i)
- for(int j = type[i]; j <= 250; ++j)
- f[j] += f[j - type[i]];
- }
AC代码:(多加了一维数组,来限定条件)
- // problem :
-
- #include
- using namespace std;
- #define ll long long
- typedef pair<int, int> PII;
- #define pb push_back
- int type[5] = {1, 5, 10, 25, 50};
- int f[255][105];
- void solve(){
- f[0][0] = 1;
- for(int i = 0; i < 5; ++i)
- for(int j = 1; j <= 100; ++j)
- for(int k = type[i]; k <= 250; ++k)
- f[k][j] += f[k - type[i]][j - 1];
- }
-
- int main(){
- int n;
- solve();
- while(~scanf("%d", &n)){
- int ans = 0;
- for(int i = 0; i <= 100; ++i)
- ans += f[n][i];
- printf("%d\n", ans);
- }
-
- return 0;
- }