欢迎访问本菜鸡的独立博客:Codecho
Summary: 感觉只有 1 4 \frac{1}{4} 41 的题目比较硬核,能学到些东西;剩下的题目比较水,以 DFS
为主。
463D - Gargari and Permutations (建图 + 拓扑序上dp)
1. 题意
给你 k k k 个长度为 n n n 的排列,问它们的最长公共子序列的长度。
数据范围: 1 ≤ n ≤ 1 0 3 ; 2 ≤ k ≤ 5 1 \le n \le 10^3; 2 \le k \le 5 1≤n≤103;2≤k≤5。
2. 解题思路
设排列 i i i 中第 j j j 个数字为 p i , j p_{i,j} pi,j,则对于每个数字,将所有的有向边 < p i , j , p i , k > ( j < k ≤ n ) <p_{i,j}, p_{i,k}>(j < k \le n) <pi,j,pi,k>(j<k≤n) 的边权均加 1 1 1。
之后,我们得到了一个 DAG
,很快想到可以在拓扑序上面进行 dp。
很显然,只有边权为 k k k 的边是有用的,我们根据这个条件先对整个图进行一遍拓扑排序,把拓扑序存储到 t o p o topo topo 数组中。
设 d p [ i ] dp[i] dp[i] 为以 i i i 为起点的 LCS
的长度。
之后,我们枚举 t o p o topo topo 数组,对于 v = t o p o [ i ] v=topo[i] v=topo[i],将其作为临时的终点,之后从 1 1 1 到 n n n 枚举起点 u u u,若 < u , v > <u,v> <u,v> 的边权为 k k k ,表明 d p [ v ] dp[v] dp[v] 可以由 d p [ u ] dp[u] dp[u] 转移过来。
遍历完之后,所有的 d p dp dp 值都已经计算完成,其中的最大值便是答案。
时间复杂度: O ( k ⋅ n 2 ) O(k \cdot n^2) O(k⋅n2)。
3. 代码
int a[10][maxn], head[maxn], n, k;
int G[maxn][maxn], indeg[maxn], topo[maxn], dp[maxn];
queue<int> que;
int cnt = 0;
void toposort()
{
for(int i = 1; i <= n; i++)
{
if(!indeg[i]) que.push(i);
}
while(!que.empty())
{
int now = que.front();
que.pop();
topo[++cnt] = now;
for(int i = 1; i <= n; i++)
{
if(G[now][i] != k) continue;
--indeg[i];
if(!indeg[i]) que.push(i);
}
}
}
int main()
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= k; i++)
{
for(int j = 1; j <= n; j++)
{
scanf("%d", &a[i][j]);
}
}
for(int i = 1; i <= k; i++)
{
for(int j = 1; j <= n; j++)
{
for(int l = j + 1; l <= n; l++)
{
G[a[i][j]][a[i][l]]++;
}
}
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
if(G[i][j] == k) indeg[j]++;
}
}
toposort();
for(int i = 1; i <= cnt; i++)
{
for(int u = 1; u <= n; u++)
{
int v = topo[i];
if(G[u][v] == k)
{
dp[v] = max(dp[v], dp[u] + 1);
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++) ans = max(ans, dp[i]);
printf("%d\n", ans + 1);
return 0;
}
449B - Jzzhu and Cities (Dijkstra 松弛条件改造)
1. 题意
给你一个有 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2≤n≤105) 个节点的无向图, 1 1 1 为其源点。
现在有两种边:
( 1 ) (1) (1) m ( 1 ≤ m ≤ 3 ⋅ 1 0 5 ) m(1 \le m \le 3 \cdot 10^5) m(1≤m≤3⋅105) 条道路,连接城市 u i u_i ui 和 v i v_i vi,长度为 x i x_i xi;
( 2 ) (2) (2) k ( 1 ≤ k ≤ 1 0 5 ) k(1 \le k \le 10^5) k(1≤k≤105) 条铁路,连接城市 1 1 1 和 x i x_i xi,长度为 y i y_i yi。
问最多删掉多少条铁路,使得 1 1 1 号城市到每个城市的最短路径的长度不变。
图保证连通,存在重边,不存在自环。
2. 解题思路
存边的时候,多存一个参数 t y p e type type,表示边的类型, 0 0 0 为道路, 1 1 1 为铁路。
设 f a v fa_v fav 为松弛 v v v 的边的 t y p e type type, d v d_v dv 为 1 1 1 到 v v v 的最短路径的长度。
对于边 ( u , v , w ) (u,v,w) (u,v,w),有两个松弛条件:
( 1 ) (1) (1) 若 d v > d u + w d_v > d_u + w dv>du+w,则按照原来的思路进行松弛,并将 v v v 加入到堆中。此时更新 f a v fa_v fav 为当前边的 t y p e type type;
( 2 ) (2) (2) 若 d v = d u + w d_v = d_u + w dv=du+w,则让类型为 0 0 0 的边替换类型为 1 1 1 的边,更新 f a v fa_v fav 为 0 0 0。
跑完 Dijkstra 之后,统计一下 f a v fa_v fav 为 1 1 1 的点的个数 n u m num num,则答案为 k − n u m k-num k−num。
时间复杂度: O ( ( n + m + k ) log n ) O((n+m+k)\log n) O((n+m+k)logn)。
3. 代码
int n, m, k, cnt;
int head[maxn], fa[maxn];
ll d[maxn];
bool vis[maxn];
struct edge
{
int v, nxt, type;
ll w;
} Edge[8 * maxn];
struct node
{
ll d;
int id;
node(ll _d, int _id):
d(_d), id(_id) {
}
const bool operator < (const node b) const
{
return d > b.d;
}
};
void init()
{
for(int i = 0; i <= n; i++)
{
head[i] = -1;
fa[i] = -1;
}
cnt = 0;
}
void addedge(int u, int v, ll w, int type)
{
Edge[cnt].v = v;
Edge[cnt].w = w;
Edge[cnt].type = type;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
priority_queue<node> que;
void Dijkstra()
{
memset(d, 0x3f, sizeof(d));
d[1] = 0;
que.push(node(d[1], 1));
while(!que.empty())
{
node now = que.top();
que.pop();
if(vis[now.id]) continue;
vis[now.id] = true;
for(int i = head[now.id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
ll w = Edge[i].w;
if(d[v] > d[now.id] + w)
{
d[v] = d[now.id] + w;
fa[v] = Edge[i].type;
que.push(node(d[v], v));
}
else if(d[v] == d[now.id] + w)
{
if(Edge[i].type == 0)
{
que.push(node(d[v], v));
fa[v] = 0;
}
}
}
}
}
int main()
{
int u, v;
ll w;
scanf("%d%d%d", &n, &m, &k);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%lld", &u, &v, &w);
addedge(u, v, w, 0);
addedge(v, u, w, 0);
}
for(int i = 1; i <= k; i++)
{
scanf("%d%lld", &v, &w);
addedge(1, v, w, 1);
addedge(v, 1, w, 1);
}
Dijkstra();
int ans = 0;
for(int i = 2; i <= n; i++) ans += fa[i];
printf("%d\n", k - ans);
return 0;
}
558C - Amr and Chemistry (建图 + 换根法)
1. 题意
给你 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 个数 a i ( 1 ≤ a i ≤ 1 0 5 ) a_i(1 \le a_i \le 10^5) ai(1≤ai≤105),对于每个数,可对其进行若干次如下操作之一:
( 1 ) (1) (1) a i ← 2 ⋅ a i a_i \leftarrow 2 \cdot a_i ai←2⋅ai;
( 2 ) (2) (2) a i ← ⌊ a i 2 ⌋ a_i \leftarrow \lfloor \frac{a_i}{2} \rfloor ai←⌊2ai⌋。
问最少的操作次数,使得所有数字一样。
2. 解题思路
我们可以通过建立一个有向图来解决该问题。
设有向边 < u , v > <u,v> <u,v> 表示数 v v v 可以通过一步操作转化为数 u u u。
按照这种思路,对于数 a a a,可以建立有向边 < ⌊ a 2 ⌋ , a > <\lfloor \frac{a}{2} \rfloor, a> <⌊2a⌋,a> 和 < a , 2 ⋅ a > <a, 2 \cdot a> <a,2⋅a>。
建边的时候不要重复建边或者超范围 ( [ 1 , 1 0 5 ] [1,10^5] [1,105]) 建边 。
因为序列 a a a 中可能出现重复数字,因此需要记录每个数字 i i i 的出现次数 w i w_i wi。
假设 1 1 1 为根,设 d p u dp_u dpu 表示以 u u u 为根的子树上面出现的数字(即在序列 a a a 中出现的数字)的个数。
设 d e p t h i depth_i depthi 为点 i i i 所在的深度,设 d e p t h 1 = 0 depth_1=0 depth1=0。
我们可以通过一遍 DFS 求出所有的 d p u dp_u dpu 和 d e p t h i depth_i depthi。
设 a n s i ans_i ansi 表示将所有的数字最终化为 i i i 所需的最小步数。
则有
a n s 1 = ∑ i = 2 1000 w i ⋅ d e p t h i ans_1=\sum_{i=2}^{1000}{w_i\cdot depth_i} ans1=i=2∑1000wi⋅depthi
基于 a n s 1 ans_1 ans1,我们可以再进行一次 DFS,利用换根法,计算出所有的 a n s i ans_i ansi。
设当前遍历到点 i d id id 和边 < i d , v > <id,v> <id,v>,则有
a n s v = a n s i d − 1 ⋅ d p v + 1 ⋅ ( d p 1 − d p v ) ans_v=ans_{id}- 1 \cdot dp_v+ 1 \cdot (dp_1-dp_v) ansv=ansid−1⋅dpv+1⋅(dp1−dpv)
这个式子的意思是,将 v v v 及其子树中点的深度均减去 1 1 1,将其他点的深度均加上 1 1 1,即得到了 d p v dp_v dpv。
但是这里需要注意的是,奇数 i i i 做整棵树的树根当且仅当 d p i = n dp_i=n dpi=n,其他奇数都不可以做整棵树的树根。
因为若 d p i < n dp_i<n dpi<n,表明有不在 i i i 子树中的数字,因而涉及到从小到大的转换。
而从小到大的转换无法将一个数转化为奇数。
因此,需要对这一情况进行特判。
最终的答案为
min 1 ≤ i ≤ 1 0 5 { a n s i } \min_{1 \le i \le 10^5}\left\{ans_i\right\} 1≤i≤105min{
ansi}
时间复杂度: O ( 1 0 5 log 1 0 5 ) O(10^5 \log 10^5) O(105log105)
3. 代码
int a[maxn], head[maxn], n, cnt;
ll dp[maxn], w[maxn], depth[maxn];
ll ans[maxn];
struct edge
{
int v, nxt;
} Edge[4 * maxn];
void init()
{
for(int i = 0; i <= 100000; i++) head[i] = -1;
cnt = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs(int id)
{
dp[id] = w[id];
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
depth[v] = depth[id] + 1;
dfs(v);
dp[id] += dp[v];
}
}
void dfs2(int id)
{
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v % 2 == 1 && dp[v] != n) ans[v] = 0x3f3f3f3f;
else ans[v] = ans[id] - dp[v] + (dp[1] - dp[v]);
dfs2(v);
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
w[a[i]]++;
}
init();
for(int i = 1; i <= 100000; i++)
{
if(i * 2 <= 100000)
{
addedge(i, i * 2);
}
if(i / 2 >= 1)
{
if(i % 2 == 1) addedge(i / 2, i);
}
}
dfs(1);
for(int i = 2; i <= 100000; i++) ans[1] += w[i] * depth[i];
dfs2(1);
ll res = 0x3f3f3f3f3f3f3f3f;
for(int i = 1; i <= 100000; i++)
{
res = min(res, ans[i]);
}
printf("%lld\n", res);
return 0;
}
739B - Alyona and a tree (树上差分)
1. 题意
给你一棵 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \le n \le 2 \cdot 10^5) n(1≤n≤2⋅10