2015 Astar Contest - Round 3 题解

1001 数长方形

题目大意

平面内有N条平行于坐标轴的线段,且不会在端点处相交
问共形成多少个矩形

算法思路

枚举4条线段的所有组合,分别作为矩形四条边,判断是否合法

时间复杂度: O(N4)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: A.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-06
 */
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <complex>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

typedef complex<int> Point;
#define X real()
#define Y imag()

const int maxn = 25 + 5;

int n;
Point s[maxn], t[maxn];
bool o[maxn];

bool inter(int i, int j)
{
        if (s[j].Y >= s[i].Y || t[j].Y <= s[i].Y) return false;
        return s[i].X < s[j].X && s[j].X < t[i].X;
}

bool check(int d, int u, int l, int r)
{
        if (!inter(d, l)) return false;
        if (!inter(d, r)) return false;
        if (!inter(u, l)) return false;
        if (!inter(u, r)) return false;
        return true;
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d", &n);
                rep(i,n) {
                        int x1, y1, x2, y2;
                        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                        if (x1 > x2) swap(x1, x2);
                        if (y1 > y2) swap(y1, y2);
                        s[i] = Point(x1, y1);
                        t[i] = Point(x2, y2);
                        o[i] = (x1 == x2);
                }
                int res = 0;
                rep(d,n) if (!o[d]) rep(u,n) if (u > d && !o[u]) {
                        rep(l,n) if (o[l]) rep(r,n) if (r > l && o[r]) {
                                if (check(d, u, l, r)) ++res;
                        }
                }
                printf("Case #%d:\n", ++cas);
                printf("%d\n", res);
        }

        return 0;
}

1002 弹吉他

题目大意

给出N个和弦需要按下的弦与品,每个和弦可以选择某种手势
序号大的手指所处的品位不能小于序号小的手指
移动某个手指的代价为曼哈顿距离,问依次弹出这N个和弦的最小代价

算法思路

DP,每个和弦最多有4!种手势
状态f[i][S]表示S对应的手势弹奏第i个和弦后,花费的最小代价
状态转移:f[i][S] = min{f[i-1][S0] + dis(S0, S)}
其中S可以直接存在4维数组中,注意检查手势是否合法

时间复杂度: O(N×4!2)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: B.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-06
 */
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 5000 + 5;

int n;
Pii p[maxn][4];
int f[maxn][4][4][4][4];

bool check(int k, int y[])
{
        rep(i,4) rep(j,4) if (j > i)
                if (p[k][y[i]].second > p[k][y[j]].second) return false;
        return true;
}

int dis(int i, int x, int y)
{
        int dx = p[i][x].first - p[i+1][y].first;
        int dy = p[i][x].second - p[i+1][y].second;
        return abs(dx) + abs(dy);
}

int solve()
{
        int x[4], y[4];
        memset(f, 0x3f, sizeof(f));
        f[0][0][1][2][3] = 0;
        rep(i,n) {
                rep(j,4) x[j] = j;
                do {
                        int t = f[i][x[0]][x[1]][x[2]][x[3]];
                        if (t == inf) continue;
                        rep(j,4) y[j] = j;
                        do {
                                if (!check(i + 1, y)) continue;
                                int r = t;
                                rep(j,4) r += dis(i, x[j], y[j]);
                                int &res = f[i+1][y[0]][y[1]][y[2]][y[3]];
                                res = min(res, r);
                        } while (next_permutation(y , y + 4));
                } while (next_permutation(x, x + 4));
        }
        int res = inf;
        rep(j,4) x[j] = j;
        do {
                res = min(res, f[n][x[0]][x[1]][x[2]][x[3]]);
        } while (next_permutation(x, x + 4));
        return res;
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d", &n);
                rep(j,4) p[0][j] = Pii(0, j + 1);
                For(i,1,n) rep(j,4)
                        scanf("%d%d", &p[i][j].first, &p[i][j].second);
                printf("Case #%d:\n", ++cas);
                printf("%d\n", solve());
        }

        return 0;
}

1003 行路难

题目大意

给出一张有向图,边权为字符串,找出指定起点到终点,字典序最小的路径
如果不存在或长度为无穷,则输出”Tough way!”

算法思路

最短路,对于我的实现方法,需要从终点向起点松弛
因为如果从起点开始,对于某个字符串是另一个串前缀的情况,无法确定保留哪个
为了避免负环对spfa队列的影响,这里使用bellman-ford算法

  • 当起点在n-1次松弛后被更新,则答案无限长
  • 松弛6n次后起点答案不再变化

下面给出粗略的证明:

由于最短路上最多有n-1条边,所以n-1次松弛后,更新一定引入重边,由此可以构造负环

而如果最短路上不存在负环,则n-1条边构成的字符串最长为6(n-1)
超过6n次松弛后,负环的长度肯定超过6(n-1),如果没有更新过起点,就再也不会更新了
至于为什么至少需要6n,可以参考下图
样例

时间复杂度: O(VE)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: C.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-06
 */
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 50 + 5;
const int maxe = 500 + 5;

int psz;
struct Edge {
        int u, v;
        string w;
        Edge *next;
} epool[maxe];

