2017-2018 ACM-ICPC Pacific Northwest Regional Contest (Div. 1)

本文解析了多项编程竞赛题目,包括比赛激情扩大、因子恐惧求和、彩虹之路等,提供了详细的解题思路及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

B - Enlarging Enthusiasm

题意:
          nn个歌手参加比赛,经过最后一轮的比赛后,他们的分数分别为pi;现在裁判有xx分的附加分数, 每个歌手可以得到大于一分的附加分,裁判为了使比赛尽可能的充满激情,他们会每次选择一个选手,给这个选手加上 qi 的附加分并使他的分数为当前第一名,裁判每轮给的附加分不会比上一轮给的附加分低,每次宣布完一个歌手的分数后,最高得分的人一定是唯一的且最高得分的人要更新。宣布歌手的分数是按照歌手的附加分从小到大的顺序宣布,而宣布的是歌手的总分,按附加分从小到大宣布第ii个歌手总分的时候,后面的歌手的分数处于暂时不添加附加分数的状态。

思路:
     dp[S][i][j]:dp[S][i][j]:当现在状态为SS,上一个分配的歌手是i,剩余jj点未分配的方案数,那么第一次分配,肯定不能给得分最高的歌手分配,因为这样将会排名不变,而后每次的分配,歌手的总分一定要比上一个分配的人高,这样排名才会发生变化,那么也就是下次分配的点数本次分配的点数,因为它只是判断存不存在,那么一定是将剩余点数尽可能少的分配给当前考虑的歌手,这样才是最优的,当分配完kk点之后, 剩余的歌手也至少分配k点,我们可以让他们同时减去kk,这样他们的相对得分就不变了,状态量也缩小了很多。

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 705;
const int maxS = (1 << 12) + 3;
using namespace std;

int n, m, T, kase = 1, x;
int dp[maxS][15][maxn];
int p[maxn];

///dp[S][las][res] : 现在是状态S, 上一个人是las,剩余res点没有分配
///形成递增序列
int dfs(int S, int las, int res) {
    if(res < 0) return 0; ///不可能分配
    if(!S) return 1; ///全部人数分配完毕
    int &ans = dp[S][las][res];
    if(~ans) return ans;
    int tot = 0; ans = 0;
    for(int i = 0; i < n; i++) if(S & (1 << i)) tot++;
    for(int i = 0; i < n; i++) {
        if(!(S & (1 << i))) continue; ///第i个人已经分好
        int nxt_S = S & (~(1 << i));
        int nxt_need = max(0, p[las] - p[i] + 1); ///最少的数量
        ans += dfs(nxt_S, i, res - nxt_need * tot); ///res - nxt_need * tot, 是保证后面的那些点数相对点数不变,状态量就缩小很多
    }
    return ans;
}

int main() {
    while(scanf("%d %d", &n, &x) != EOF) {
        memset(dp, -1, sizeof dp);
        memset(p, 0, sizeof p);
        int mx = 0, ans = 0;
        for(int i = 0; i < n; i++) { scanf("%d", &p[i]); mx = max(mx, p[i]); }
        for(int i = 0; i < n; i++) if(p[i] != mx) ans += dfs(((1 << n) - 1) & (~(1 << i)), i, x - max(1, mx - p[i] + 1) * n);
        cout << ans << endl;
    }
    return 0;
}

C - Fear Factoring

题意:
     n=abx|nx∑n=ab∑x|nx1ab10121⩽a⩽b⩽1012ba106b−a⩽106

思路:
          枚举每个因子的贡献,对于大于106106的因子贡献最多一次, 所以枚举11106的时候在处理下106106以后的因子的贡献就行了.

#include<bits/stdc++.h>
typedef long long ll;
const ll maxn = 1e6 + 10;
const ll INF = 1e10;
const double eps = 1e-8;
using namespace std;

ll n, m, T, kase = 1;
char s[maxn];

