前言:这次真的送命了,第一题脑抽了,第二题最小割建图又错了,第三题树形背包没调试出来,还把程序注释了。题目简单,5个人AK了,我该怎么办?这要到了真正比赛时我又会怎样?
a 星星
题目描述
天空中有N (1 ≤ N ≤ 400)颗星,每颗星有一个唯一的坐标(x, y), (1 ≤ x, y ≤ N)。请计算可以覆盖至少K(1 ≤ K ≤ N)颗星的矩形的最小面积。矩形的边必须平行于X轴或Y轴,长度必须为正整数。星如果在矩形的边上,也认为它是属于矩形内的。
输入格式
样例数据的第一行是两个整数N,K,代表星的数目和至少应该覆盖的数目。
接下来有N行,每行有两个整数x, y,代表星的坐标。
输出格式
输出一个整数,代表矩形的最小面积。
解题思路(尺取法+前缀和)
这题是签到题,但也是送命题。我考试时脑一抽,居然想到矩形框的左上角是星星(这明显是错的,假设一个菱形),这种题以前还做过的,我也不知道我怎么了。最近情绪低落,脑子也有毛病了。
考试时我想了个n^2logn的二分方法,甚至认为n^3的是暴力(数据太魔性),结果正解就是枚举两重,然后单调一重的n^3的解法。
直接讲正解吧,我都不好意思扯下去了。
枚举矩形的左边的边的y坐标和右边的边的y坐标,然后用尺取法枚举上下两条边的x坐标,检验框内有几个就用二维的前缀和就行了。注意:这里的每颗星星占1个面积,不是点而是格子。
尺取法的方法就是先枚举一个L,然后R必然是单调不减的,如果区间不满足R就加,然后L在外面一直加,如果R过头了就break。
这样
O(n3)
是可以过的。考试时我这个sb却在想n^2logn的错误方法。对拍还是万分重要的。
ps:LZZ说还有
O(n2log2n)
的算法。%%%
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cstring>
#define N 405
#define oo 0x7fffffff
using namespace std;
int n, K, sum[N][N], exist[N][N], Ans = oo;
int main(){
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%d%d", &n, &K);
int x, y;
for(int i = 1; i <= n; i++){
scanf("%d%d", &x, &y);
exist[x][y] = 1;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++) sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + exist[i][j];
for(int i = 1; i <= n; i++)
for(int j = i; j <= n; j++){
int R = 1;
for(int L = 1; L <= n; L++){
while(R <= n && sum[R][j] - sum[R][i-1] - sum[L-1][j] + sum[L-1][i-1] < K) R ++;
if(R > n) break;
Ans = min(Ans, (R-L+1)*(j-i+1));
}
}
printf("%d\n", Ans);
return 0;
}
b 战争
题目描述
X国和Y国是死对头,X国有N个炮台, Y国有M个基地和K个发电站,炮台、基地、发电站所在的位置的坐标都是整数。Y国的每个发电站能对距离其L以内(包括L)的基地供电。X国的每个炮台都可以发射无限次,每次发射可以击毁一个基地或者一个发电站,消耗的能量为两者距离的平方,这里的距离是欧几里德距离。X国决定要摧毁Y国的所有基地,我们说Y国的某个基地被摧毁的条件是:基地本身被直接摧毁或者对其供电的所有发电站被击。
问X国摧毁Y国所有基地需要消耗的总能量最小是多少。
提示:点(X1,Y1)和点(X2,Y2)的欧几里德距离是:
dis = sqrt( (X2-X1)* (X2-X1) + (Y2-Y1)* (Y2-Y1)).
输入格式
第一行:四个整数:N、M、K、L。1 <= N、M、K <= 50. 1<=L<=2000.
第二行:N个整数,第i个整数表示第i个炮台的横坐标。
第三行:N个整数,第i个整数表示第i个炮台的纵坐标。
第四行:M个整数,第i个整数表示第i个基地的横坐标。
第五行:M个整数,第i个整数表示第i个基地的纵坐标。
第六行:K个整数,第i个整数表示第i个发电站的横坐标。
第七行:K个整数,第i个整数表示第i个发电站的纵坐标。
所有的坐标的范围:[-500,500]。所有的炮台、基地、发电站不会重复。
数据保证每个基地至少有一个发电站为其供电。
输出格式
问X国摧毁Y国所有基地需要消耗的总能量最小是多少。
解题思路(最小割)
这是一道最小割的果题。然而直接贪心(割掉s连出去的边和割掉连到t的边取min)能拿98分,ykm直接贪心100分,这数据,真是感人。
我在考场上很快想出了最小割,然后Dinic没写错,却犯了很严重的错误:构图错了。这智商,真是感人。
本来每个发电站直接被离之最近的炮台击毁就行了,就是s向每个发电站连边,发电站与它供电的基地连边,在基地向t连边。中间容量为oo,两边分别为摧毁其建筑的最小能量花费。就是说有一定的贪心。
然而这个贪心是不能少的,因为每个建筑被摧毁一次就可以一条割掉s-t路径了。所以这样割和花费才是一一对应的。
然而naive的我,将炮台放进了图中,徒增麻烦,还错了。因为如果炮台与基地(或发电站)每个都连边的话,割掉一条路径(使一座建筑摧毁)就需要割掉炮台与之相连的所有边,相当于每个炮台都打它一遍,这破坏了这种一一对应关系,唉,我的最小割都白学了。
然而样例中只有一个炮台,我还能说什么呢,论检查代码的重要性,早知道就不去死磕第三题了QAQ。。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 60
#define tu 100000
#define tutu 1000000
#define oo 1e9
using namespace std;
int n, m, K, L;
struct Data{
int x, y;
}at[N], bs[N], ele[N];
int head_p[tu], cur = -1, s, t, level[tu], iter[tu], q[tu];
struct Adj{int next, obj, cap;} Edg[tutu];
void Insert(int a, int b, int c){
cur ++;
Edg[cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
head_p[a] = cur;
}
int Dinic(int now, int f){
if(now == t || !f) return f;
int ret = 0;
for(int &i = iter[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(level[v] > level[now] && c){
int d = Dinic(v, min(f, c));
f -= d;
ret += d;
Edg[i].cap -= d;
Edg[i^1].cap += d;
if(!f) break;
}
}
return ret;
}
bool bfs(){
for(int i = s; i <= t; i++) level[i] = -1;
level[s] = 0;
int head, tail;
q[head = tail = 0] = s;
while(head <= tail){
int now = q[head++];
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(level[v] == -1 && c){
q[++tail] = v;
level[v] = level[now] + 1;
}
}
}
return level[t] != -1;
}
int Min_cut(){
int flow = 0;
while(bfs()){
for(int i = s; i <= t; i++) iter[i] = head_p[i];
flow += Dinic(s, oo);
}
return flow;
}
int Dis(Data A, Data B){
return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y);
}
void Build(){
s = 1; t = s+K+m+1;
for(int i = s; i <= t; i++) head_p[i] = -1;
for(int i = 1; i <= K; i++){
int Min = oo;
for(int j = 1; j <= n; j++) Min = min(Min, Dis(ele[i], at[j]));
Insert(s, s+i, Min);
Insert(s+i, s, 0);
}
for(int i = 1; i <= m; i++){
int Min = oo;
for(int j = 1; j <= n; j++) Min = min(Min, Dis(bs[i], at[j]));
Insert(s+K+i, t, Min);
Insert(t, s+K+i, 0);
}
for(int i = 1; i <= K; i++)
for(int j = 1; j <= m; j++)
if(Dis(ele[i], bs[j]) <= L * L){
Insert(s+i, s+K+j, oo);
Insert(s+K+j, s+i, 0);
}
}
int main(){
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
scanf("%d%d%d%d", &n, &m, &K, &L);
for(int i = 1; i <= n; i++) scanf("%d", &at[i].x);
for(int i = 1; i <= n; i++) scanf("%d", &at[i].y);
for(int i = 1; i <= m; i++) scanf("%d", &bs[i].x);
for(int i = 1; i <= m; i++) scanf("%d", &bs[i].y);
for(int i = 1; i <= K; i++) scanf("%d", &ele[i].x);
for(int i = 1; i <= K; i++) scanf("%d", &ele[i].y);
Build();
printf("%d\n", Min_cut());
return 0;
}
c 染色树
题目描述
一棵共含有X个结点的树,结点编号1至X,根结点编号是1。有Y种不同的颜色,颜色编号从1至Y。
现在给每个结点都染上一种颜色,整颗树染色后满足:
1、对于编号是i的颜色,整颗树当中,至少有一个结点被染成了颜色i。
2、根结点必须被染成1号颜色,而且整颗树当中,恰好要有Z个结点被染成1号颜色。
染色过程结束后,现在要计算染色的总代价,总代价等于每一条边的代价之和,那么怎么计算一条边的代价呢?
不妨设结点a与结点b之间有一条边,该边的权重是W。
1、如果结点a和结点b最终被染成不同的颜色,那么a与b之间的那条边的代价等于0。
2、如果结点a和结点b最终被染成相同的颜色,那么a与b之间的那条边的代价等于W。
现在的问题是:在满足上述要求的前提下,染色树最小的总代价是多少?如果无法完成染色的任务,输出-1。
输入格式
第一行,三个整数:X、 Y 、Z。 1 <= X <= 300, 2 <= Y <= X, 1 <= Z <= X。
接下来有N-1行,第i行有三个整数:a,b,W。表示结点a与结点b之间有一条边,权重是W。 1 <= a, b <= X, 0 <= W <= 100000。
输出格式
一个整数。
输入样例
8 2 4
1 2 20
1 3 4
1 4 13
2 5 10
2 6 12
3 7 15
3 8 5
输出样例
4
【样例解释】
把结点1,3,5,6染成1号颜色,剩下的结点染成2号颜色。
解题思路(树形分组背包dp)
这是一道很典型的树形分组背包dp。
一开始我没想到这个背包模型,推了个错误的树形dp,后来改成了分组背包。
写完代码后,调了1h+,样例死活出不来。没有单步跟踪,调试很烦。结果我的背包犯了一些细节错误,最后实在改不出来,我竟然将其直接注释掉了,心想:反正我都知道怎么做了。结果第一、二题完全不在意料之中,后悔莫及。
首先记
f[root][j]
为第
root
个点染1,子树中有
j
个1的答案,
然后一开始清为无穷大。
f[root][1]=g[root][0]=0;
然后就做树形背包,按照模型,枚举
j
要倒序,因为不能将本轮的答案贡献出来,否则就不是分组背包了。然后枚举儿子
到这里,还有问题,就是当
Y=2
时会有问题,因为我们只记两维因为转移(算不算边权)只跟是否取1有关,就是说如果取其他的话,就不算权值(
root
和
v
都不取1,那就随便去两个不同的非1数)。如果
综上,方程如下
一开始将 k=0 丢进去,枚举 k 从1开始就行(
输出-1的话,就是X < Y + Z - 1的情况。
时间复杂度 O(n3) 。
ps:三目表达式多加括号,写在加号后面不加就错。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 333
#define oo 1e9
using namespace std;
int n, m, K;
int cur = -1, head_p[N];
struct Tadj{int next, obj, w;} Edg[N<<1];
void Insert(int a, int b, int c){
cur ++;
Edg[cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].w = c;
head_p[a] = cur;
}
int f[N][N], g[N][N];
void dfs(int root, int fa){
f[root][1] = g[root][0] = 0;
for(int i = head_p[root]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, w = Edg[i].w;
if(v == fa) continue;
dfs(v, root);
for(int j = K; j >= 0; j--){
f[root][j] += g[v][0];
g[root][j] += g[v][0]+((m==2)?w:0);
for(int k = 1; k <= j; k++){
f[root][j] = min(f[root][j], min(f[root][j-k]+f[v][k]+w, f[root][j-k]+g[v][k]));
g[root][j] = min(g[root][j], min(g[root][j-k]+f[v][k], g[root][j-k]+g[v][k]+((m==2)?w:0)));
}
}
}
}
int main(){
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
scanf("%d%d%d", &n, &m, &K);
if(n < m + K - 1){
printf("-1\n");
return 0;
}
cur = -1;
for(int i = 1; i <= n; i++) head_p[i] = -1;
int a, b, c;
for(int i = 1; i < n; i++){
scanf("%d%d%d", &a, &b, &c);
Insert(a, b, c);
Insert(b, a, c);
}
for(int i = 1; i <= n; i++)
for(int k = 0; k <= K; k++) f[i][k] = g[i][k] = oo;
dfs(1, 0);
printf("%d\n", f[1][K]);
return 0;
}
总结
这次题目十分简单,但我反而爆炸了。这足以说明我的实力实在不如于人,我要调整好心态,调整状况,不能被困难与失败所打倒。
以后写程序一定要注意细节啊,满分跟零分只是一点点的差别。
天高云阔 风语如歌