贪心与图
A
Problem A. 最小差距
时间限制 1000 ms
内存限制 128 MB
题目描述
给定一些不同的一位数字,你可以从这些数字中选择若干个,并将它们按一定顺序排列,组成一个整数,把剩下的数字按一定顺序排列,组成另一个整数。组成的整数不能以0开头(除非这个整数只有1位)。
例如,给定6个数字,0,1,2,4,6,7,你可以用它们组成一对数10和2467,当然,还可以组成其他的很多对数,比如210和764,204和176。这些对数中两个数差的绝对值最小的是204和176,为28。
给定N个不同的0~9之间的数字,请你求出用这些数字组成的每对数中,差的绝对值最小的一对(或多对)数的绝对值是多少?
输入数据
第一行包括一个数 T (T≤1000),T (T≤1000), 为测试数据的组数。
每组数据包括两行,第一行为一个数 N (2≤N≤10),N (2≤N≤10), 表示数字的个数。下面一行为 NN 个不同的一位数字。
输出数据
TT 行,每行一个数,表示第 ii 个数据的答案。即最小的差的绝对值。
样例输入
2
6
0 1 2 4 6 7
4
1 6 3 4
样例输出
28
5
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define INF 0x3f3f3f3f
using namespace std;
int a[15];
int visited[15];
int odd(int n){
if(a[1]==0){
int temp=a[1];
a[1]=a[2];
a[2]=temp;
}
int s1 = 0, s2 = 0;
for(int i=1; i<=n/2+1; i++)
s1 = s1*10 + a[i];
for(int i=n; i>n/2+1; i--)
s2 = s2*10 + a[i];
return s1-s2;
}
int even(int n){
int res = INF;
for(int i=2; i<=n; i++){
if(a[i-1]){
memset(visited, 0, sizeof(visited));
int s1=a[i], s2=a[i-1];
visited[i]=1;
visited[i-1]=1;
int left=1, right=n;
for(int j=1; j<=(n-2)/2; j++){//每一轮选出两个数字
while(visited[left]) left++;
while(visited[right]) right--;
visited[left]=1;
visited[right]=1;
s1 = s1*10 + a[left];
s2 = s2*10 + a[right];
}
res = min(res, s1-s2);
}
}
return res;
}
int main(){
int T;
cin>>T;
for (int id = 0; id < T; id++)
{
int N, ans;
cin >> N;
memset(a, 0, sizeof(a));
for (int i = 1; i <= N; i++)
cin >> a[i];
sort(a + 1, a + N + 1);
if (N == 2) {
ans = a[2] - a[1];
}
else if (N % 2 == 1) {
ans = odd(N);
}
else {
ans = even(N);
}
cout << ans << endl;
}
return 0;
}
B
Problem B. 合并果子
时间限制 1000 ms
内存限制 128 MB
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
输入数据
输入包括两行,第一行是一个整数 n (1<=n<103),n (1<=n<103), 表示果子的种类数。第二行包含 nn 个整数,用空格分隔,第 ii 个整数 ai (1<=ai<2×103)ai (1<=ai<2×103) 是第 ii 种果子的数目。
输出数据
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 231231 。
样例输入
3
1 2 9
样例输出
15
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
int n, ans = 0,x;
cin >> n;
//这里使用优先队列的方法
priority_queue < int, vector <int>, greater <int> > q;
for (int i = 0; i < n; i++)
{
cin >> x;
q.push(x);
}
for (int i=0;i<n-1;i++)
{
int x1 = q.top();
q.pop();
int x2 = q.top();
q.pop();
ans += x1 + x2;
q.push(x1 + x2);
}
cout << ans << endl;
}
C
Problem C. 北京2008的挂钟
时间限制 1000 ms
内存限制 128 MB
题目描述
在2008北京奥运会雄伟的主会场的墙上,挂着如上图所示的3*3的九个挂钟(一开始指针即时针指向的位置请根据输入数据调整)。然而此次奥运会给与了大家一个机会,去用最少的移动操作改变上面的挂钟的时间全部为12点正(我们只考虑时针)。然而每一次操作并不是任意的,我们必须按照下面给出的列表对于挂钟进行改变。每一次操作我们给而且必须给指定的操作挂钟进行,每一个挂钟顺时针转动90度。列表如下:
操作 指定的操作挂钟
1 ABDE
2 ABC
3 BCEF
4 ADG
5 BDEFH
6 CFI
7 DEGH
8 GHI
9 EFHI
输入数据
你的程序按照标准的 3∗33∗3 格式读入,一共 99 个 0−30−3 的数。 00 代表 1212 点 ,1,1 代表 33 点 ,2,2 代表 66 点 ,3,3 代表 99 点。
Your program is to read from standard input. Nine numbers give the start positions of the dials. 0=12 o’clock, 1=3 o’clock, 2=6 o’clock, 3=9 o’clock.
输出数据
你的程序需要写出标准的输出。输出一个最短的能够使所有挂钟指向 1212 点的移动操作序列,中间以空格隔开,最后有空格,加回车。这一条最短操作需要是所有最短操作中最小的,也就是说选择最小的第一个操作数,如果第一个操作数相等,那么选择最小的第二个操作数……以此类推。值得肯定的是,这一条操作序列是唯一的。
Your program is to write to standard output. Output a shortest sorted sequence of moves (numbers), which returns all the dials to 12 o’clock. You are convinced that the answer is unique.
样例输入
3 3 0
2 2 2
2 1 2
样例输出
4 5 8 9
#include<iostream>
using namespace std;
const int way[9][9] = { { 1,1,0,1,1,0,0,0,0 }, // 操作1 ABDE
{ 1,1,1,0,0,0,0,0,0 }, // 操作2 ABC
{ 0,1,1,0,1,1,0,0,0 }, // 操作3 BCEF
{ 1,0,0,1,0,0,1,0,0 }, // 操作4 ADG
{ 0,1,0,1,1,1,0,1,0 }, // 操作5 BDEFH
{ 0,0,1,0,0,1,0,0,1 }, // 操作6 CFI
{ 0,0,0,1,1,0,1,1,0 }, // 操作7 DEGH
{ 0,0,0,0,0,0,1,1,1 }, // 操作8 GHI
{ 0,0,0,0,1,1,0,1,1 } // 操作9 EFHI
};
bool judge;//判断是否调整完成
int x[9], ans[9];
void dfs(int num)
{
if (num == 9)//9种方式全部搜完
{
for (int i = 0; i < 9; i++)
{
if (x[i] % 4 != 0)//有不是12点的钟
return;
}
judge = true;//全部调整完成
return;
}
for (int i = 0; i <= 3; i++)
{
ans[num] = i;//第(i+1)钟方式的数量
for (int j = 0; j < 9; j++)
{
x[j] += way[num][j] * i;//改变
}
dfs(num + 1);//深搜
if (judge)//此时已经调整完成,而调整方式有且只有一种,因此可以输出答案
return;
for (int j = 0; j < 9; j++)
{
x[j] -= way[num][j] * i;//回溯
}
}
}
void printRes()
{
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < ans[i]; j++)
{
cout << i + 1 << ' ';
}
}
cout << endl;
}
int main()
{
for (int i = 0; i < 9; i++)
{
cin >> x[i];
}
dfs(0);
printRes();
return 0;
}
D
Problem D. 毒药?解药?
时间限制 1000 ms
内存限制 128 MB
题目描述
羽毛笔和im是抽签到同一个考场的,她们突然闻到一阵刺鼻的化学试剂的气味。
机灵鼠:(头都不抬)你们是考生么?还在门口磨蹭什么?快进来帮我忙!!……怎么还不进来?你们拖赛,拖赛,把你们的青春都拖掉赛……
im:开…开策了> <
羽毛笔:哎呀~~机灵鼠大人要我们帮什么忙?^^
机灵鼠:你们看这里的这些药,都是我研制的对付各种症状的解药。可是我一个不小心,每种药都小小地配错了一点原料,所以这些药都有可能在治愈某些病症的同时又使人患上某些别的病症……(im:那…那是解药还是毒药啊?!)……经过我天才的努力(背景:我是天才!!),终于弄清了每种药的具体性能(路人甲:那是你自己配的吗?--),我会把每种药能治的病症和能使人患上的病症列一张清单给你们,然后你们要根据这张清单找出能治愈所有病症的最少药剂组合……顺便说一声,病症的数目不超过10种(小呆:偶是好人吧^^),我的药是用不完的,就是说每种药剂都可以被重复使用。给你们的单子里第一行是病症的总数n,第二行是药剂的种类m(0< m< =100),以下有m行,每行有n个数字用空格隔开,文件的第i+2行的n个数字中,如果第j个数为1,就表示第i种药可以治愈病症j(如果患有这种病的话则治愈,没有这种病则无影响),如果为0表示无影响,如果为-1表示反而能使人得上这种病(无病患上,有病无影响)。我制的药任何两种性能都不同。你们只要给我用的最少的药剂数就可以了。给你们个样例:
输入数据
输出数据
样例输入
3
2
1 0 1
-1 1 0
样例输出
2
样例说明
其实还有可能用尽了所有的药也不能将所有病治愈(真是不好意思嗬^^bb),那样的话你们只要写上“The patient will be dead.”就可以了。
im:做不出来啊哇啊啊啊(暴走中)
羽毛笔:哎呀~~im……来来吃药了。^^
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 15
#define maxm 110
#define maxg 1040
bool stateT[maxn], hashT[maxg];
int mp[maxm][maxn];
int stateI[maxg][maxn];
int n, m, finish;
int Hash(bool a[]) {
int x = 1, s = 0;
for (int i = 1; i <= n; i++) {
s += x * a[i];
x *= 2;
}
return s;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
cin >> mp[i][j];
int l = 0, r = 1;
finish = (1 << n) - 1;
while (l < r) {
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (mp[i][j] == 1) stateT[j] = 1;
else if (mp[i][j] == -1) stateT[j] = 0;
else stateT[j] = stateI[l][j];
}
int x = Hash(stateT);
if (x == finish) {
cout << stateI[l][0] + 1 << endl;
return 0;
}
if (!hashT[x]) {
hashT[x] = 1;
stateI[r][0] = stateI[l][0] + 1;
for (int j = 1; j <= n; j++) stateI[r][j] = stateT[j];
r++;
}
}
l++;
}
cout << "The patient will be dead." << endl;
return 0;
}
E
Problem E. 矩形覆盖
时间限制 1000 ms
内存限制 128 MB
题目描述
在平面上有 n 个点(n < = 50),每个点用一对整数坐标表示。例如:当 n=4 时,4个点的坐标分另为:p1(1,1),p2(2,2),p3(3,6),P4(0,7)。
这些点可以用 k 个矩形(1< =k< =4)全部覆盖,矩形的边平行于坐标轴。当 k=2 时,可用如图二的两个矩形 sl,s2 覆盖,s1,s2 面积和为 4。问题是当 n 个点坐标和 k 给出后,怎样才能使得覆盖所有点的 k 个矩形的面积之和为最小呢。约定:覆盖一个点的矩形面积为 0;覆盖平行于坐标轴直线上点的矩形面积也为0。各个矩形必须完全分开(边线与顶点也都不能重合)。
输入数据
格式为
n k
xl y1
x2 y2
… …
xn yn (0< =xi,yi< =500)
输出数据
一个整数,即满足条件的最小的矩形面积之和。
样例输入
4 2
1 1
2 2
3 6
0 7
样例输出
4
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n, m;//输入值
int SquareAns = 10000000;//结果
struct node {
int x, y;
}Node[510];
struct node2 {
int l, r, u, d;
bool IsChoosed;
}rectangle[5];
bool IsNodeInRectangle(node2 a, int x, int y)
{
if (x <= a.r && x >= a.l && y <= a.u && y >= a.d)
return 1;
return 0;
}
bool IsRectangleInRectangle(node2 a, node2 b) //判断是否包含
{
if (IsNodeInRectangle(a, b.l, b.u)) return 1; //左上角 任何一个顶点在已知矩阵内就不合法
if (IsNodeInRectangle(a, b.l, b.d)) return 1; //左下角
if (IsNodeInRectangle(a, b.r, b.u)) return 1; //右上角
if (IsNodeInRectangle(a, b.r, b.d)) return 1; //右下角
return 0;
}
int Search(int t)
{
int i, j, square = 0;
for (i = 1; i <= m; i++)
{
if (rectangle[i].IsChoosed)
{
for (j = 1; j <= m; j++)
{
if (i != j && rectangle[j].IsChoosed && IsRectangleInRectangle(rectangle[i], rectangle[j]))
return 0;
}
}
square += (rectangle[i].r - rectangle[i].l) * (rectangle[i].u - rectangle[i].d);
}
if (square >= SquareAns)
return 0;
if (t > n)
{
SquareAns = square;//满足条件更新SquareAns
return 0;
}
for (i = 1; i <= m; i++)
{
node2 tmp = rectangle[i];
if (rectangle[i].IsChoosed == 0)
{
rectangle[i].IsChoosed = 1;
rectangle[i].l = rectangle[i].r = Node[t].x;
rectangle[i].u = rectangle[i].d = Node[t].y;
Search(t + 1);
rectangle[i] = tmp;
}
else
{
rectangle[i].l = min(rectangle[i].l, Node[t].x);
rectangle[i].r = max(rectangle[i].r, Node[t].x);
rectangle[i].u = max(rectangle[i].u, Node[t].y);
rectangle[i].d = min(rectangle[i].d, Node[t].y);
Search(t + 1);
rectangle[i] = tmp;
}
}
}
int main()
{
scanf("%d%d", &n, &m); //输入
for (int i = 1; i <= n; i++)
scanf("%d%d", &Node[i].x, &Node[i].y);
Search(1);
printf("%d", SquareAns);
return 0;
}
F
Problem F. 传染病防治
时间限制 1000 ms
内存限制 128 MB
题目描述
研究表明,这种传染病的传播具有两种很特殊的性质;
第一是它的传播途径是树型的,一个人X只可能被某个特定的人Y感染,只要Y不
得病,或者是XY之间的传播途径被切断,则X就不会得病。
第二是,这种疾病的传播有周期性,在一个疾病传播周期之内,传染病将只会感染一
代患者,而不会再传播给下一代。
这些性质大大减轻了蓬莱国疾病防控的压力,并且他们已经得到了国内部分易感人群
的潜在传播途径图(一棵树)。但是,麻烦还没有结束。由于蓬莱国疾控中心人手不够,同时也缺乏强大的技术,以致他们在一个疾病传播周期内,只能设法切断一条传播途径,而没有被控制的传播途径就会引起更多的易感人群被感染(也就是与当前已经被感染的人有传播途径相连,且连接途径没有被切断的人群)。当不可能有健康人被感染时,疾病就中止传播。所以,蓬莱国疾控中心要制定出一个切断传播途径的顺序,以使尽量少的人被感染。你的程序要针对给定的树,找出合适的切断顺序。
输入数据
输入格式的第一行是两个整数 n (1≤n≤300)n (1≤n≤300) 和 pp 。接下来 pp 行,每一行有两个整数 ii
和 j,j, 表示节点 ii 和 jj 间有边相连(意即,第 ii 人和第 jj 人之间有传播途径相连)。其中节点
11 是已经被感染的患者。
输出数据
只有一行,输出总共被感染的人数。
样例输入
7 6
1 2
1 3
2 4
2 5
3 6
3 7
样例输出
3
#include <iostream>
#include <math.h>
#include <algorithm>
using namespace std;
#define maxn 305
int n, m;//总结点数和节点联系
int map[maxn][maxn] = { 0 };
int ans = 0x7ffffff;
int child[maxn];
void dfs(int tree[maxn], int s, int d, int res)
{
int son[maxn];
int cur = 0;
for (int i = 1; i <= s; i++)
{
int& a = tree[i];
if (a == d) continue;
for (int i = 1; i <= n; i++)
if (map[a][i])
son[++cur] = i;
}
if (!cur)
ans = min(ans, res);
else
{
for (int i = 1; i <= cur; i++)
if (res + cur - 1 <= ans)
dfs(son, cur, son[i], res + cur - 1);
}
}
int main()
{
cin >> n >> m;
int a, b;
int cur = 0;
for (int i = 0; i < m; i++)
{
cin >> a >> b;
if (a > b)
swap(a, b);
map[a][b] = 1;
if (a == 1) child[++cur] = b;//记录1(根)的子节点
}
for (int i = 1; i <= cur; i++)
dfs(child, cur, child[i], cur);
cout << ans << endl;
return 0;
}