您的位置:首页 > 其它

【解题报告】2014ACM/ICPC亚洲区北京站

2016-02-22 23:13 375 查看
题目链接

A.A Curious Matt(HDU5112)

分析

简单的实现题。需要注意的是,输入数据不是按照时间先后顺序给出的。

代码

#include <bits/stdc++.h>
using namespace std;

typedef pair <int, int> p;
int t, n, ti, xi;
double dx, dt, ans;
vector <p> a;

int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%d", &n);
a.clear();
for(int i = 0; i < n; i++) {
scanf("%d%d", &ti, &xi);
a.push_back(p(ti, xi));
}
sort(a.begin(), a.end());
ans = 0;
for(int i = 1; i < a.size(); i++) {
dt = a[i].first - a[i-1].first;
dx = a[i].second - a[i-1].second;
ans = max(ans, fabs(dx / dt));
}
printf("Case #%d: %.2f\n", kase, ans);
}
return 0;
}


B.Black And White(HDU5113)

分析

因为棋盘比较小,所以可以通过搜索和剪枝来得出解。由于这是约束满足问题,因次无法设计最优性剪枝。这样以来该怎么办呢?按照人解决填色问题的习惯,应该先拿可选次数比较多的颜色来填。事实上,在搜索中也可以用这种方法来剪枝,不仅容易实现,而且实际效果不错。其原理是让看起来更合理的选择先出现,这样能够大大地优化搜索的效率。

代码

#include <bits/stdc++.h>
using namespace std;

struct color {
int c, n;
bool operator < (const color& o) const {
return n > o.n;
}
};

const int maxn = 10, maxk = 30;
// 用于四个方向的坐标变换的常数组
const int d[2][4] = { {-1, 1, 0, 0}, {0, 0, -1, 1} };
int t, n, m, k, maxc, G[maxn][maxn];
color c[maxk];

// 判断当前的填色是否与之前的冲突
inline bool ok(int x, int y, int color) {
for(int i = 0; i < 4; i++) {
int dx = x + d[0][i];
int dy = y + d[1][i];
if(G[dx][dy] == color) {
return false;
}
}
return true;
}

bool dfs(int x, int y) {
// 枚举颜色
for(int i = 1; i <= k; i++) {
if(c[i].n > 0 && ok(x, y, c[i].c)) {
// 破坏现场
G[x][y] = c[i].c;
c[i].n--;
if(x == n && y == m) {
return true;
}
if(y < m && dfs(x, y + 1)) {
return true;
}
if(y == m && x < n && dfs(x + 1, 1)) {
return true;
}
// 还原现场
G[x][y] = 0;
c[i].n++;
}
}
return false;
}

int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
printf("Case #%d:\n", kase);
scanf("%d%d%d", &n, &m, &k);
maxc = 0;
for(int i = 1; i <= k; i++) {
scanf("%d", &c[i].n);
c[i].c = i;
maxc = max(maxc, c[i].n);
}
// 排序剪枝
sort(c + 1, c + k + 1);
if(maxc > (n * m + 1) / 2) {
puts("NO");
continue;
}
memset(G, 0, sizeof(G));
dfs(1, 1);
puts("YES");
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
printf("%d", G[i][j]);
putchar(j == m ? '\n' : ' ');
}
}
}
return 0;
}


D.Dire Wolf(HDU5115)

分析

刚开始的想法是,因为所有狼的基本攻击之和是一定会被主角承受的,因此或许会存在某种贪心策略能够只凭借额外攻击来判断先杀哪只狼。但是因为杀掉不同的狼,局面的变化会很大,因此也无法找出贪心策略。另外因为数据量比较大,所以用枚举排列的办法也将不奏效。那么或许可以用动态规划来解决?因为这是个线性结构上的问题,所以考虑区间动规。