ll solve(ll x, ll y) {
    ll ans = 0;
    for(ll i = 1; i < maxn; i++) {
        ll tot1 = (x - 1) / i, tot2 = y / i;
        if(tot2) ans += (tot2 - tot1) * i;
        ll k = max(maxn, tot1 + 1);
        if(tot2 >= k) {
            if((tot2 + k) % 2 == 0) ans += (tot2 - k + 1) * ((tot2 + k) / 2);
            else ans += (tot2 - k + 1) / 2 * (tot2 + k);
            //cout << ans << endl;
        }
    }
    return ans;
}

int main() {
    while(scanf("%lld %lld", &n, &m) != EOF) cout << solve(n, m) << endl;
    return 0;
}

D - Rainbow Roads

题意:
        一棵nn个节点的树,每条边有一种颜色(标号1nn),定义一个好的节点为:该节点到树上其他所有节点的简单路径中没有任何两条相邻的边的颜色相同,问有多少个好的节点。
思路:
    从根节点往下搜索,遇到一个节点uu和他的父节点的连边还有和孩子节点v的连边颜色相同的话,那么uu的非子树节点和v的子树节点都不行了,跑个dfsdfs序树状数组更新一下,还有就是和两个孩子节点的连边颜色一样的话,两个孩子节点的子树也都不可以了,更新之后剩余没更新到的就是答案。

#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
using namespace std;

struct Edge {
    int v, col, vis;
    bool operator < (Edge e) const { return col < e.col; }
};
vector<Edge> G[maxn], g[maxn];
vector<int> ans, vec, nxt;
int flag[maxn], col[maxn], C[maxn];
int l[maxn], r[maxn], dfn[maxn], cnt;

void update(int x, int val) { for( ; x <= cnt; x += x & -x) C[x] += val; }
int get_sum(int x) { int ans = 0; for( ; x; x -= x & -x) ans += C[x]; return ans; }

void dfs(int x, int fa) {
    dfn[++cnt] = x; l[x] = cnt;
    for(int i = 0; i < G[x].size(); i++) {
        int v = G[x][i].v;
        if(v == fa) continue;
        g[x].push_back(G[x][i]);
        dfs(v, x);
    }
    r[x] = cnt;
}

void dfs2(int x, int fa, int c) {
    sort(g[x].begin(), g[x].end());
    int flag = 0;
    for(int i = 0; i < g[x].size(); i++) {
        int v = g[x][i].v, cl = g[x][i].col;
        if(cl == c) {
            flag = 1;
            update(l[v], -1); update(r[v] + 1, 1);
        }
        dfs2(v, x, cl);
    }
    int L = l[x], R = r[x];
    if(flag) {
        update(R + 1, -1);
        update(1, -1);
        update(L, 1);
    }
    for(int i = 0; i < g[x].size(); i++) {
        int from = i;
        while(from < g[x].size() && g[x][from].col == g[x][i].col) from++;
        if(from - i > 1) {
            while(i < from) {
                int j = g[x][i].v;
                update(l[j], -1);
                update(r[j] + 1, 1);
                i++;
            }
        }
        i = from - 1;
    }
}

void solve(int n) {
    cnt = 1;
    dfs(1, 0);
    dfs2(1, 0, 0);
    ans.clear();
    for(int i = 1; i <= n; i++) {
        int flag = get_sum(l[i]);
        if(!flag) ans.push_back(i);
    }
    cout << ans.size() << endl;
    for(int i = 0; i < ans.size(); i++) cout << ans[i] << endl;
}

int main() {
    int n;
    while(cin >> n) {
        for(int i = 0; i < maxn; i++) { g[i].clear(); G[i].clear(); }
        ans.clear();
        for(int i = 1; i < n; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            G[u].push_back(Edge{v, w, 0});
            G[v].push_back(Edge{u, w, 0});
        }
        solve(n);
    }
    return 0;
}

G - Security Badge

题意:
          nn(1n1000)个房间,m(1m5000)m(1⩽m⩽5000)条有向路径,房间aiai到房间bibi的路径有一个安全度范围[ci,di][ci,di],现在有kk个人(1<=k<=1e9),代表的安全度标号是11k,要从房间ss到房间t,问有多少人能安全到达tt这个房间(路径安全度范围如果是[ci,di], 只有标号范围在[ci,di][ci,di]个人可以经过这条路径)。
思路:
          可以先将安全度范围离散化, 再去判断每个范围可不可达就行了。

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e3 + 10;
using namespace std;