void add_edge(int u, int v, string w)
{
        Edge *p = epool + psz++;
        p->u = u; p->v = v; p->w = w;
}

int n, m, s, t;
string dis[maxn];
bool vis[maxn];

string bellman_ford()
{
        int clk = 0;
        memset(vis, false, sizeof(vis));
        dis[t] = ""; vis[t] = true;
        while (true) {
                bool update = false;
                for (Edge *i = epool; i < epool + psz; ++i) if (vis[i->v]) {
                        int u = i->u, v = i->v;
                        string tmp = i->w + dis[v];
                        if (!vis[u] || tmp < dis[u]) {
                                dis[u] = tmp;
                                update = vis[u] = true;
                                if (clk >= n - 1 && u == s) return "Tough way!";
                        }
                }
                if (!update || ++clk > n * 6) break;
        }
        return vis[s]? dis[s]: "Tough way!";
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                psz = 0;
                scanf("%d%d%d%d", &n, &m, &s, &t);
                while (m--) {
                        int u, v;
                        char buf[8];
                        scanf("%d%d%s", &u, &v, buf);
                        add_edge(u, v, buf);
                }
                printf("Case #%d:\n", ++cas);
                puts(bellman_ford().c_str());
        }

        return 0;
}

1004 蜀道难

题目大意

有N座山均匀分布在圆周上,相邻两座山之间的弧长为R,找出距离最远的两个山顶
其中山顶的距离为两座山的高度和,加上两座山沿圆弧的最短距离

算法思路

维护连续区间最值
将环复制一遍后变成链上的问题,不难发现,离某座山最远的山一定在它的前面的N/2座中
所以向右遍历每座山的同时,维护向前N/2宽度的区间内的最值,加上当前山的高度更新答案
而将当前高度H插入区间时,需要将区间内的值统一加上R
可以记录一个总的增量D,将H - D插入区间,再将D累加R
这样区间内的大小关系不变,而此时的最值只需要加上D,便可得到实际值
下面的代码中,使用了单调队列维护这一最值

时间复杂度: O(N)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: D.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-06
 */
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

typedef pair<int, LL> Pil;

const int maxn = 100000 + 5;

int n, r, l;
int h[maxn*2];

struct Queue {
        LL d;
        deque<Pil> deq;
        void clear() { d = 0; deq.clear(); }
        void push(int i)
        {
                while (!deq.empty() && h[i] > deq.back().second + d)
                        deq.pop_back();
                deq.push_back(Pii(i, h[i] - d)); d += r;
        }
        Pil query()
        {
                Pil res = deq.front(); res.second += d;
                return res;
        }
        void pop(int i)
        {
                if (!deq.empty() && deq.front().first == i)
                        deq.pop_front();
        }
} que;

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%d", &n, &r);
                l = n / 2;
                rep(i,n) scanf("%d", &h[i]), h[n+i] = h[i];
                que.clear();
                rep(i,l) que.push(i);
                LL res = 0; Pii resp;
                for (int i = l; i < 2 * n; ++i) {
                        LL v = que.query().second + h[i];
                        Pii p = Pii(i % n, que.query().first % n);
                        if (p.first > p.second) swap(p.first, p.second);
                        if (v > res) {
                                res = v; resp = p;
                        } else if (v == res) {
                                resp = min(resp, p);
                        }
                        que.pop(i - l);
                        que.push(i);
                }
                printf("Case #%d:\n", ++cas);
                printf("%d %d\n", resp.first + 1, resp.second + 1);
        }

        return 0;
}

1005 最强密码

题目大意

给出一个“密码生成串”
找到一个“最强密码”,不是这个“密码生成串”的子序列,并且长度最短
统计“最强密码”的个数

算法思路

DP
f[i]表示不是“原串以i结尾前缀”的子序列的最短字符串的长度
g[i]是上述字符串的个数
维护i前面每个字符最后出现的位置last[]
若存在未出现的字符,则
f[i] = 1
g[i] = 未出现的字符个数
否则
f[i] = min{f[last[j]]} + 1
g[i] = sum{g[last[j]] | f[[last[j]] + 1 == f[i]}
上式的含义为,i之前,以各个字母结尾的,长度为f[i]-1的密码串个数的累加

时间复杂度: O(26N)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: E.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-06
 */
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int mod = 1000000007;
const int maxn = 100000 + 5;

int n;
char s[maxn];
int last[26];
int f[maxn], g[maxn];

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%s", s);
                n = strlen(s);
                memset(last, -1, sizeof(last));
                For(i,0,n) {
                        vector<int> vec;
                        rep(j,26) if (~last[j]) vec.push_back(last[j]);
                        if (i < n) last[s[i]-'a'] = i;
                        if (vec.size() < 26) {
                                f[i] = 1;
                                g[i] = 26 - vec.size();
                        } else {
                                int t = inf;
                                foreach(it,vec) t = min(t, f[*it]);
                                f[i] = t + 1;
                                g[i] = 0;
                                foreach(it,vec) if (f[*it] == t)
                                        g[i] = (g[i] + g[*it]) % mod;
                        }
                }
                printf("Case #%d:\n", ++cas);
                printf("%d %d\n", f[n], g[n]);
        }

        return 0;
}

