A Plug for UNIX POJ - 1087 (读题的技巧+建图+Dinic+建图标点的技巧)

目录

关于读题:

关于建图标点:


传送门

题意:

一间房间有n个插座,有m个电器要使用一些插座(有的插座没在n个里面),有k个转换器,每个转换器可以插在相应的插座或者转换器上(插上之后相当于插座变成另一种插座),每个转换器上可以插相应的插头或者转换器(转换器可以当作插头看待)。要求尽可能多的插头能充电,问最少有多少电器充不上点。

  • n,m,k<=100
  • 插座,插头都有不超过24位的字符串表示
  • n个插座各不一样(题目好像没有明显说)
  • 转换器的种类无穷多
  • 输入:n->n个插座->m->m个电器名称以及插头种类(只能插相应种类的插座)->k->k个转换器插的(插座/转换器。可以视作插在插座上面就变成了另一种插座,同时视转换器本身为插头)的种类以及可以插在它上面的(插头/转换器)的种类
  • 输出:一个整数,最少的充不上电的电器的数量

关于读题:

  • 题目阅读真的只一间难事,尽量多习惯英文阅读罢,翻译出来的东西太TM感人了(主要是区域赛都是这类型emm)。
  • 不要浮躁,急于求出,急功近利。就慢慢读题,慢慢写题,你感觉自己太慢了,才会更觉得轻松,更觉得自己学有余力。——慢才能保证效率,觉得自己学有余力才会更有动力。

题解:

  1. 无他,读题+建图。
  2. 图:源点->插头->插座->汇点(到达汇点就表示匹配到了一对)

关于建图标点:

一个一个标,就不必特地的计算拿个点表示的是什么(大概只是一些情况用得到,但实测相当好用)

代码:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 2e3 + 10;  // 100个点?
const int M = 2e3 + 10;
const int INF = 1e9 + 10;
int n, m, k;
int s, t;
struct Edge {
    int v, w, next;
    Edge(int v = 0, int w = 0, int next = 0) : v(v), w(w), next(next) {}
} e[M];
int head[N], now[N], cnt;
void add(int u, int v, int w) {
    e[cnt] = Edge(v, w, head[u]), head[u] = cnt++;
    e[cnt] = Edge(u, 0, head[v]), head[v] = cnt++;
}

int dep[N];
// bfs使dfs按照dep来增广,得到优化(具体证明就看不太懂了)。
bool bfs() {
    memset(dep, -1, sizeof(dep));
    queue<int> q;
    q.push(s), dep[s] = 0;
    now[s] = head[s];  //当前弧优化(初始化)
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = now[x]; ~i; i = e[i].next) {
            int v = e[i].v;
            if (dep[v] == -1 && e[i].w > 0) {
                dep[v] = dep[x] + 1;
                now[v] = head[v];  //当前弧优化(初始化)。
                q.push(v);
                if (v == t) return true;
                //遇到v==t即退出,因为<dep[v]的已经遍历完了,dfs的增广路不可能经过深度>=dep[v]的点
            }
        }
    }
    return false;
}
//一次dfs可能有几条增广路:见图
int dfs(int x, int flow) {
    if (x == t) return flow;
    int ans = 0;
    // flow表示x点还能操作的流量,ans表示增广的这几路后流出x总最大流
    // flow不剩下了,就没必要增广了
    for (int i = now[x]; ~i && flow; i = e[i].next) {
        int v = e[i].v;
        now[x] = i;
        //当前弧优化(这里才能得到优化,bfs中的只是初始化:另now[x]=head[x])
        if (dep[v] == dep[x] + 1 && e[i].w > 0) {
            int tmp = dfs(v, min(flow, e[i].w));
            // tmp为流过点v的最大流量
            if (tmp == 0) dep[v] = -1;  //剪枝,去掉增广完毕的点
            e[i].w -= tmp;
            e[i ^ 1].w += tmp;
            ans += tmp;
            flow -= tmp;  //流过当前点(x)的流量累加(回溯)
        }
    }
    return ans;  //最后返回的是这几路增广的流量和
}
int Dinic() {
    int maxflow = 0;
    // bfs为真就一直dfs多路增广
    while (bfs()) maxflow += dfs(s, INF);
    return maxflow;
}

map<string, int> mp;
string s0, s1;
int id;
void init() {
    memset(head, -1, sizeof(head));
    read(n);
    s = ++id, t = ++id;  //这也许,是一种极好的设置下标的方式
    for (int i = 1; i <= n; i++) {
        cin >> s0;
        mp[s0] = ++id;
        add(id, t, 1);  //每种插座只有一个,还有插头需要的插座可能没有出现
        //插座与汇点相连
    }
    read(m);
    for (int i = 1; i <= m; i++) {
        cin >> s0 >> s1;
        add(s, ++id, 1);  //源点与插头相连。插头设点
        //需要但是没有的插座(就看后面转换器adapter有没有转机)
        if (!mp[s1]) {
            mp[s1] = ++id;
            //, add(id, t, 1);  //插座全与汇点t相连
            //注意了,并不是所有插座都与汇点相连,这种只配使用转换器,不能直接出结果(到t就相当关于直接完成匹配)
            add(id - 1, id, 1);  //插头插在插座上
        } else
            add(id, mp[s1], 1);  //插头插在插座上
    }
    read(k);
    for (int i = 1; i <= k; i++) {
        cin >> s0 >> s1;
        if (!mp[s0]) mp[s0] = ++id;
        if (!mp[s1]) mp[s1] = ++id;
        add(mp[s0], mp[s1], INF);  //无成本将s0插座转换为s1(不需要用到s1)
    }
}

signed main() {
    init();
    int maxflow = Dinic();
    print(m - maxflow, '\n');
    return 0;
}
/*
1 1
1
1

2 2
1 0
1 0
1 1
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POJ - 3616是一个题目,题目描述如下: 给定一组区间,每个区间有一个权重,要求选择一些区间,使得这些区间的右端都小于等于k,并且权重之和最大。请问最大的权重和是多少? 解决这个问题的思路是使用动态规划。首先,将区间按照左端从小到大进行排序。然后,定义一个dp数组,dp[i]表示右端小于等于i的所有区间所能得到的最大权重。 接下来,遍历每一个区间,对于每个区间i,将dp[i]初始化为区间i的权重。然后,再遍历i之前的每个区间j,如果区间j的右端小于等于k,并且区间j的权重加上区间i的权重大于dp[i],则更新dp[i]为dp[j]加上区间i的权重。 最后,遍历整个dp数组,找到最大的权重和,即为所求的答案。 下面是具体的代码实现: ```cpp #include <cstdio> #include <cstring> #include <algorithm> using namespace std; struct interval{ int start, end, weight; }; interval intervals[10005]; int dp[10005]; int n, m, k; bool compare(interval a, interval b) { if (a.start == b.start) { return a.end < b.end; } else { return a.start < b.start; } } int main() { while(~scanf("%d %d %d", &n, &m, &k)) { memset(dp, 0, sizeof dp); for (int i = 0; i < m; i++) { scanf("%d %d %d", &intervals[i].start, &intervals[i].end, &intervals[i].weight); } sort(intervals, intervals + m, compare); for (int i = 0; i < m; i++) { dp[i] = intervals[i].weight; for (int j = 0; j < i; j++) { if (intervals[j].end <= k && dp[j] + intervals[i].weight > dp[i]) { dp[i] = dp[j] + intervals[i].weight; } } } int maxWeight = 0; for (int i = 0; i < m; i++) { maxWeight = max(maxWeight, dp[i]); } printf("%d\n", maxWeight); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值