struct edge {
    int from, to, c, d;
    edge() {}
    edge(int f, int t, int c, int d) :
        from(f), to(t), c(c), d(d) {}
};
int n, m, k, T, kase = 1, s, t;
vector<edge> G[maxn];
int res[maxn * 30], vis[maxn];
typedef pair<int, int> pa;

bool solve(int s, int t, int C, int D) {
    queue<int> que;
    memset(vis, 0, sizeof vis);
    que.push(s); vis[s] = 1;
    while(!que.empty()) {
        int u = que.front(); que.pop();
        if(u == t) return true;
        for(int i = 0; i < G[u].size(); i++) {
            edge e = G[u][i];
            int v = e.to, ci = e.c, di = e.d;
            if(vis[v]) continue;
            if(!(ci <= C && di >= D - 1)) continue;
            vis[v] = 1; que.push(v);
        }
    }
    return false;
}

int main() {
    while(scanf("%d %d %d", &n, &m, &k) != EOF) {
        scanf("%d %d", &s, &t);
        int num = 0;
        for(int i = 1; i <= m; i++) {
            int u, v, c, d;
            scanf("%d %d %d %d", &u, &v, &c, &d);
            G[u].push_back(edge(u, v, c, d));
            res[num++] = c; res[num++] = d;
            res[num++] = c + 1; res[num++] = d + 1;
            res[num++] = c - 1; res[num++] = d - 1;
        }
        res[num++] = 1; res[num++] = 2; res[num++] = k + 1; res[num++] = k; res[num++] = 0;
        sort(res, res + num);
        num = unique(res, res + num) - res;
        int ans = 0;
        for(int i = 1; res[i] <= k; i++) {
            int from = res[i], to = res[i + 1];
            if(solve(s, t, from, to)) ans += to - from;
        }
        printf("%d\n", ans);
    }
    return 0;
}

H. Avoiding Airports

题意:
        你要从城市11到城市n,现在有mm次航班,第i次航班是从城市aiai到城市bibi(单向),起飞时间是sisi,到达时间是eiei,现在你对坐飞机没有沮丧感,但是等飞机你有沮丧感,当你等下一趟航班等了tt时间的时候,你产生的沮丧感将是t2,问你到城市nn的沮丧感之和最小是多少?
思路:
    对所有的边按照开始时间排序,处理到飞机从aab, 起飞时间是ss,到达时间是t的这条边的时候, 假设dp[b][t]:dp[b][t]:tt时刻到达b城市的最小沮丧感, 那么有:
dp[b][t]=min{dp[a][e]+(se)2}dp[b][t]=min{dp[a][e]+(s−e)2}
            =min{dp[a][e]+s22se+e2}            =min{dp[a][e]+s2−2se+e2}
            =s2+min{dp[a][e]+e22se}            =s2+min{dp[a][e]+e2−2se}
经典的斜率dpdp,单调队列维护一下即可

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
const ll INF = 1e18;
using namespace std;

typedef pair<ll, ll> pa;
struct P {
    int from, to, s, t;
    P() {}
    P(int from, int to, int s, int t) : from(from), to(to), s(s), t(t) {}
    bool operator < (P p) const { return (s < p.s || (s == p.s) && (t < p.t)); }
};
int n, m, T, kase = 1;
vector<P> vec;
vector<ll> dis[maxn], arr[maxn];
vector<pa> que[maxn];
int it[maxn], id_que[maxn];

bool solve_k(pa lasi, pa lasj, pa lask) { ///i-j斜率是否比j-k斜率大
    ll dy_ij = lasi.first - lasj.first;
    ll dx_ij = lasi.second - lasj.second;
    ll dy_jk = lasj.first - lask.first;
    ll dx_jk = lasj.second - lask.second;
    return dy_ij * dx_jk > dx_ij * dy_jk;
}

