2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) 【solved:9 / 12】

来源:互联网 发布:手机订车票软件 编辑:程序博客网 时间:2024/06/05 07:32

A - Alphabet (LIS)

题意给你一个字符串,问你最少添加几个字符使得它能够存在一个子序列“abcdefg…xyz”。长度不超过50。

思路:26-LIS。

#include <bits/stdc++.h>using namespace std;int dp[1005];int main(){    string s;    cin >> s;    memset(dp, 0x3f3f3f3f, sizeof(dp));    for(int i = 0; i < s.size(); i++)        *lower_bound(dp, dp + s.size(), s[i]) = s[i];    cout << 26-(lower_bound(dp, dp + s.size(), 0x3f3f3f3f) - dp) << endl;    return 0;}

B - Buggy Robot (bfs)

题意:大概就是你写一个up,down,left,right的指令,操控小机器人走出迷宫,然后如果机器人遇到的这条指令,是让它走到障碍物上的,它会跳过这条指令。如果机器人到达了终点,则所有剩余指令失效。你可以通过增加和删除指令,来使得机器人走到中间,问你至少需要修改几个指令。迷宫的大小50*50。指令的长度是50。

思路:dp[i][j][k]:(i, j)这个位置 下一步执行原指令的第k条的时候 需要增加的最少指令数。然后转移一下就行了。用bfs。

#include <bits/stdc++.h>using namespace std;const int maxn = 50 + 5;const int INF = 0x3f3f3f3f;int n, m, sx, sy;struct node{int x, y, k;};int dx[] = {0, 0, -1, 1};int dy[] = {-1, 1, 0, 0};bool isInside(int x, int y){return 0 <= x && x < n && 0 <= y && y < m;}int dp[maxn][maxn][maxn];char ma[maxn][maxn];char cmd[maxn];int dir[200];int cmdLen;int bfs(){    int ret = INF;    memset(dp, INF, sizeof(dp));    dp[sx][sy][0] = 0;    queue<node>que;    que.push({sx,sy,0});    while(que.size())    {        node cur = que.front();que.pop();        if(ma[cur.x][cur.y] == 'E')        {            ret = min(ret, dp[cur.x][cur.y][cur.k]);            continue;        }        for(int i = 0; i < 4; i++)        {            int fx = cur.x + dx[i], fy = cur.y + dy[i];            if(!isInside(fx, fy) || ma[fx][fy] == '#')            {                if(dir[cmd[cur.k]] == i && dp[cur.x][cur.y][cur.k + 1] > dp[cur.x][cur.y][cur.k])                {                    if(dp[cur.x][cur.y][cur.k + 1] == INF)    que.push({cur.x, cur.y, cur.k+1});                    dp[cur.x][cur.y][cur.k + 1] = dp[cur.x][cur.y][cur.k];                }                continue;            }            int fk = cur.k, ans = dp[cur.x][cur.y][cur.k];            if(dir[cmd[cur.k]] == i)    fk++;            else ans++;            if(dp[fx][fy][fk] > ans)            {                if(dp[fx][fy][fk] == INF)    que.push({fx, fy, fk});                dp[fx][fy][fk] = ans;            }        }    }    return ret;}int main(){    memset(dir, -1, sizeof(dir));    dir['L'] = 0, dir['R'] = 1, dir['U'] = 2, dir['D'] = 3;    scanf("%d%d", &n, &m);    for(int i = 0; i < n; i++)    {        scanf("%s", ma[i]);        for(int j = 0; j < m; j++)        {            if(ma[i][j] == 'R') sx = i, sy = j;        }    }    scanf("%s", cmd);    printf("%d\n", bfs());    return 0;}

C - Cameras (贪心)

题意:数轴上1-n,你已经在其中k个位置有了标记,问你至少需要添加几个标记,使得任意连续r个位置,都至少有diff = 2个标记。2n100000,0kn,2rn

