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终于也开始做作业了
我要复活!我要复活!我要复活!