A. 油箱
【问题描述】
有 (n) 个城市,每个城市都有加油站,有 (m) 条单向道路,距离为 (x) 的道路需要消耗 (x) 升的汽油。请问你的车辆可以携带的最小油箱容量,使得不限加油次数的情况下,无论你在哪个城市都可以到达任意的城市。
【输入格式】
第一行两个正整数 (n),(m)。
接下来 (m) 行,每行三个正整数 (x,y,z) 表示一条 (x) 到 (y) 的有向边,边权为 (z)。
【输出格式】
输出符合条件的最小油箱容量,如果无法满足输出 (−1)
【样例输入】
3 5
1 2 1
1 3 2
2 3 5
3 1 4
2 3 1
【样例输出】
4
【样例解释】
油箱容量为 (4) 时, (1→2,1→3,3→1,2→3) 道路可以通行,此时满足无论你在哪个城市都可以到达任意的城市。
【数据规模与约定】
(20%) 的数据 (n≤5,m≤20)
(40%) 的数据 (n≤50,m≤200)
(60%) 的数据 (n≤5×10^2,m≤2×10^3)
100%100% 的数据 (n≤5×10^4,m≤2×10^9) 保证 (1≤z≤10^9)
这道题我用了一个很神奇的操作完成了。
首先,这道题我们使用二分。判断有无解。连接所有边权小于等于(mid)的边,判断所有点是否互相可达。
实际上如果所有点都能到达(1),并且(1)能到达所有点,则说明所有点互相可达。
本人的做法如下:
判断所有点是否在同一连通分量。如果是,则进行下一步;
统计所有点的入度和出度,如果所有点的入度和出度都不为0,该联通块具可达性。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
using namespace std;
const int SIZE = 50000 + 10, INF = 1 << 30;
struct Edge
{
int u, v, w;
bool operator < (const Edge& lhs)
{
return w > lhs.w;
}
} e[SIZE << 2];
vector <int> G[SIZE];
bool judge[SIZE] = {};
int n, m, deg[SIZE] = {}, out[SIZE] = {}, fa[SIZE];
int get(int x)
{
if(fa[x] == x) return x;
return fa[x] = get(fa[x]);
}
void dfs(int u)
{
judge[u] = true;
for(int i = 0; i < G[u].size(); ++ i)
{
int v = G[u][i];
if(!judge[v]) dfs(v);
}
return;
}
bool valid(int x)
{
memset(out, 0, sizeof(out));
memset(deg, 0, sizeof(deg));
for(int i = 1; i <= n; ++ i) fa[i] = i;
for(int i = 0; i < m; ++ i)
{
if(e[i].w <= x && e[i].u != e[i].v)
{
++ deg[e[i].v];
++ out[e[i].u];
fa[get(e[i].v)] = get(e[i].u);
}
}
for(int i = 1; i <= n; ++ i) if(!deg[i] || !out[i]) return false;
get(1);
for(int i = 2; i <= n; ++ i)
{
if(get(i) != fa[1]) return false;
}
return true;
}
int main()
{
scanf("%d %d", &n, &m);
for(int i = 0; i < m; ++ i)
{
scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);
int u = e[i].u, v = e[i].v, w = e[i].w;
if(u != v)
{
G[u].push_back(v);
++ out[u];
++ deg[v];
}
}
dfs(1);
for(int i = 1; i <= n; ++ i)
if(!judge[i])
{
puts("-1");
return 0;
}
sort(e, e + m);
if(n == 1) puts("0");
else
{
for(int i = 1; i <= n; ++ i)
if(!deg[i] || !out[i])
{
puts("-1");
return 0;
}
int L = INF, R = 0, mid;
for(int i = 0; i < m; ++ i) L = min(L, e[i].w), R = max(R, e[i].w);
while(L < R)
{
mid = L + ((R - L) >> 1);
if(valid(mid)) R = mid;
else L = mid + 1;
}
printf("%d
", R);
}
return 0;
}
B. 求和
【问题描述】
给定一颗(n)个点组成的树,根节点为(1)号节点,每个点上有两个权值(a_i, b_i)。共有(m)次询问,每次询问给出两个正整数(x, y),从(x)到(y)的路径设为(t_1,t_2,…,t_k) 要求输出
【输入格式】
第一行两个正整数(n,m)
第二行(n-1)个数 (f_2,f_3,…,f_n),表示点i的父亲((f_i< i))
接下来两行,每行(n)个数 分别表示(a_i,b_i)
接下来(m)行,每行两个正整数(x_i,y_i) ,表示第(i)次询问
【输出格式】
对于每个询问操作,输出答案。
【样例输入】
5 4
1 2 3 4
1 2 3 4 5
1 2 3 4 5
1 2
1 3
1 4
1 5
【样例输出】
2
11
35
85
【数据规模与约定】
20% n,m<=100
40% n,m<=2000
另20% 保证 ai=bi
另20% 保证fi=i-1, x<=y
100% n,m<=100000 保证1<=ai,bi<=10000
这道题我使用树上倍增。
当然,我们首先先考虑维护(suma[u])和(sumb[u])代表从该点到根节点的所有权值和。发现我们还可以维护(prob_a[u])和(prob_b[u])用以计算从该点(u)到根节点的所有(a_i*b_j)。然后分别维护。
对于(u,v),答案即为:
这道题启发我可以利用维护序列上的操作维护树上较难处理的操作。
最后,别忘开long long!
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
using namespace std;
const int SIZE = 100000 + 5;
vector <int> G[SIZE];
int n, m, t, a[SIZE], b[SIZE], dep[SIZE], F[SIZE][40];
long long sa[SIZE], sb[SIZE], prob_a[SIZE], prob_b[SIZE];
void prework()
{
memset(sa, 0, sizeof(sa));
memset(sb, 0, sizeof(sb));
memset(prob_a, 0, sizeof(prob_a));
memset(prob_b, 0, sizeof(prob_b));
memset(dep, 0, sizeof(dep));
memset(F, 0, sizeof(F));
return;
}
void dfs(int u, int Fa)
{
sa[u] = a[u] + sa[Fa], sb[u] = b[u] + sb[Fa];
prob_a[u] = a[u] * sb[Fa] + prob_a[Fa], prob_b[u] = b[u] * sa[Fa] + prob_b[Fa];
dep[u] = dep[Fa] + 1;
F[u][0] = Fa;
for(int i = 1; i <= t; ++ i) F[u][i] = F[F[u][i - 1]][i - 1];
int v;
for(int i = 0; i < G[u].size(); ++ i)
{
v = G[u][i];
if(v != Fa) dfs(v, u);
}
return;
}
int LCA(int x, int y)
{
if(dep[x] > dep[y]) swap(x, y);
for(int i = t; i >= 0; -- i)
if(dep[F[y][i]] >= dep[x]) y = F[y][i];
if(x == y) return x;
for(int i = t; i >= 0; -- i)
{
if(F[x][i] != F[y][i])
{
x = F[x][i];
y = F[y][i];
}
}
return F[x][0];
}
long long query(int x, int y)
{
int lca = LCA(x, y), fa = F[lca][0];
return prob_a[x] + prob_b[y] - (sa[x] - sa[fa]) * sb[fa] - (sb[y] - sb[fa]) * sa[fa] - prob_a[fa] - prob_b[fa] + (sb[y] - sb[lca]) * (sa[x] - sa[lca]);
}
int main()
{
scanf("%d %d", &n, &m);
t = log(n) / log(2);
for(int i = 1; i <= n; ++ i) G[i].clear();
for(int u = 2; u <= n; ++ u)
{
int v;
scanf("%d", &v);
G[u].push_back(v), G[v].push_back(u);
}
for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
for(int i = 1; i <= n; ++ i) scanf("%d", &b[i]);
prework();
dfs(1, 0);
int x, y;
while(m --)
{
scanf("%d %d", &x, &y);
printf("%lld
", query(x, y));
}
return 0;
}
C. 染色
【问题描述】
一排有n个格子,染成m种颜色,相邻格子颜色不能相同。此外,允许一个长度不超过k的区间染成相同的颜色。求最小代价。
【输入格式】
第一行包含三个正整数n, m, k表示格子数量、颜色数量、区间长度。
接下来的n行,每行有m个正整数cost i,j 表示将第i个格子染成颜色j需要付出的代价。
由于输入数据过大,可能需要使用快速读入
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0'|ch>'9')ch= getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch= getchar ();
return x;
}
【输出格式】
输出一个整数,表示合法方案的最小代价。
【样例输入】
5 3 3
1 5 6
1 2 3
9 1 9
9 1 9
1 2 3
【样例输出】
6
【样例说明】
颜色分别为1 2 2 2 1
【数据规模与约定】
15% n, m<=10 k<=n
40% n, m<=100 k<=10
60% n, m<=300 k<=n
另15% n, m<=2000 k=1
100% n, m<=2000 k<=n cost<=10000
题意中有一句话:一个长度不超过k的区间染成相同的颜色,指的是一个(考场期间我就以为是多个)。
首先(k=1),互不相同。DP带走;
其次,我们可以枚举每一个长度不超过(k)的子串,分别计算。
接着刚才的想法,我们可以不妨优化一下:预处理DP,接着单调队列。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
const int N = 2000 + 5, M = 2000 + 5, INF = 1 << 30;
int n, m, k, ans = 0, cost[N][M], s[N], dpL[N][M], dpR[N][M], dp[M];
deque <int> Q;
inline void read(int &x)
{
bool mark = false;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
if(mark) x = -x;
return;
}
void prework()
{
memset(dpL, 0x3f, sizeof(dpL));
memset(dpR, 0x3f, sizeof(dpR));
for(int i = 1; i <= m; ++ i) dpL[0][i] = dpR[n + 1][i] = 0;
int val;
for(int i = 1; i <= n; ++ i)
{
val = INF;
for(int j = 1; j <= m; ++ j) dp[j] = dpL[i - 1][j] + cost[i][j];
for(int j = 1; j < m; ++ j)
{
val = min(val, dp[j]);
dpL[i][j + 1] = min(dpL[i][j + 1], val);
}
val = INF;
for(int j = m; j > 1; -- j)
{
val = min(val, dp[j]);
dpL[i][j - 1] = min(dpL[i][j - 1], val);
}
}
for(int i = n; i > 0; -- i)
{
val = INF;
for(int j = 1; j <= m; ++ j) dp[j] = dpR[i + 1][j] + cost[i][j];
for(int j = 1; j < m; ++ j)
{
val = min(val, dp[j]);
dpR[i][j + 1] = min(dpR[i][j + 1], val);
}
val = INF;
for(int j = m; j > 1; -- j)
{
val = min(val, dp[j]);
dpR[i][j - 1] = min(dpR[i][j - 1], val);
}
}
return;
}
int main()
{
read(n), read(m), read(k);
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= m; ++ j) read(cost[i][j]);
prework();
ans = INF;
for(int i = 1; i <= m; ++ i)
{
memset(s, 0, sizeof(s));
for(int j = 1; j <= n; ++ j) s[j] = s[j - 1] + cost[j][i];
Q.clear();
for(int j = 0; j <= n; ++ j)
{
while(Q.size() && j - Q.front() > k) Q.pop_front();
if(Q.size()) ans = min(ans, dpR[j + 1][i] + s[j] - s[Q.front()] + dpL[Q.front()][i]);
while(Q.size() && dpL[Q.back()][i] - s[Q.back()] >= dpL[j][i] - s[j]) Q.pop_back();
Q.push_back(j);
}
}
printf("%d
", ans);
return 0;
}
D. 数字
【问题描述】
给出n个好数和m个坏数,求包含所有好数但不包含任何坏数,并且只由1-4组成的数中,各位数字之和最小值是多少。(好数和坏数均为k位数,且由1-4组成)
【输入格式】
第一行包含三个正整数n, m, k
接下来n行包含每行1个正整数,表示好数
接下来m行包含每行1个正整数,表示坏数
【输出格式】
输出一个正整数,表示最小的值。输入数据保证一定存在解。
【样例输入】
3 3 4
1111
1222
2333
1122
1133
3122
【样例输出】
20
【样例说明】
12223331111
【数据规模与约定】
20% n<=5 m=0 k=2
30% n<=10 m<=10 k<=5
40% n<=17 m<=20 k<=5
50% n<=18 m<=20 k<=5
60% n<=19 m<=20 k<=5
70% n<=20 m<=20 k<=5
100% n<=20 m<=10000 k<=8
题目好评,思维难题。
一看数据规模,再看好数的定义,第一想法就是旅行商问题,不过状压只是一个外套罢了啊。
转移过程中花费貌似挺难想的。我们把每一个(k)位数看作一个节点,花费即为节点距离。跑最短路。
举个例子:(1234)可以向(2341)、(2342)、(2343)、(2344)(这四个数如果都不是不满足题意的数据)连边。
这些点压缩为一个数,四进制。注意常数因子给程序的影响!
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<queue>
#define pii pair <int, int>
using namespace std;
const int N = 20 + 5, M = 10000 + 5, SIZE = 1 << 20, INF = 1 << 30;
priority_queue <pii> Q;
string Good[N], Bad[M];
bool vis[SIZE], book[SIZE];
int n, m, k, st[N], dis[SIZE], d[N][N], dp[N][SIZE];
void Dijkstra(int S)
{
while(Q.size()) Q.pop();
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[S] = 0;
Q.push(make_pair(0, S));
while(Q.size())
{
int u = Q.top().second;
Q.pop();
if(vis[u]) continue;
vis[u] = true;
for(int op = 0; op < 4; ++ op)
{
int v = ((u % (1 << (2 * k - 2))) << 2) + op, w = op + 1;
if(book[v]) continue;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
Q.push(make_pair(-dis[v], v));
}
}
}
return;
}
void prework()
{
memset(st, 0, sizeof(st)), memset(book, false, sizeof(book));
for(int i = 1; i <= n; ++ i)
{
for(int j = 0; j < k; ++ j)
{
int tmp = Good[i][j] - '0';
st[i] = st[i] * 4 + tmp - 1;
}
}
int x;
for(int i = 1; i <= m; ++ i)
{
x = 0;
for(int j = 0; j < k; ++ j)
{
int tmp = Bad[i][j] - '0';
x = x * 4 + tmp - 1;
}
book[x] = true;
}
memset(d, 0x3f, sizeof(d));
for(int i = 1; i <= n; ++ i)
{
Dijkstra(st[i]);
for(int j = 1; j <= n; ++ j) d[i][j] = dis[st[j]];
}
return;
}
int compute(int x)
{
int res = 0;
for(int i = 0; i < k; ++ i) res += Good[x][i] - '0';
return res;
}
int main()
{
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= n; ++ i) cin >> Good[i];
for(int i = 1; i <= m; ++ i) cin >> Bad[i];
prework();
memset(dp, 0x3f, sizeof(dp));
for(int i = 0; i < n; ++ i) dp[i + 1][1 << i] = compute(i + 1);
for(int i = 1; i < 1 << n; ++ i)
{
for(int j = 1; j <= n; ++ j)
{
if(i & (1 << j - 1))
{
int pre_mask = i ^ (1 << j - 1);
for(int p = 1; p <= n; ++ p)
{
if(pre_mask & (1 << p - 1))
{
dp[j][i] = min(dp[j][i], dp[p][pre_mask] + d[p][j]);
}
}
}
}
}
int ans = INF;
for(int i = 1; i <= n; ++ i) ans = min(ans, dp[i][(1 << n) - 1]);
printf("%d
", ans);
return 0;
}