思路:先把[1,r]区间填充的满足条件,然后每次挪动其实只要更改第一个和最后一个就行了,你们觉不觉得其实这个diff这个不是2,是3是4是5都行呀,只要<=r。这题可以随便改啊,这样O(n)就够了。

#include<bits/stdc++.h>using namespace std;const int diff = 2;int vis[100000 + 5];int main(){    int n, k, r;    scanf("%d%d%d", &n, &k, &r);    for(int i = 0; i < k; i++)    {        int x;        scanf("%d", &x);        vis[x] = 1;    }    int num = 0, ans = 0;    for(int i = 1; i <= r; i++)    {        num += vis[i];    }    ans += max(0, diff - num);    for(int i = r; i >= 1; i --)    {        if(num == diff)  break;        if(!vis[i]) vis[i] = 1, num++;    }    //接下来可以保证每一个进入的区间[l,r]的前一个[l-1,r-1]都是满足diff这个条件的。因为每次只挪动1,所以只需要更改最后一个。    for(int nail = r + 1; nail <= n; nail++)    {        num = num - vis[nail - r] + vis[nail];        if(num < diff)  vis[nail] = 1, ans++, num = diff;    }    printf("%d\n", ans);    return 0;}

F - Illumination(2-SAT)

题意:给你一个n*n的方格,里面有l个方格有灯,每个灯可以选择左右发射长度r格的光或者上下发射,问你是否有一种方案,使得每个格子,不会被同为纵向(横向)的多束光照射。n1000
思路:2-SAT模板题

#include <bits/stdc++.h>using namespace std;const int maxn = 2000 + 5;struct TwoSat{//2i为假  2i+1为真    int n;    vector<int>G[maxn * 2];    bool mark[maxn * 2];    int S[maxn * 2], c;    void init(int n)    {        this->n = n;        for(int i = 0; i < n * 2; i++)   G[i].clear(), mark[i] = 0;    }    bool dfs(int x)    {        if(mark[x^1])   return false;        if(mark[x]) return true;        mark[x] = true;        S[c++] = x;        for(auto o : G[x])  if(!dfs(o)) return false;        return true;    }    void add_clause(int x, int xval, int y, int yval)    {        x = x * 2 + xval;        y = y * 2 + yval;        G[x^1].push_back(y);        G[y^1].push_back(x);    }    bool solve()    {        for(int i = 0; i < n * 2; i += 2)        {            if(!mark[i] && !mark[i + 1])            {                c = 0;                if(!dfs(i))                {                    while(c > 0)    mark[S[--c]] = false;                    if(!dfs(i + 1))  return false;                }            }        }        return true;    }}solver;struct node{int x, y;}nodes[maxn];int main(){    int n, r, l;    scanf("%d%d%d", &n, &r, &l);    for(int i = 0, x, y; i < l; i++)    {        scanf("%d%d", &x, &y);        nodes[i] = {x, y};    }    solver.init(l);    for(int i = 0; i < l; i++)    {        for(int j = i + 1; j < l; j++)        {            if(nodes[i].x == nodes[j].x && abs(nodes[i].y - nodes[j].y) <= r)            {                solver.add_clause(i, 0, j, 0);            }            if(nodes[i].y == nodes[j].y && abs(nodes[i].x - nodes[j].x) <= r)            {                solver.add_clause(i, 1, j, 1);            }        }    }    if(solver.solve())  puts("YES");    else puts("NO");    return 0;}

G - Maximum Islands(最小顶点覆盖)

题意:给你一个50*50 的图,里面有LWC三种字符,L代表陆地,W代表水,C代表可以由你决定是水还是陆地,问你这个图中L的联通块最多有几块。

思路:首先很容易想到把所有L联通块周围围上W,剩下的就是C了,然后我们可以发现要使得L的联通块最多,那么就尽量每个联通块只占用给一个点,那么就变成了剩下的C连成的图,划分成两个点集,然后成为一个最小顶点覆盖问题,由于是二分图,所有转换成二分最大匹配,无向图的二分匹配随便搞搞。

