【NOIP模拟】20151015模拟

3 篇文章 0 订阅
2 篇文章 0 订阅

T1 找位置

Problem

问题描述

FJ 想找一个最好的位置来建新农场,这样他每天可以少走些路。FJ 所在的区域,有 N个城镇(1 <= N <= 10,000),城镇之间,有 M(1 <= M <= 50,000)条双向路相连,所有城镇都可以借助一些路相互连接。
FJ 需要你的帮助来选择最合适建新农场的城镇。K(1 <= K <= 5)个城镇中有超市,FJ 每天都会去这些超市。他计划每天从新农场出发,访问包含超市的 K 个城镇,然后返回新农场。FJ 可以按照任意的顺序访问这些超市。FJ 只会在 N-K 个城镇中,选择一个城镇来建新农场。因为其他城镇的房价,比较低一些。如果他把农场建在最优的位置,而且尽可能明智的选择行走路线。
请帮 FJ 计算,他每天需要行走的路线长度。

输入

第 1 行:三个空格隔开的整数,N,M 和 K。
第 2..1+K 行:第 i+1 行包含一个整数,范围 1..N,表示包含第 i 个超市的城镇。每个超市在不同城镇。
第 2+K..1+K+M 行:每行包含三个空格隔开的整数,i,j(1 <= i,j <= N),和 L(1 <= L <=1000), 表示城镇 i 和城镇 j 之间存在一条长度为 L 的路。

输出

输出一行一个数,表示他把农场建在最优的位置,每天需要行走的最短路线长度。

输入输出样例

relocation.in
5 6 3
1
2
3
1 2 1
1 5 2
3 2 3
3 4 5
4 2 7
4 5 10
relocation.out
12

Solution

做K遍单源最短路,把K个超市到每个点的最短路算出来
再用K!的时间预处理dp[i][j],表示先去第i个超市,最后去第j个超市的最短时间
最后枚举所有的非超市点,即新建位置,并枚举先去的超市和最后去的超市,统计答案

Code

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long

inline int read() {
    int x = 0;
    char c = getchar(); 
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x;
}

const int N = 15000, M = 55000;
const int inf = 1000000000;
struct edge{
    int from, to, len, nxt;
}e[M * 2];
int head[N], sup[N], used[N], inq[N];
int dist[10][N];
int dp[10][10];
int n, m, K, tail = 0, ans;

void addedge(int x, int y, int z) {
    e[++tail].from = x;
    e[tail].to = y;
    e[tail].len = z;
    e[tail].nxt = head[x];
    head[x] = tail;
}

void spfa(int s, int k) {
    rep(i, 1, n) dist[k][i] = inf;
    memset(inq, 0, sizeof(inq));
    queue<int> q;
    q.push(s); inq[s] = 1; dist[k][s] = 0;
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = 0;
        for(int i = head[u]; i != -1; i = e[i].nxt) {
            int v = e[i].to;
            if (dist[k][u] + e[i].len < dist[k][v]) {
                dist[k][v] = dist[k][u] + e[i].len;
                if (!inq[v]) {
                    inq[v] = 1;
                    q.push(v);
                }
            }
        }
    }
}

void work(int dep, int now, int start, int len) {
    if (dep >= K) {
        if (len < dp[start][now]) dp[start][now] = len;
        return;
    }
    used[now] = 1;
    rep(i, 1, K) {
        if (used[i]) continue;
        work(dep + 1, i, start, len + dist[now][sup[i]]);
    }
    used[now] = 0;
}