1006 平衡大师

题目大意

对一张N个点的有向图进行删边,使得每个点“入度减出度绝对值”的最大值最小
要求至少保留K条边

算法思路

费用流

先考虑最大流
对于有向图,存在总入度等于总出度这一性质,类比于网络流中的流量平衡
因此,从源点向入度大于出度的点连边,而入度小于出度的点连向汇点
由于要达到最大流,前者尽量向网络中流出,而后者则尽量从网络中流入
至于费用
如果给网络中每条边加上为1的费用,则通过网络的流量需要花费代价

回到本题,可以二分每个点“入度减出度绝对值”的最大值
对于不超过这个值的流量,可以建立一个临时节点,免费将流量流向此处,或从此处获取流量
而对于超出的部分,依然只能走网络中通过,带来的费用相当于需要删除的边
此时二分的判断条件为,删除等量于最大流最小费用的边后,是否还留有至少K条边

时间复杂度: O(logV×kVE)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: F.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-06
 */
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#include <map>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 50 + 5;
const int maxe = 10000 + 5;

class Cost_flow {
        int n, psz;
        struct Edge {
                int u, v, r, c;
                Edge *next, *cp;
        } epool[maxe], *e[maxn];
        int s, t, dist, cost;
        int dis[maxn];
        bool inq[maxn], vis[maxn];

        bool modlable()
        {
                deque<int> deq;
                memset(dis, 0x3f, sizeof(dis));
                memset(inq, false, sizeof(inq));
                dis[t] = 0; inq[t] = true; deq.push_back(t);
                while (!deq.empty()) {
                        int u = deq.front(); deq.pop_front(); inq[u] = false;
                        for (Edge *i = e[u]; i; i = i->next) if (i->cp->r) {
                                int v = i->v;
                                if (dis[v] <= dis[u] - i->c) continue;
                                dis[v] = dis[u] - i->c;
                                if (inq[v]) continue; inq[v] = true;
                                deq.empty() || dis[v] < dis[deq.front()]?
                                        deq.push_front(v): deq.push_back(v);
                        }
                }
                for (int u = 0; u < n; ++u)
                        for (Edge *i = e[u]; i; i = i->next)
                                i->c += dis[i->v] - dis[u];
                dist += dis[s];
                return dis[s] < inf;
        }

        int aug(int u, int m)
        {
                if (u == t) return cost += dist * m, m;
                int f = 0; vis[u] = true;
                for (Edge *i = e[u]; i; i = i->next) {
                        int v = i->v;
                        if (i->r && !i->c && !vis[v]) {
                                int d = aug(v, min(i->r, m));
                                i->r -= d; i->cp->r += d; m -= d; f += d;
                                if (!m) break;
                        }
                }
                return f;
        }

        public:
        void init(int n)
        {
                this->n = n; psz = 0;
                memset(e, 0, sizeof(e));
        }

        void add_edge(int u, int v, int w, int c)
        {
                Edge *i = epool + psz;
                i->v = v; i->r = w; i->c = c; i->next = e[u]; e[u] = i;
                i->cp = epool + (psz++ ^ 1);
                if (psz & 1) add_edge(v, u, 0, -c);
        }

        int min_cost(int s, int t, int &flow)
        {
                this->s = s; this->t = t; dist = cost = flow = 0;
                while (modlable()) {
                        int d;
                        do {
                                memset(vis, false, sizeof(vis));
                                flow += (d = aug(s, inf));
                        } while (d);
                }
                return cost;
        }
} grp;

int n, m, k, tot;
int u[maxe], v[maxe], deg[maxn];
map<string, int> id;

int get_id(string s)
{
        if (id.count(s)) return id[s];
        return id[s] = tot++;
}

bool check(int c)
{
        grp.init(n + 3);
        int src = n, trg = n + 1, tmp = n + 2;
        rep(i,m) grp.add_edge(u[i], v[i], 1, 1);
        rep(i,n) {
                if (deg[i] > 0) {
                        grp.add_edge(src, i, deg[i], 0);
                        grp.add_edge(i, tmp, c, 0);
                }
                if (deg[i] < 0) {
                        grp.add_edge(i, trg, -deg[i], 0);
                        grp.add_edge(tmp, i, c, 0);
                }
        }
        return m - grp.min_cost(src, trg, tmp) >= k;
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%d%d", &n, &m, &k);
                tot = 0;
                id.clear();
                memset(deg, 0, sizeof(deg));
                rep(i,m) {
                        char s[24];
                        scanf("%s", s);
                        u[i] = get_id(s);
                        scanf("%s", s);
                        v[i] = get_id(s);
                        ++deg[u[i]], --deg[v[i]];
                }
                int l = 0, r = n;
                while (l < r) {
                        int mid = (l + r) >> 1;
                        if (check(mid)) r = mid;
                        else l = mid + 1;
                }
                printf("Case #%d:\n", ++cas);
                printf("%d\n", r);
        }

        return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值