#include <bits/stdc++.h>using namespace std;const int maxn = 100 + 5;vector<int>G[2500 + 5];int used[2500 + 5], match[2500 + 5];bool dfs(int v){    used[v] = 1;    for(int i = 0; i < G[v].size(); i++)    {        int u = G[v][i], w = match[u];        if(w < 0 || !used[w] && dfs(w))        {            match[v] = u;            match[u] = v;            return true;        }    }    return false;}int hungary(int V){    int res = 0;    memset(match, -1, sizeof(match));    for(int u = 1; u <= V; u++)    {        if(match[u] < 0)        {            memset(used, 0, sizeof(used));            if(dfs(u))  res++;        }    }    return res;}int n, m;int dx[] = {0, 0, -1, 1};int dy[] = {1, -1, 0, 0};int vis[maxn][maxn];char ma[maxn][maxn];void dfs(int x, int y){    vis[x][y] = 1;    for(int i = 0; i < 4; i++)    {        int fx = dx[i] + x;        int fy = dy[i] + y;        if(0 <= fx && fx < n && 0 <= fy && fy < m)        {            if(ma[fx][fy] == 'C')   ma[fx][fy] = 'W';            else if(ma[fx][fy] == 'L' && vis[fx][fy] == 0)  dfs(fx, fy);        }    }}int main(){    cin >> n >> m;    for(int i = 0; i < n; i++)        cin >> ma[i];    int ans = 0;    for(int i = 0; i < n; i++)    {        for(int j = 0; j < m; j++)        {            if(ma[i][j] == 'L' && vis[i][j] == 0)            {                dfs(i, j), ans++;            }        }    }    int id = 1;    map<pair<int,int>, int>rec;    for(int i = 0; i < n; i++)    {        for(int j = 0; j < m; j++)        {            if(ma[i][j] == 'C')            {                if(rec[{i,j}] == 0)  rec[{i,j}] = id++;                for(int k = 0; k < 4; k++)                {                    int fx = i + dx[k], fy = j + dy[k];                    if(0 <= fx && fx < n && 0 <= fy && fy < m)                    {                        if(ma[fx][fy] == 'C')                        {                            if(rec[{fx,fy}] == 0) rec[{fx, fy}] = id++;                            int idx1 = rec[{i,j}], idx2 = rec[{fx, fy}];                            G[idx1].push_back(idx2);                            G[idx2].push_back(idx1);                        }                    }                }            }        }    }    int vs = id - 1;    if(vs)  ans = ans + vs -  hungary(vs);    cout << ans << endl;    return 0;}

H - Paint (dp)

题意:给你20w条线段,问你选其中若干条两两不相互覆盖的线段,最多能覆盖1-n这个区间中多少个点。

思路:离散化以后,做成点,dp[i]:前i个点最多能覆盖多少。

#include <bits/stdc++.h>using namespace std;typedef long long LL;vector<LL>point;vector<pair<LL, LL>>seg;vector<LL>G[400000 + 5];LL dp[400000 + 5];int main(){    LL n, k;    scanf("%lld%lld", &n, &k);    for(int i = 0; i < k; i++)    {        LL l, r;        scanf("%lld%lld", &l, &r);        r++;        point.push_back(l);        point.push_back(r);        seg.push_back({l, r});    }    sort(point.begin(), point.end());    point.resize(unique(point.begin(), point.end()) - point.begin());    for(int i = 0; i < k; i++)    {        LL lb = seg[i].first, rb = seg[i].second;        lb = lower_bound(point.begin(), point.end(), lb) - point.begin();        rb = lower_bound(point.begin(), point.end(), rb) - point.begin();        G[rb].push_back(lb);    }    dp[0] = 0;    for(int i = 1; i < point.size(); i++)    {        dp[i] = dp[i - 1];        for(auto o : G[i])        {            dp[i] = max(dp[i], dp[o] + point[i] - point[o]);        }    }    printf("%lld\n", n - dp[point.size()-1]);    return 0;}