设a, b为储存基本攻击力和额外攻击力的数组。考虑是否能根据小区间的最优解推出大区间大区间的最优解。先考虑某个长度为 11 的区间,将这个区间内的狼杀死的代价是固定的。再考虑长度为 22 的区间 [i,j][i, j] ,假设这段区间内有p, q两只狼,唯一影响这段区间的最优解的因素就是p和q谁先死,用方程来表述就是 d[i][j]=min(d[j][j]+a[i],d[i][i]+a[j])+b[i−1]+b[j+1]d[i][j] = min(d[j][j] + a[i], d[i][i] + a[j]) + b[i-1] + b[j + 1] ,max的第一项是p最后死的情况,第二项是q最后死的情况。接着考虑长度为 33 的区间 [i,j][i, j] ,由于我们已经缓存过长度为1和长度为2的区间的最优值,因此我们可以模仿上一步,去枚举排列。不过等等,不能真的去枚举排列。要是真的去枚举排列的话复杂度是我们无法承受的。事实上很多排列的枚举都是多余的。例如,如果我们要利用 d[i+1][j]d[i+1][j] 这个长度为 22 的子区间的结果的话,我们可以无视这个区间内的排列情况而直接使用它,因为它已经是这个长度为2的子区间的最优值了。观察上面给出的方程就不难发现,我们只需要枚举最后死的狼是哪一只,就能无条件利用之前的结果了。转移方程就可以描述为:

d[i][j]=max{b[i−1]+d[i][k]+a[k]+d[k][j]+b[j+1],i<k<j}d[i][j] = max \left\{ b[i-1] + d[i][k] + a[k] + d[k][j]+ b[j+1], i < k < j \right\} ,

注意 i=ki = k 或 j=kj = k 的时候需要特判(特判的转移方程类似描述长度为 22 区间时的方程)。这个方程可以推广到任意长度的区间。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 205;
int t, n, tmp, a[maxn], b[maxn], d[maxn][maxn];

int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
}
// 每端设置一只虚拟的狼
b[0] = b[n+1] = 0;
// 初始化动态规划要用的数组
for(int i = 1; i <= n; i++) {
d[i][i] = b[i-1] + a[i] + b[i+1];
for(int j = i + 1; j <= n; j++) {
d[i][j] = INT_MAX;
}
}
// 求出小区间的最优值以后才能求大区间的最优值
for(int len = 2; len <= n; len++) {
for(int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
for(int k = i; k <= j; k++) {
// 前两种情况为特判
if (i == k) tmp = a[k] + d[k+1][j];
else if(j == k) tmp = d[i][k-1] + a[k];
else tmp = d[i][k-1] + a[k] + d[k+1][j];
d[i][j] = min(d[i][j], b[i-1] + tmp + b[j+1]);
}
}
}
printf("Case #%d: %d\n", kase, d[1]
);
}
return 0;
}


H.Happy Matt Friends(HDU5119)

分析

枚举集合的子集,对子集中的数进行某种运算得到某个结果。最后考虑这些结果是否满足某种性质。这样的问题适合套用背包问题的模型。用数组 d[i][j]d[i][j] 记录第 ii 次决策(考虑第 ii 个数)后,异或和为j的情况有多少种。初始令 d[0][0]=1d[0][0] = 1 , 其他值为 00 ,逐行扫描 dd 矩阵,只要 d[i][j]=1d[i][j] = 1%就进行状态转移 d[i][ja[i]]=d[i−1][j]+d[i−1][j]d[i][ja[i]] = d[i-1][j] + d[i-1][j] ,表示对拿这个数进入子集和不拿这个数进入子集的情况数进行加和。最后的答案是 d[n][m]+d[n][m+1]+⋯+d[n][INF]d
[m] + d
[m+1] + \dots + d
[INF] 。本题可以用滚动数组优化空间复杂度,但是数组至少要有两行,因为两个数的异或和不一定会比这两个数大,或比两个数小,因此需要额外的一行来缓存对上一个数的运算结果。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 45, maxm = 11e5;
int t, n, m, ub, a[maxn];
ll ans, d[2][maxm];

