目录
夹角有多大
translation:
给定时、分、秒,要求你求出时针和分针之间的角度。
problem:
- 时分秒也要实时匹配,也就是说,分钟走一点,也是会影响是针的角度的。同理,秒针也是。
- 当时卡在了角度要保证在【0,180】之间。假如时针走的很多,分针走的很少,那他们之间的角度比如为30度,但如果以圆点到 0 点位起始轴计算角度的话,那计算出来的结果是330,此时我想的是对180取模,可这样是得到的是150呀,那30和150之间还有转换,这.... 反正对角度的问题很菜就是了..
当角度k大于180时, k = 360 - k
// problem :
#include <bits/stdc++.h>
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;
}
汉诺塔3/4
汉诺塔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 <bits/stdc++.h>
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 <bits/stdc++.h>
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 <bits/stdc++.h>
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 <bits/stdc++.h>
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;
}
Subset sequence
红色病毒问题
problem: 只能说是毫无头绪
solve:涉及什么生成函数,直接记住公式了。 4^(n-1) + 2^(n-1)
// problem :
#include <bits/stdc++.h>
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 <bits/stdc++.h>
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<node> 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 <bits/stdc++.h>
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;
}
RPG的错排
problem:怎么说呢,这题错细节上了,有两个地方没有注意到
- factorial[21] 就会爆 long long 排列组合时,要先除
- m个人选对,那么就有n-m个人全错排,但是给写成了 m个人全错排
solve:
需要注意,选对n - 1个人 和 选对 n 个人是同一种情况,所以我们直接舍弃这两种情况,最后加1即可。
// problem :
#include <bits/stdc++.h>
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;
}
Coin Change
题目大概就是跟找零钱花费最少的硬币差不多, 不过这里的题意是要求你求出有多少种凑够零钱的方式,并且所花费的硬币数量不能超过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 <bits/stdc++.h>
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;
}