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

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值