UVA - 1572 Self-Assembly
思路
有n(n≤40000)种边上带标号的正方形。每条边上的标号要么为一个大写字母后面跟着一个加号或减号,要么为数字00。
当且仅当两条边的字母相同且符号相反时,两条边能拼在一起(00不能和任何边拼在一起,包括另一条标号为00的边)。
假设输入的每种正方形都有无穷多种,而且可以旋转和翻转,你的任务是判断能否组成一个无限大的结构。每条边要么悬空(不和任何边相邻),要么和一个上述可拼接的边相邻。
(书上原话)本题看上去很难下手,但不难发现“可以旋转和翻转”是一个很有意思的条件,值得推敲。“无限大结构”并不一定能铺满整个平面,只需要能连出一条无限长的“通路”即可。借助于旋转和翻转,可以让这条“通路”总是往右和往下延伸,因此永远不会自交。这样一来,只需以某个正方形为起点开始“铺路”,一旦可以拼上一块和起点一样的正方形,无限重复下去就能得到一个无限大的结构。
可惜这样的分析仍然不够,因为正方形的数目n很大。进一步分析发现:实际上不需要正方形本身重复,而只需要边上的标号重复即可。这样问题就转化为:把标号看成点(一共只有A+~Z+,A-~Z-这52种,因为00不能作为拼接点),正方形看作边,得到一个有向图。则当且仅当图中存在有向环时有解。只需要做一次拓扑排序即可。
所以这是一道拓扑排序题,我也是看了书上的思路才敲出来的,不过想想也有道理,只是看不出来是拓扑排序,而且不知道图在哪而已。。。
这次拓扑是用dfs写的。
代码
int n, maps[maxn][maxn], vis[maxn];
char s[15];
int getN(int x) { return 2*(s[2*x]-'A') + (s[2*x+1] == '+'? 0 : 1); }
void Link(int x, int y) {
if(s[2*x] == '0' || s[2*y] == '0') return;
int tx = getN(x), ty = getN(y)^1;
maps[tx][ty] = 1;
}
bool dfs(int x) {
vis[x] = -1;
for(int i = 0; i < 52; ++i) {
if(maps[x][i]) {
if(vis[i] < 0) return 0;
else if(!vis[i] && ! dfs(i)) return 0;
}
}
vis[x] = 1; return 1;
}
bool topSort() {
met(vis, 0);
for(int i = 0; i < 52; ++i) if(!vis[i]) {
if(!dfs(i)) return 0;
}
return 1;
}
int main() {
#ifdef _LOCAL
IN;
#endif // _LOCAL
while(scanf("%d", &n) == 1) {
met(maps, 0);
for(int i = 0; i < n; ++i) {
scanf("%s", s);
for(int i = 0; i < 4; ++i)
for(int j = 0; j < 4; ++j) {
if(i!=j) Link(i, j);
}
}
if(topSort()) printf("bounded\n");
else printf("unbounded\n");
}
return 0;
}
UVA - 1599 Ideal Path
思路
对我而言,这道题让我重新认识了如何记录路径,同时让我重新认识了bfs的结构,每次不一定只出队一个,也不一定只入队一个,bfs比我想的要更灵活。
题目意思是, 给一个n个点m条边(2 ≤ n ≤ 100000,1 ≤ m ≤ 200000)的无向图,每条边上都涂有一种颜色。求从结点1到结点n的一条路径,使得经过的边数尽量少,在此前提下,经过边的颜色序列的字典序最小。一对结点间可能有多条边,一条边可能连接两个相同结点。输入保证结点1可以达到结点n。颜色为1 ~ 109 的整数。
从终点开始“倒着”BFS,得到每个结点i到终点的最短距离d[i],然后直接从起点开始走,但是每次到达一个新结点时要保证d值恰好减少1(如有多个选择则可以随便走),直到到达终点。
然后再从起点开始按照上述规则走,如果有多种走法,选颜色字典序最小的走;如果有多条边的颜色字典序都是最小,则记录所有这些边的终点,走下一步时要考虑从所有这些点出发的边。
代码
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
using namespace std;
typedef long long LL;
typedef pair<int, int> Pii;
typedef set<int> Si;
typedef vector<int> Vi;
const int maxn = 1e6 + 100;
const int INF = 0x7fffffff;
int n, m, a, b, c;
int dis[maxn], ans[maxn];
struct edge {
int to, next, we;
edge(){}
edge(int t, int n, int w):to(t), next(n), we(w){}
}e[maxn];
int tot;
int head[maxn];
bool vis[maxn];
void init() {
tot = 0; met(head, -1); met(vis, 0); met(dis, 0); met(ans, 0);
}
void add(int from, int to, int we) {
e[tot] = edge(to, head[from], we);
head[from] = tot++;
}
void bfs() {
queue<int> q;
q.push(n); vis[n] = 1; dis[n] = 0;
while(!q.empty()) {
int t = q.front(); q.pop();
for(int i = head[t]; i != -1; i = e[i].next) {
int tmp = e[i].to;
if(!vis[tmp]) { q.push(tmp); vis[tmp] = 1; dis[tmp] = dis[t]+1; }
}
}
}
void bfs2() {
met(vis, 0);
queue<int> q;
q.push(1); vis[1] = 1;
while(!q.empty()) {
int M = INF, D; Vi v;
while(!q.empty()) {
int t = q.front();q.pop();
for(int i = head[t]; i != -1; i = e[i].next) {
int tt = e[i].to; D = dis[t];
if(vis[tt] || dis[t] != dis[tt]+1 || e[i].we > M) continue;
if(e[i].we < M) M = e[i].we, v.clear();
v.push_back(tt);
}
}
ans[D] = M;
for(int i= 0; i < v.size(); ++i) {
if(!vis[v[i]]) q.push(v[i]), vis[v[i]] = 1;
}
}
}
int main() {
#ifdef _LOCAL
IN;
#endif // _LOCAL
while(scanf("%d%d", &n, &m) == 2) {
init();
for(int i = 0; i < m; ++i) {
scanf("%d%d%d", &a, &b, &c);
if(a != b) add(a, b, c), add(b, a, c);
}
bfs(); bfs2();
cout << dis[1] <<endl << ans[dis[1]];
for(int i = dis[1]-1; i >= 1; --i) cout << " " << ans[i];
cout << endl;
}
return 0;
}
UVA - 506 System Dependencies
思路
模拟安装软件和卸载软件同时修正相关组件的操作。
这一题感觉还算简单,题目意思并不难懂,也好实现。感觉主要是考察递归。
但是要注意一些细节上的问题,记录各个组件的状态,关系(依赖关系)等。
代码
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
using namespace std;
typedef long long LL;
typedef pair<int, int> Pii;
typedef map<string, int> Msi;
typedef set<int> Si;
typedef vector<int> Vi;
const int maxn = 1e6 + 100;
const int INF = 0x7fffffff;
string s, t1, t2, name[maxn];
stringstream ss;
Msi m;
Vi depd[maxn], dept[maxn], insed;
int state[maxn]; //0 1 2 未装 隐式 显式
int getID(string str) {
if(m.count(str)) return m[str];
name[m.size()+1] = str;
return m[str] = m.size();
}
void depend() {
ss.clear(); ss << s;
ss >> t1 >> t1;
int id = getID(t1);
while(ss >> t2) { depd[id].push_back(getID(t2)); dept[getID(t2)].push_back(id); }
}
void install(int id, int ok) {
if(state[id] != 0) {
if(!ok) cout << " " << name[id] <<" is already installed."<<endl;
return;
}
for(int i = 0; i < depd[id].size(); ++i) {
install(depd[id][i], 1);
}
state[id] = ok?1:2;
cout << " Installing " << name[id] <<endl;
insed.push_back(id);
}
bool need(int x) {
for(int i = 0; i < dept[x].size(); ++i) {
if(state[dept[x][i]] != 0 ) return 1;
}
return 0;
}
void remov(int id, int ok) {
if(state[id] == 0) {
if(!ok) cout << " " << name[id] << " is not installed." <<endl;
}
else if((!ok || state[id] == 1) && !need(id)) {
state[id] = 0;
cout << " Removing " << name[id] <<endl;
insed.erase(remove(insed.begin(), insed.end(), id), insed.end());
for(int i = depd[id].size()-1; i >= 0; --i) {
remov(depd[id][i], 1);
}
return;
}
else if(!ok) cout << " " << name[id] << " is still needed." <<endl;
}
void List() {
for(int i = 0; i < insed.size(); ++i) {
cout << " " << name[insed[i]] <<endl;
}
}
void init() {
m.clear(); insed.clear();
for(int i = 0; i < maxn; ++i) depd[i].clear(), dept[i].clear();
met(state, 0);
}
int main() {
#ifdef _LOCAL
IN;
#endif // _LOCAL
init();
while(getline(cin, s)) {
cout << s <<endl;
if(s == "END") {
init(); continue;
}
if(s[0] == 'D') {
depend();
}
else if(s[0] == 'I') {
ss.clear(); ss << s;
ss >> t1 >> t1;
install(getID(t1), 0);
}
else if(s[0] == 'R') {
ss.clear(); ss << s;
ss >> t1 >> t1;
remov(getID(t1), 0);
}
else if(s[0] == 'L') {
List();
}
}
return 0;
}
UVA - 11853 Paintball
思路
特别好的一道题。
题目意思是,有一个1000×1000的正方形战场,战场西南角的坐标为(0,0),西北角的坐标为(0,1000)。
战场上有n(0≤n≤1000)个敌人,第i个敌人的坐标为(x i ,y i ),攻击范围为r i 。
为了避开敌人的攻击,在任意时刻,你与每个敌人的距离都必须严格大于它的攻击范围。
你的任务是从战场的西边(x=0的某个点)进入,东边(x=1000的某个点)离开。
如果有多个位置可以进/出,你应当求出最靠北的位置。
输入每个敌人的x i 、y i 、r i ,输出进入战场和离开战场的坐标。
这一题个人感觉比较难想的有两点。
第一点是如何判断能不能从西边走到东边,这个应该是此题的核心问题,然后有一个思路是从与上边界相交(切)的每一个圆(每个攻击范围是一个圆)开始,做一遍dfs或bfs,如果在这个过程中有与下边界相交(切)的圆,说明东西被分成了两块,根本无法通过,这个时候结果就是impossible了
但是呢,如果很不幸,在dfs或bfs的过程中,没有圆与下边界相交(切),那么说明你是可以从最西边走到最东边的,那么最北的入口和出口怎么找呢?这是第二个难想的点。
这个时候,我们不妨将dfs(bfs)的过程细分一下,大致有三种情况。
第一种是在这一遍dfs中没有圆与左边界相交,也没有圆与右边界相交,那这个时候最北边应该是1000或者是第一个开始dfs时的那个圆的最南边,如果它与左边界或右边界相交的话;
第二种是在这一遍dfs中有的圆碰到了左边界(右边界同样),那么这个时候自上到左是不是就组成了一个封闭的空间,而你要想出去就只能从下面走,所以这个时候的最北入口应该就是在dfs过程中所有与左边界相交的边的最南边。第三种情况是碰到了右边界,与左边界一样。
然后我们会发现,第一种情况好像结果也在第二,三种情况之内,所有最后的结论就是,我在dfs的过程中,当有的圆与左或右边界相交时,我就取它们最南边的值的最小值,便是入口或者出口了
代码
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
using namespace std;
typedef long long LL;
typedef pair<int, int> Pii;
typedef map<string, int> Msi;
typedef set<int> Si;
typedef vector<int> Vi;
const int maxn = 1e6 + 100;
const int INF = 0x7fffffff;
const double eps = 1e-2;
struct node {
int x, y, r;
}e[maxn];
int n;
bool vis[maxn];
double et, lv;
void init() { met(vis, 0); et = lv = 1000.0; }
bool touch(int x, int y) {
return (e[x].x-e[y].x)*(e[x].x-e[y].x) + (e[x].y-e[y].y)*(e[x].y-e[y].y) - (e[x].r+e[y].r)*(e[x].r+e[y].r) <= 0;
}
bool bottom(int id) { return e[id].y-e[id].r <= 0; }
bool left(int id) { return e[id].x - e[id].r <= 0; }
bool right(int id) { return e[id].x + e[id].r >= 1000; }
bool bfs(int id) {
queue<int> q;
q.push(id);
while(!q.empty()) {
int t = q.front(); q.pop();
if(bottom(t)) return 1;
if(left(t)) et = min(et, e[t].y - sqrt(e[t].r*e[t].r-e[t].x*e[t].x));
if(right(t)) lv = min(lv, e[t].y - sqrt(e[t].r*e[t].r-(1000-e[t].x)*(1000-e[t].x)));
for(int i = 0; i < n; ++i) {
if(i == t) continue;
if(vis[i]) continue;
if(touch(t, i)) {
vis[i] = 1; q.push(i);
}
}
}
return 0;
}
int main() {
#ifdef _LOCAL
IN;
#endif // _LOCAL
while(scanf("%d", &n) == 1) {
init();
for(int i = 0; i < n; ++i) {
scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].r);
}
bool ok = 0;
for(int i = 0; i < n; ++i) {
if(e[i].y+e[i].r >= 1000 && !vis[i]) {
vis[i] = 1;
if(bfs(i)) { ok = 1; break; }
}
}
if(ok) printf("IMPOSSIBLE\n");
else printf("0.00 %.2lf 1000.00 %.2lf\n", et, lv);
}
return 0;
}