I - Postman(贪心)

题意:有一个邮差员要去n家送信,他每次只能带k封信。每一家的坐标为xi,需要送mi封信,然后邮局在0点,问你最少走多少路能送完信。n1e3k,xi,mi1e7

思路:

  在纸上画一画,可以大概的发现,如果某次邮递员跨越了0点,那么和重新出发没有区别,哦不对,重新出发应该更优,能带更多的信。所以可以把问题拆分成两个独立的部分,解决正半轴以后,负半轴同理。

  正半轴怎么办呢,明显是由远及近的送,然后递减就好了。

#include <bits/stdc++.h>using namespace std;struct node{    int x, m;    bool operator < (const node &other)const    {        if(x != other.x)    return x < other.x;        return m < other.m;    }};int main(){    int n, k;    scanf("%d%d", &n, &k);    priority_queue<node>que1, que2;    for(int i = 0, x, m; i < n; i++)    {        scanf("%d%d", &x, &m);        if(x < 0)   que2.push({-x, m});        else que1.push({x, m});    }    long long ans = 0;    while(que1.size())    {        node cur = que1.top();que1.pop();        long long times = (cur.m + k - 1) / k;        long long Left = times * k - cur.m;        ans += 2LL * cur.x * times;        while(que1.size() && Left)        {            node temp = que1.top();que1.pop();            if(temp.m <= Left)   Left -= temp.m;            else if(temp.m > Left)            {                temp.m -= Left;                Left = 0;                que1.push(temp);            }        }    }    while(que2.size())    {        node cur = que2.top();que2.pop();        long long times = (cur.m + k - 1) / k;        long long Left = times * k - cur.m;        ans += 2LL * cur.x * times;        while(que2.size() && Left)        {            node temp = que2.top();que2.pop();            if(temp.m <= Left)   Left -= temp.m;            else if(temp.m > Left)            {                temp.m -= Left;                Left = 0;                que2.push(temp);            }        }    }    printf("%lld\n", ans);    return 0;}

J - Shopping(RMQ+二分)

题意:给了n件商品(n2e5),接下来有q个客户(q2e5),每个客户有一个金钱value(value1e18),和购买区间。它会从左到右挨个购买商品,一直尽可能的买每个商品,问最后剩下多少钱。

思路:因为可以考虑到%的特殊性,最多只会执行log次,所以直接用rmq维护一个区间最小值,然后二分找到右边第一个小于当前值的位置即可。

#include <bits/stdc++.h>using namespace std;const int maxn = 200000 + 5;long long a[maxn];long long d[maxn][20];void initRMQ(int n, long long a[]){    for(int i = 0; i < n; i++)  d[i][0] = a[i];    for(int j = 1; (1 << j) <= n; j++)    {        for(int i = 0; i + (1 << j) - 1 < n; i++)        {            d[i][j] = min(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);        }    }}long long query(int l, int r){    int k = 0;    while((1 << (k + 1)) <= r - l + 1)    k++;    return min(d[l][k], d[r - (1 << k) + 1][k]);}long long solve(long long v, int ql, int qr){    while(v)    {        int lb = ql, rb = qr;        if(query(lb, rb) > v)  return v;        while(lb < rb)        {            int mid = (lb + rb) / 2;            if(query(lb, mid) <= v) rb = mid;            else lb = mid + 1;        }        v %= a[rb];        ql = rb + 1;        if(ql > qr) return v;    }    return v;}int main(){    int n, q;    scanf("%d%d", &n, &q);    for(int i = 0; i < n; i++)  scanf("%lld", &a[i]);    initRMQ(n, a);    while(q--)    {        long long v;        int ql, qr;        scanf("%lld%d%d", &v, &ql, &qr);        ql--, qr--;        printf("%lld\n", solve(v, ql, qr));    }    return 0;}
阅读全文
0 0
原创粉丝点击