USACO training chapter1 入门/section1.4
花了好几天才把这个section写完,一直在肝浙大的数据结构,最短路给我写吐了,虽然不难,码量好大,抽空弄个专题。接下来打算写几道状压dp,挑战白书上的,很久没写了。来看题了,这次有几道题我个人觉得还是不错的。
A.Mixing Milk
很简单的一道题,是道贪心水题,我觉得这个题应该都能写出来的,就不详细讲了,就是按照价格排序。
/*
ID: b1703011
TASK: milk
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int max_n = 2e6;
struct F{
int v, m;
bool operator < (const F &f1) const {
return v < f1.v;
}
} milk[max_n + 10];
int main() {
freopen("milk.in", "r", stdin);
freopen("milk.out", "w", stdout);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++)
cin >> milk[i].v >> milk[i].m;
int res = 0;
sort(milk, milk + m);
for (int i = 0; i < m; i++) {
if (milk[i].m <= n) {
n -= milk[i].m;
res += milk[i].v * milk[i].m;
}
else {
res += n * milk[i].v;
break;
}
}
cout << res << endl;
return 0;
}
B.Barn Repair
贪心题,这个题直接想的话应该没啥思路,所以要反着想,首先最少的情况下也需要一块板子,就是直接盖住所有的畜栏,我们需要考虑的是,哪些地方并不需要修补,也就是哪里没有牛,那么我们就直接拿掉那一部分,这样板子数就会加1。如何考虑拿掉哪一部分呢,很明显,如果有两头牛之间的间距很大,那么这个地方就不是必须的,直接去掉这部分就好了。所以我们只需要先求出每相邻两头牛之间的间距,然后对间距大小排序,在板子数量允许的前提下,从大到小选取这个间距,然后在那里断开
更新答案。
上代码
/*
ID: b1703011
TASK: barn1
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int cows[210], diff[210];
int main() {
freopen("barn1.in", "r", stdin);
freopen("barn1.out", "w", stdout);
int m, s, c;
cin >> m >> s >> c;
for (int i = 0; i < c; i++) cin >> cows[i];
sort(cows, cows + c); // 牛的位置可能是乱序的。
for (int i = 1; i < c; i++)
diff[i] = cows[i] - cows[i-1] - 1; // 计算每两个相邻牛的间隔
sort(diff + 1, diff + 1 + c, greater<int>()); //从大到小排
int res = cows[c-1] - cows[0] + 1;
for (int i = 1; i < m; i++) res -= diff[i];
cout << res << endl;
return 0;
}
C.Prime Cryptarithm
小学乘法,我写的很繁琐,因为直接枚举了所有可能的集合,其实没必要这么蠢,直接枚举两个乘数就好了,然后判断,每个出现的数字用一个数组维护一下就能很快的知道一个数字是不是在集合里。
/*
ID: b1703011
TASK: crypt1
LANG: C++
*/
#include <iostream>
using namespace std;
int num[10], dic[10];
bool check(int f, int c) {
int cnt = 0;
while (f) {
int t = f % 10;
f /= 10;
if (dic[t] == 0) return false;
cnt++;
}
if (cnt != c) return false;
return true;
}
int main() {
freopen("crypt1.in", "r", stdin);
freopen("crypt1.out", "w", stdout);
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> num[i];
dic[num[i]] = 1;
}
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
for (int l = 0; l < n; l++) {
for (int r = 0; r < n; r++) {
int f = num[i] + num[j] * 10 + num[k] * 100;
int ans1 = f * num[l], ans2 = f * num[r];
if (check(ans1, 3) && check(ans2, 3)
&& check(ans1 + ans2 * 10, 4)) res++;
}
}
}
}
}
cout << res << endl;
return 0;
}
D.Combination Lock
很明显如果两个密码的上2下2范围内互不重叠,且n >= 5的话,方案是250种,n < 5的话是n^3种,因为随便找一个密码都在题目要求的范围内,都可以开锁,如果n >= 5但是两个密码的范围有重叠,方案就需要减少,减小的个数分别为两个密码在每一位的重叠的的数字之积,比如第一位有两个重叠的,第二为有1个,第三位有一个,那么就需要减少2 * 1 * 1这么多种方案。
/*
ID: b1703011
TASK: combo
LANG: C++
*/
#include <iostream>
#include <cstdio>
using namespace std;
int ans1[4], ans2[4], diff[4];
int main() {
freopen("combo.in", "r", stdin);
freopen("combo.out", "w", stdout);
int n;
cin >> n;
for (int i = 0; i < 3; i++) cin >> ans1[i];
for (int i = 0; i < 3; i++) cin >> ans2[i];
int res;
if (n >= 5) {
res = 250;
int d = -1;
for (int i = 0; i < 3; i++) {
diff[i] = min(abs(ans1[i]-ans2[i]), n-abs(ans1[i]-ans2[i]));
d = max(d, diff[i]);
}
// d <= 4说明有重叠的部分,如果两个数字上2下2没有重叠,差值是大于等于5的。
if (d <= 4) res -= (5 - diff[0]) * (5 - diff[1]) * (5 - diff[2]); //每个重叠部分之积。
}
else res = n*n*n;
cout << res << endl;
return 0;
}
E.Wormholes
思路是递归去生成两两组和的排列(配对的两个虫洞),然后模拟判断。
注意生成排列时(1, 3)与(3,1)是相同配对,(1, 2) (3,4)与(3,4)(1,2)是相同的情况,因此生成两两配对的组合时,需要注意顺序问题,都按照从大到小的顺序生成。那么怎么表示两个虫洞的配对情况呢,用一个par数组:par[i]=j,par[j]=i,表示i与j两个虫洞的配对,这样之后模拟的时候会方便很多。
如何模拟:我们可以知道,一个洞可以作为出口,也可以作为入口,也就是说一个洞最多可以访问两次,一旦超过两次,就说明成了虫洞之间成了一个环,就再也出不去了。(但是你只判断一次也能过 )
下面是代码
/*
ID: b1703011
TASK: wormhole
LANG: C++
*/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
struct P {
int x, y;
bool operator < (const P& p1) const {
return x < p1.x;
}
};
P p[20];
int n, res;
int par[20], used[20], vis[20];
bool find(int i) {
//牛每次只能沿着x+方向移动,一旦遇到下个虫洞就要进入!
for (int j = 0; j < n; j++) {
if(p[j].y != p[i].y || p[j].x <= p[i].x) continue;
if (vis[j] == 2) return true; //如果一个虫洞经过两次,凉凉
vis[j]++;
if (find(par[j])) return true;
else return false;
}
return false;
}
// 生成一个没有重复的两两排列。
void dfs(int cnt, int lst_i) {
if (cnt < n) {
//注意生成排列的顺序,j>i,i从上次枚举的位置开始(lst_i)。
for (int i = lst_i; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (used[i] || used[j]) continue;
used[i] = used[j] = 1;
par[i] = j, par[j] = i;
// cout << "cnt: " << cnt << " " << i << " " << j << endl;
dfs(cnt + 2, i + 1);
used[i] = used[j] = 0;
}
}
} else {
for (int i = 0; i < n; i++) {
memset(vis, 0, sizeof(vis));
vis[i]++;
if(find(par[i])) {
res++;
break;
}
}
}
}
int main() {
freopen("wormhole.in", "r", stdin);
freopen("wormhole.out", "w", stdout);
cin >> n;
for (int i = 0; i < n; i++)
cin >> p[i].x >> p[i].y;
sort(p, p + n); // 一定要按照x的坐标从小到大排序。
res = 0;
dfs(0, 0);
cout << res << endl;
return 0;
}
F. Ski Course Design
这个题也是,直接去想的话很没有思路,因为无法预知当前的高度的改变对之后的影响,但是我们可以知道一下几个事实:
1)我们可以通过补或者削来改变山峰的高度到任何值。
2)改变之后最终的情况就是最低峰与最高的高度之差小于等于17.
那么我们就可以构造出任意一种情况,暴力求解。
如何构造?枚举最低峰可能的高度,一旦最低高度确定下来,那么我们只需要把所有低于这个最低高度的山峰加上相应的高度来满足最低峰的高度,所有与最低峰的高度差大于17的山峰减去相应的高度来满足事实2)。
之后我们就需要确定枚举的高度的范围了,显而易见上界是原始数据中最大的,下界是最小的。
/*
ID: b1703011
TASK: skidesign
LANG: C++
*/
#include <iostream>
using namespace std;
int hill[1100];
int main() {
freopen("skidesign.in", "r", stdin);
freopen("skidesign.out", "w", stdout);
int n; cin >> n;
int low = 1<<30, high = -1;
for (int i = 0; i < n; i++) {
cin >> hill[i];
low = min(hill[i], low);
high = max(hill[i], high);
}
int res = 1<<30;
for (int h = low; h <= high; h++) {
int tmp = 0;
for (int i = 0; i < n; i++) {
if (hill[i] - h > 17) tmp += (hill[i]-h-17) * (hill[i]-h-17);
else if (hill[i] < h) tmp += (h-hill[i]) * (h-hill[i]);
}
res = min(res, tmp);
}
cout << res << endl;
return 0;
}
👋