int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%d%d", &n, &m);
ub = m;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
// 更新异或和的上界
ub |= a[i];
}
// 初始化数据区
for(int j = 1; j <= ub; j++) {
d[0][j] = 0;
}
d[0][0] = 1;
for(int i = 1; i <= n; i++) {
// 初始化缓冲区
for(int j = 0; j <= ub; j++) {
d[1][j] = 0;
}
for(int j = 0; j <= ub; j++) {
if(d[0][j]) {
d[1][a[i]^j] = d[0][j];
}
}
// 将缓冲区的值累加到数据区
for(int j = 0; j <= ub; j++) {
d[0][j] += d[1][j];
}
}
ans = 0;
for(int j = m; j <= ub; j++) {
ans += d[0][j];
}
printf("Case #%d: %I64d\n", kase, ans);
}
return 0;
}


I.Intersection(HDU5120)

分析

两个圆环相交以后的图形比较复杂,且情况比较多,难以用直接法求出。于是不妨用间接法,将图形画出来以后可以看出,圆环面积交 = 大圆面积交 - 2 * 大圆小圆面积交 + 小圆面积交。这里似乎用到了容斥的思想,不过因为比较简单,所以可以说本题没有用到容斥定理。这样,就把问题转化为求两圆面积交了,套模板即可(模板的原理是:设两圆交点为 a,ba, b ,两圆圆心分别为 o1,o2o_1, o_2 ,问题转化为求四边形 ao1o2bao_1o_2b 和扇形 o1abo_1ab 、 o2abo_2ab 的面积)。

代码

#include <bits/stdc++.h>
using namespace std;

const double eps = 1e-10, pi = 4 * atan(1.0);

int dcmp(double x) {
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}

struct Point {
double x, y;
Point() {}
Point(double x, double y): x(x), y(y) {}
double distance(Point p) {
return hypot(x - p.x, y - p.y);
}
};

struct Circle {
double r;
Point o;
Circle() {}
Circle(double r, Point o): r(r), o(o) {}
double area(Circle c) {
double d = o.distance(c.o);
if(dcmp(d - r - c.r) >= 0) {
return 0;
}
if(dcmp(d - fabs(r - c.r)) <= 0) {
double R = dcmp(r - c.r) < 0 ? r : c.r;
return pi * R * R;
}
double x = (r * r + d * d - c.r * c.r) / (2 * d);
double a1 = acos(x / r);
double a2 = acos((d - x) / c.r);
double s1 = a1 * r * r + a2 * c.r * c.r;
double s2 = r * d * sin(a1);
return s1 - s2;
}
};

int main() {
int t;
scanf("%d", &t);
double r, R, x1, y1, x2, y2;
for(int kase = 1; kase <= t; kase++) {
scanf("%lf%lf%lf%lf%lf%lf", &r, &R, &x1, &y1, &x2, &y2);
Point o1(x1, y1), o2(x2, y2);
Circle c1(r, o1), C1(R, o1);
Circle c2(r, o2), C2(R, o2);
double s1 = C1.area(C2);
double s2 = C1.area(c2);
double s3 = c1.area(c2);
printf("Case #%d: %.6f\n", kase, s1 - 2 * s2 + s3);
}
return 0;
}


K.K.Bro Sorting(5122)

分析

最优的策略实际上是一个贪心策略,每趟交换都把当前没归位的最大数归位。于是我们可以从后往前遍历数列,统计在每个数之后有多少个比它小的数。统计结果的和就是答案。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e6 + 10;
int t, n, m, ans, a[maxn];

int main() {
scanf("%d", &t);
for(int kase = 1; kase <=t; kase++) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
m = a
;
ans = 0;
for(int i = n - 1; i > 0; i--) {
if(a[i] > m) {
ans++;
}
else {
m = a[i];
}
}
printf("Case #%d: %d\n", kase, ans);
}
return 0;
}


(其它题目略)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: