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

B - Enlarging Enthusiasm

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

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

#include
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 = a b ∑ x | n x 1ab1012 1 ⩽ a ⩽ b ⩽ 10 12 ba106 b − a ⩽ 10 6

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

#include
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

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

#include
const int maxn = 1e5 + 10;
using namespace std;

struct Edge {
    int v, col, vis;
    bool operator < (Edge e) const { return col < e.col; }
};
vector 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

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

#include
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 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

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

#include
typedef long long ll;
const int maxn = 2e5 + 10;
const ll INF = 1e18;
using namespace std;

typedef pair 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

vec; vector dis[maxn], arr[maxn]; vector 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

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

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

#include
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;
}

你可能感兴趣的:(其他)