int main() {
    while(scanf("%d %d", &n, &m) != EOF) {
        memset(it, 0, sizeof it);
        memset(id_que, 0, sizeof id_que);
        for(int i = 0; i < maxn; i++) {
            dis[i].clear();
            que[i].clear();
        }
        vec.clear();
        for(int i = 1; i <= m; i++) {
            int a, b, s, t;
            scanf("%d %d %d %d", &a, &b, &s, &t);
            vec.push_back(P(a, b, s, t));
            arr[b].push_back(t);
        }
        arr[1].push_back(0);
        for(int i = 1; i <= n; i++) {
            sort(arr[i].begin(), arr[i].end());
            arr[i].erase(unique(arr[i].begin(), arr[i].end()), arr[i].end());
            for(int j = 0; j < arr[i].size(); j++) dis[i].push_back(INF);
        }
        dis[1][0] = 0;
        sort(vec.begin(), vec.end());
        for(int i = 0; i < vec.size(); i++) {
            int from = vec[i].from, to = vec[i].to;
            ll st = vec[i].s, et = vec[i].t;
            ///处理from -> to, 起飞st, 到达et的边
            int id = lower_bound(arr[to].begin(), arr[to].end(), et) - arr[to].begin();
            ///x = 2 * ei,  y = Aei + ei*ei
            while(it[from] < arr[from].size() && arr[from][it[from]] <= st) {
                if(dis[from][it[from]] == INF) { it[from]++; continue; } ///不可能在st之前到达
                ll as = arr[from][it[from]];
                pa lasi = pa(dis[from][it[from]] + as * as, 2 * as); ///i点
                while(1) {
                    int now_sz = que[from].size();
                    if(now_sz - id_que[from] < 2) break;
                    pa lasj = que[from][now_sz - 1];
                    pa lask = que[from][now_sz - 2];
                    if(solve_k(lasi, lasj, lask)) break;
                    else que[from].pop_back();
                }
                ///维护下凸包
                que[from].push_back(lasi); it[from]++;
            }
            if(que[from].size() < id_que[from] + 1) continue; ///队列中没有点
            ll now_k = st;
            while(id_que[from] + 1 < que[from].size()) {
                pa now = que[from][id_que[from]];
                pa nxt = que[from][id_que[from] + 1];
                if(now.first - now_k * now.second < nxt.first - nxt.second * now_k) break;
                else id_que[from]++; 
            }
            ll fx = que[from][id_que[from]].first - now_k * que[from][id_que[from]].second;///fx = d - st*st;
            dis[to][id] = min(dis[to][id], fx + st * st);
        }
        ll ans = INF;
        for(int i = 0; i < dis[n].size(); i++) ans = min(ans, dis[n][i]);
        cout << ans << endl;
    }
    return 0;
}

J - Grid Coloring

题意:
        给一个nmn∗m的网格, 有蓝色和红色可以涂, 现在有些网格可能已经涂了颜色,要求所有涂蓝色(B)(B)的格子左上角全部是蓝色,涂红色(R)(R)的格子,右下角全部是红色,问一共有多少种涂色方案(1n,m30)(1⩽n,m⩽30)

思路:
        dp[i][j]dp[i][j]:考虑到第ii行时, 前面j行是蓝色,后面全部是红色的方案数,那么dp[i][j]dp[i][j]的状态对i+1i+1中所有的dp[i+1][k](kj)dp[i+1][k](k⩽j)满足条件的都有贡献,处理下不满足条件的就行了。

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 40;
using namespace std;

int n, m, T, kase = 1;
char s[maxn][maxn];
bool can[maxn][maxn];
ll dp[maxn][maxn], ans;

bool check(int x, int y) {
    for(int i = 1; i <= y; i++) if(s[x][i] == 'R') return false;
    for(int i = y + 1; i <= m; i++) if(s[x][i] == 'B') return false;
    return true;
}

int main() {
    while(scanf("%d %d", &n, &m) != EOF) {
        for(int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
        memset(can, false, sizeof can);
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j <= m; j++) {
                can[i][j] = check(i, j);
            }
        }
        for(int j = 0; j <= m; j++) dp[1][j] = can[1][j];
        for(int i = 1; i < n; i++) {
            for(int j = 0; j <= m; j++) {
                for(int k = 0; k <= j; k++) {
                    dp[i + 1][k] += dp[i][j] * can[i + 1][k];
                }
            }
        }
        for(int j = 0; j <= m; j++) ans += dp[n][j];
        cout << ans << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值