山峰和山谷(grz)
Description
Byteasar特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。为了能够让他对他的旅程有一个安排,他想知道山峰和山谷的数量。
给定一个地图,为Byteasar想要旅行的区域,地图被分为n*n的网格,每个格子(i,j) 的高度w(i,j)是给定的。
若两个格子有公共顶点,那么他们就是相邻的格子。(所以与(i,j)相邻的格子有(i−1, j−1),(i−1,j), (i−1,j+1), (i,j−1), (i,j+1), (i+1,j−1), (i+1,j), (i+1,j+1))。
我们定义一个格子的集合S为山峰(山谷)当且仅当:
1.S的所有格子都有相同的高度。
2.S的所有格子都联通
3.对于s属于S,与s相邻的s’不属于S。都有ws>ws’(山峰),或者ws小于ws’(山谷)
你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。
Input Format
输入文件grz.in第一行包含一个正整数n,表示地图的大小(1<=n<=1000)。接下来一个n*n的矩阵,表示地图上每个格子的高度。(0<=w<=1000000000)
Output Format
输出文件grz.out应包含两个数,分别表示山峰和山谷的数量。
Sample Input 1
5
8 8 8 7 7
7 7 8 8 7
7 7 7 7 7
7 8 8 7 8
7 8 8 8 8
Sample Output 2
2 1
Sample Input 2
5
5 7 8 3 1
5 5 7 6 6
6 6 6 2 8
5 7 2 5 8
7 1 0 1 7
Sample Output 2
3 3
Solution
一道经典的bfs问题,遍历整个图求各个高度相同的连通块,若在扩展中遇到了高度比当前连通块小的,则当前连通块不是山谷;若在扩展中遇到了高度比当前连通块大的,则当前连通块不是山峰。
Code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int n, map[1001][1001];
bool vis[1001][1001];
int hans, lans;//山峰、山谷
int dx[9] = {0, 1, 1, 1, 0, -1, -1, -1, 0}, dy[9] = {0, -1, 0, 1, 1, 1, 0, -1, -1};//增量数组
struct pnt {
int x, y, h; //坐标,高度
pnt(int x = 0, int y = 0, int h = 0)
:x(x), y(y), h(h) {}//构造器
};
inline void bfs(int x, int y) {
queue <pnt> q;
q.push(pnt(x, y, map[x][y]));
pnt u;
bool hgh = true, lw = true;//记录是否为山峰、山谷
while(!q.empty()) {
u = q.front();
q.pop();
for(int i = 1; i <= 8; ++i) {
u.x += dx[i]; u.y += dy[i];
if(u.x>0 && u.x<=n && u.y>0 && u.y<=n) {
if(map[u.x][u.y] > u.h) hgh = false;
else if(map[u.x][u.y] < u.h) lw = false;
else if(!vis[u.x][u.y]) {
vis[u.x][u.y] = true;
q.push(pnt(u.x, u.y, map[u.x][u.y]));
}
}
u.x -= dx[i]; u.y -= dy[i];
}
}
if(hgh) ++hans;
if(lw) ++lans;
}
int main() {
freopen("grz.in", "r", stdin);
freopen("grz.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
scanf("%d", &map[i][j]);
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
if(!vis[i][j]) bfs(i, j);
printf("%d %d", hans, lans);
return 0;
}
超级马(sup)
Description
在一个无限的棋盘上有一个超级马,它可以完成各种动作。每一种动作都是通过两个整数来确定——第一个数说明列的数(正数向右,负数向左),第二个数说明行的数(正数向上,负数向下),移动马来完成这个动作。
编写一个程序,从文本文件SUP.IN输入说明各种超级马的数据库。
对每一个超级马进行确认,是否通过自己的行动可以到达盘面上的每一个区。
将结果存储到文本文件SUP.OUT。
Input Format
在文本文件SUP.IN 的第一行中存在一个整数k,它代表数据库的数1≤k≤100。在这个数字后出现K 数据库。它们的每一个第一行中会出现整数N,它是马能够完成的各种动作的数,1≤n≤100。接下来数据库的每一个行中包含两个整数P 和Q,它们由单个空格分开,说明动作,-100≤p,q≤100。
Output Format
文本文件SUP.OUT 应由K 行组成,当第i 个数据库的超级马可以到达棋盘面的每一个区,第i 行应包含一个词TAK,而另一个词NIE 则恰恰相反。
Sample Input
2
3
1 0
0 1
-2 -1
5
3 4
-3 -6
2 -2
5 6
-1 4
Sample Output
TAK
NIE
Solution
因为坐标一次不超过[-100,100],棋盘开到201*201就可以。再bfs,从棋盘中心开始,记录是否中心的上下左右都可以走到,若可以则能走到整个棋盘。注意去重。一开始写成是否能走到201*201的所有格子了,又慢又容易错。
Code
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int sumpoints = 40401;
int T, n;
int dx[101], dy[101];
bool vis[201][201];
struct pnt {
int x, y;
pnt(int x = 100, int y = 100) :x(x), y(y) {}
};
bool bfs() {
queue <pnt> q;
q.push(pnt());
vis[100][100] = true;
pnt u;
while(!q.empty()) {
u = q.front();
q.pop();
for(int i = 1; i <= n; ++i) {
u.x += dx[i]; u.y += dy[i];
if(u.x>=0 && u.x<=200 && u.y>=0 && u.y<=200)
if(!vis[u.x][u.y]) {
q.push(u);
vis[u.x][u.y] = true;
if(vis[100][99] && vis[100][101] && vis[99][100] && vis[101][100])
return true;
}
u.x -= dx[i]; u.y -= dy[i];
}
}
if(vis[100][99] && vis[100][101] && vis[99][100] && vis[101][100]) return true;
else return false;
}
int main() {
freopen("sup.in", "r", stdin);
freopen("sup.out", "w", stdout);
scanf("%d", &T);
while(T--) {
memset(dx, 0, sizeof(dx));
memset(dy, 0, sizeof(dy));
memset(vis, false, sizeof(vis));
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d%d", &dx[i], &dy[i]);
if(bfs()) puts("TAK");
else puts("NIE");
}
return 0;
}
传递游戏(pass)
Description
n个人在做传递物品的游戏,编号为1-n。
游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。
即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;
求当物品经过所有n个人后,整个过程的最小总代价是多少。
Input Format
第一行为n,表示共有n个人(16>=n>=2);
以下为n*n的矩阵,第i+1行、第j列表示物品从编号为i的人传递到编号为j的人所花费的代价,特别的有第i+1行、第i列为-1(因为物品不能自己传给自己),其他数据均为正整数(<=10000)。
Output Format
一个数,为最小的代价总和。
Sample Input
2
-1 9794
2724 –1
Sample Output
2724
Solution
dfs:
传3个参数,一个表示当前传到的人,一个是传过了多少人,一个是当前代价;
最优化剪枝:若当前代价加上(所有人中最小的代价*(n-传过的人数))>=ans则剪掉。可以1s出解,但一开始我写挫了,记录的是每个人的最小代价,又用了奇怪的卡时,WA了。
Code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<climits>
using namespace std;
int n, map[17][17], ans = INT_MAX;
int times, maxm;
bool vis[17];
void dfs(int k, int lft, int now) {
if(lft == n) {
ans = min(ans, now);
return;
}
if(now+(n-lft)*maxm >= ans) return;
for(int i = n; i >= 1; --i) if(!vis[i]) {
vis[i] = true;
dfs(i, lft+1, now + map[k][i]);
vis[i] = false;
}
}
int main() {
freopen("pass.in", "r", stdin);
freopen("pass.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j){
scanf("%d", &map[i][j]);
if(i != j) maxm = min(maxm, map[i][j]);
}
for(int i = n; i >= 1; --i) {
memset(vis, false, sizeof(vis));
vis[i] = true;
dfs(i, 1, 0);
}
printf("%d", ans);
return 0;
}
dp:
方程:f[i][k] = min(f[i][k], f[j][k^pos[i-1]]+map[j][i]);
i,j表示由j传到了i,k是一个二进制数表示状态,1为当前传过了某位置,0则为未传过,详见代码。
Code
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cstring>
using namespace std;
int pos[17] = {1, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15, 1<<16};
//二进制记录状态,1表示该位置的人已经传过,0表示没有传过
int n, map[17][17];
int f[17][70000], ans = 707406378;
inline void read(int &a) {//读入优化
int f = 1;
char s = getchar();
while(s<'0' || s>'9') {
if(s == '-') f = -1;
s = getchar();
}
while(s>='0' && s<='9') {
a = a*10+s-48;
s = getchar();
}
a *= f;
}
inline void write(int a) {//输出优化
char s[20];
int top = 0;
if(a < 0) {
putchar('-');
a = -a;
}
do {
s[++top] = a%10+48;
a /= 10;
} while(a);
for(int i = top; i >= 1; --i) putchar(s[i]);
}
inline void init() {//读入数据
freopen("pass.in", "r", stdin);
read(n);
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
read(map[i][j]);
}
inline void print() {//输出数据
freopen("pass.out", "w", stdout);
write(ans);
}
inline void work() {//dp
memset(f, 127/3, sizeof(f));
for(int i = 1; i <= n; ++i) f[i][pos[i-1]] = 0;
for(int k = 0; k <= pos[n]-1; ++k) //枚举已经传过了哪些人
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) {//当前由j传到了i
if(i == j) continue;
if((k&pos[i-1]) != pos[i-1]) continue;//若不等,则说明没有传过i,不符
if((k&pos[j-1]) != pos[j-1]) continue;//若不等,则说明没有传过j,不符
f[i][k] = min(f[i][k], f[j][k^pos[i-1]]+map[j][i]);//k^pos[i-1]表示的状态就是pos[i-1]并把i的位置去掉
}
for(int i = 1; i <= n; ++i) ans = min(f[i][pos[n]-1], ans);
}
int main() {
init();
work();
print();
return 0;
}
皇后守卫(queen)
Description
给一个N * M的棋盘,棋盘上的有些格子被打上了标记。现在需要在其中放置尽量少的皇后,使得所有被打上标记的格子至少被某一个皇后攻击或占据到。皇后之间可以互相攻击。
Input Format
输入最多15组数据。
每组数据第一行包含两个整数N和M(1 < N, M < 10),以下为一个N行M列的棋盘,其中打上标记的格子用‘X’表示,其它格子用‘.’表示。
输入以一个0结尾。
Output Format
对于每组数据,输出一个数表示最少需要使用的皇后数目。
Sample Input
8 8
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
8 8
X…….
.X……
..X…..
…X….
….X…
…..X..
……X.
…….X
0
Sample Output
(注意输出格式为每组数据占一行,Case k:m表示第k组数据对应的需要使用的皇后的数目)
Case 1: 5
Case 2: 1
Solution
本题可以先写暴搜,发现答案最大是5,于是答案的上界就定下来了。具体的搜法可以是枚举皇后的位置,如果在某位置可以控制某些X,就递归这个位置。本题最优化剪枝是很明显的,但若只有这些的话还是会T。于是乎我就加了个错误的剪枝:只在X上放皇后,然后就全W了。
还有一个剪枝是从X出发寻找可以控制X的格子,不过我写的方法比较挫,T了。
最终设法参透了洪一大神的二进制优化,本质上是另一种搜法:
使用一个二进制数k表示每一行被皇后控制的状态,一个一维数组就可以记录整个棋盘。
二进制的一些知识:
一个数左移后一定是偶数,偶数(or)1等同于偶数+1;
……
以下来自
http://blog.youkuaiyun.com/qq_21110267/article/details/44002315
:
/*
许多精巧的位运算 :
判断是否满足条件, 标记的地方都被覆盖到 枚举 x, 如果所有 (k[x]&map[x]) == map[x] 说明满足 解释 : map[x]表示第 x 行需要被覆盖的二进制状态. 只有当 map[x] 为 1 的地方k[x] 也为 1 才满足
更新状态, 在第 x 行二进制下 p 位置放置皇后对原可覆盖状态 k 的影响
*/
end;
dfs传递4个参数,当前递归的格子横纵坐标,棋盘状态,当前已用的皇后数。
若标记的格子均已被覆盖,更新答案,返回;
若当前已用的皇后数加一(此时不满足条件,否则在第一步就已经返回)大于等于答案则返回;
递归在这个格子放皇后的情况;
递归不放皇后的情况;
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 20;
const int maxans = 5;
int n, m, T, ans, maxlim;
int map[maxn];
inline void read(int &a) {
a = 0;
char ch = getchar();
int f = 1;
while(ch<'0' || ch>'9') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch>='0' && ch<='9') {
a = a*10+ch-48;
ch = getchar();
}
}
inline void write(int a) {
char c[20];
int top = 0;
if(a < 0) {
putchar('-');
a = -a;
}
do {
c[++top] = a%10+48;
a /= 10;
} while(a);
for(int i = top; i >= 1; --i) putchar(c[i]);
}
inline void print(int x, int y) {
putchar('C'); putchar('a'); putchar('s');
putchar('e'); putchar(' '); write(x);
putchar(':'); putchar(' '); write(y);
putchar('\n');
}
//以上一坨为IO优化
void update(int x, int y, int *k) {//放皇后以后更新棋盘状态
k[x] = maxlim;
for(int i = 1; x-i >= 0; ++i) {
if((y<<i) < maxlim) k[x-i] |= (y<<i);
if((y>>i) > 0) k[x-i] |= (y>>i);
k[x-i] |= y;
}
for(int i = 1; x+i < n; ++i) {
if((y<<i) < maxlim) k[x+i] |= (y<<i);
if((y>>i) > 0) k[x+i] |= (y>>i);
k[x+i] |= y;
}
}
bool check(int *k) { //检查是否满足条件
for(int i = 0; i < n; ++i)
if((k[i]&map[i]) != map[i]) return false;
return true;
}
void dfs(int x, int y, int *k, int now) {
if(check(k)) {
ans = min(ans, now);
return;
}
if(y >= m) { ++x; y = 0; }
if(x>=n || now+1>=ans) return;
int p = (1<<y);
int k2[maxn];
memcpy(k2, k, sizeof(int)*n);
update(x, p, k2);
dfs(x, y+1, k2, now+1);
dfs(x, y+1, k, now);
}
int main() {
freopen("queen.in", "r", stdin);
freopen("queen.out", "w", stdout);
char ch[20];
while(++T) {
read(n);
if(n == 0) break;
read(m);
maxlim = (1<<m)-1; //当前棋盘一行的格子数
memset(map, 0, sizeof(map));
for(int i = 0; i < n; ++i) {
gets(ch);
for(int j = 0; j < m; ++j) { //预处理棋盘
map[i]<<=1;
if(ch[j] == 'X') map[i] |= 1;
}
}
int k[maxn];
ans = maxans;
memset(k, 0, sizeof(k));
dfs(0, 0, k, 0);
print(T, ans);
}
return 0;
}