入门经典_Chap06_例题[四]:最后四题

来源:互联网 发布:cygwin 运行linux程序 编辑:程序博客网 时间:2024/06/05 17:20

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;}

以上。

阅读全文
1 0
原创粉丝点击