int main() {
    freopen("relocation.in", "r", stdin);
    freopen("relocation.out", "w", stdout);

    n = read(); m = read(); K = read();
    rep(i, 1, K) sup[i] = read();
    rep(i, 1, n) head[i] = -1;
    rep(i, 1, m) {
        int x = read(), y = read(), z = read();
        addedge(x, y, z);
        addedge(y, x, z);
    }
    rep(i, 1, K) spfa(sup[i], i);
    rep(i, 1, K) rep(j, 1, K) dp[i][j] = inf;
    rep(i, 1, K) {
        memset(used, 0, sizeof(used));
        work(1, i, i, 0);
    }
    memset(used, 0, sizeof(used));
    rep(i, 1, K) used[sup[i]] = 1;
    ans = inf;
    rep(i, 1, n) {
        if (used[i]) continue;
        rep(j, 1, K) {
            rep(k, 1 ,K) {
                if (k == j) continue;
                if (ans > dist[j][i] + dp[j][k] + dist[k][i])
                    ans = dist[j][i] + dp[j][k] + dist[k][i];
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

T2 招募

Problem

问题描述

小 y 的老爸是个军人,最近在招募新兵。
现在需要招募女兵 n 人、男兵 m 人。每招募一个人需要花费 10000 元。但是,如果已经招募的人中有一些关系亲密的人,那么就可以少花一些钱。假设,他已经弄清楚 r 对男女之间的亲密关系指数(1~9999 之间的整数),招募某个人的费用为:10000 -(已经招募的人中和这个人的亲密关系指数的最大值)。现在,他想知道,通过适当的招募顺序使得招募所有人所需要的最小费用是多少。
当然,这个任务手工完成起来太困难了。于是,小 y 的老爸就请学习编程的小 y 来帮他完成。

输入

输入数据的第 1 行为 3 个整数,依次为 m,n 和 r。
以下 r 行,每行 3 个整数 x、y 和 d,表示第 x 号男兵与第 y 号女兵的亲密关系指数为 d。

输出

输出一行一个数,表示需要的最小招募费用。

输入输出样例

conscription.in
5 5 8
4 3 6831
1 3 4583
0 0 6592
0 1 3063
3 3 4975
1 3 2049
4 2 2104
2 2 781
conscription.out
71071

数据规模

对于 20%数据满足:m,n <= 20
对于 100%数据满足:1 <= n,m <= 10000,0 <= r <= 50000
0 <= d <= 10000

Solution

图的每个生成树都可以对应一种选择方案。
要使最终花费最少,则需要使总亲密度最高,也就是求一棵最大生成树。
这个图可能是不连通的,未必面计数连通块个数,可以这么做——把每个点都像一个虚节点连一条长度为0的无向边,对(点数+1)的图做最大生成树。复杂度会变劣,但对这道题仍然足够。

Code

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long

inline int read() {
    int x = 0;
    char c = getchar(); 
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x;
}

const int M = 100000, N = 100000;
struct edge {
    int from, to, len, nxt;
}e[M];
int n, m, r, num, ans, tail = 0;
int f[N], head[N];

bool cmp(edge a, edge b) { return a.len > b.len; }

int find(int x) { return x == f[x] ? f[x] : f[x] = find(f[x]); }

void addedge(int x, int y, int z) {
    e[++tail].from = x;
    e[tail].to = y;
    e[tail].len = z;
    e[tail].nxt = head[x];
    head[x] = tail;
}

int main() {
    freopen("conscription.in", "r", stdin);
    freopen("conscription.out", "w", stdout);

    m = read(); n = read(); r = read();
    rep(i, 1, n + m + 10) head[i] = -1;
    rep(i, 1, r) {
        int x, y, d;
        x = read(); y = read(); d = read();
        x++; y++;
        addedge(x, y + m, d);
    }
    rep(i, 1, n + m) addedge(i, n + m + 1, 0);
    r += n + m;
    n = n + m + 1;
    sort(e + 1, e + r + 1, cmp);
    rep(i, 1, n) f[i] = i;
    num = 0; ans = 0;
    rep(i, 1, r) {
        int fx = find(e[i].from);
        int fy = find(e[i].to);
        if (fx == fy) continue;
        f[fx] = fy;
        ans += e[i].len;
        num++;
        if (num == n - 1) break;
    }
    printf("%d\n", 10000 * (n - 1) - ans);
    return 0;
}

T3 公共子串

Problem

问题描述

问在 0~m-1 中有多少数与 Z 有至少一个长度等于 r 的起始位置相同的公共子串,补充前导零至 m-1 的位数。

输入

第一行一个整数 t 代表数据组数(小于等于 5000)。
下面 t 行,每行三个整数 m,Z,r。

输出

对于每组输入数据,输出一行,代表个数。

输入输出样例

ticket.in
8
89 32 1
67 49 1
67 45 2
1000 23 1
1000 401 2
1000 54 2
3571 2 3
3571 976 3
ticket.out
18
15
1
271
19
19
13
12

数据规模

对于 30%的数据满足:m <= 100
对于 100%的数据满足:1 <= m <= 2^63-1,Z <= m,r <= 18

Solution

一道数位dp题
记dp[less][pos][match]表示,从高到低做到第pos位,之前已经匹配了match位,是(less==0)否(less==1)卡住上界的方案数
记忆个lim数组表示m-1的每一位数
若less==0,枚举每一位从0到lim[pos]
若less==1,枚举每一位从0到9
具体分类讨论见代码
当已经匹配满r位时,直接对答案计数,这是为了防止这样的情况被重复记数
这里写图片描述

Code

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long

int T;
ll xp[30], lim[30];
ll f[2][30][30];
int limit[30], pp[30];
ll m, z, r, ans;

ll dfs(int less, int pos, int match) {
    if (pos == 0) return 0;
    if (~f[less][pos][match]) return f[less][pos][match];
    f[less][pos][match] = 0;
    if (!less) {
        rep(k, 0, limit[pos]) {
            if (k == pp[pos]) {
                if (match + 1 == r) {
                    ll num;
                    if (k == limit[pos]) f[less][pos][match] += lim[pos - 1] + 1;
                    else f[less][pos][match] += xp[pos - 1];
                }else f[less][pos][match] += dfs(k < limit[pos], pos - 1, match + 1);
            }else f[less][pos][match] += dfs(k < limit[pos], pos - 1, 0);
        }
    }else{
        rep(k, 0, 9) {
            if (k == pp[pos]) {
                if (match + 1 == r) f[less][pos][match] += xp[pos - 1];
                else f[less][pos][match] += dfs(1, pos - 1, match + 1);
            }else f[less][pos][match] += dfs(1, pos - 1, 0);
        }
    }
    return f[less][pos][match];
}

int main() {
    freopen("ticket.in", "r", stdin);
    freopen("ticket.out", "w", stdout);

    scanf("%d", &T);
    xp[0] = 1ll;
    rep(i, 1, 18) xp[i] = xp[i - 1] * 10ll;
    while(T--) {
        memset(limit, 0, sizeof(limit));
        memset(lim, 0, sizeof(lim));
        memset(pp, 0, sizeof(pp));
        scanf("%I64d%I64d%I64d", &m, &z, &r);
        ll x = m - 1, base = 1;
        int end = 0, end2 = 0;
        while(x) {
            limit[++end] = x % 10;
            x /= 10;
        }
        lim[0] = 0;
        rep(i, 1, end) {
            lim[i] = lim[i - 1] + limit[i] * base;
            base *= 10ll;
        }
        x = z;
        while(x) {
            pp[++end2] = x % 10;
            x /= 10;
        }
        rep(i, end2 + 1, end) pp[i] = 0;
        memset(f, 0xff, sizeof(f));
        ans = dfs(0, end, 0);
        printf("%I64d\n", ans);
    }
    return 0;
}

尾声

终于AK了!终于AK了!终于AK了!
重要的事说三遍
感谢HBH最后一题的假fc和GJ同学的不可抗爆炸
我就Rank1了
小朋友炸的挺惨的
这是一次成功的数位dp
但这个夜晚并没有迎来一次成功的物理作业
PTJ终于走出了失恋的阴影,喜欢上了。。我的抱枕
HBH终于也开始做作业了
我要复活!我要复活!我要复活!

End.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值