Summary: 感觉只有 1 4 \frac{1}{4} 41 的题目比较硬核,能学到些东西;剩下的题目比较水,以 DFS
为主。
给你 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。
设排列 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)。
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;
}
给你一个有 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 号城市到每个城市的最短路径的长度不变。
图保证连通,存在重边,不存在自环。
存边的时候,多存一个参数 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)。
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;
}
给你 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⌋。
问最少的操作次数,使得所有数字一样。
我们可以通过建立一个有向图来解决该问题。
设有向边 < 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)
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;
}
给你一棵 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \le n \le 2 \cdot 10^5) n(1≤n≤2⋅105) 个节点的无向树,根节点为 1 1 1。
对于每个点 i i i,都有一个权值 a i ( 1 ≤ a i ≤ 1 0 9 ) a_i(1 \le a_i \le 10^9) ai(1≤ai≤109)。
设 d i s t ( u , v ) dist(u,v) dist(u,v) 表示 u u u 到 v v v 路径上面点的权值和。
点 u u u 控制点 v ( u ≠ v ) v(u \ne v) v(u̸=v) 当且仅当 v ∈ s u b t r e e ( u ) v \in subtree(u) v∈subtree(u) 并且 d i s t ( u , v ) ≤ a v dist(u,v) \le a_v dist(u,v)≤av。
问每个点可以控制多少个点。
我们能够发现,在 DFS 的过程中,我们既可以认为在处理点 i d id id,也可以认为在处理点 i d id id 所在的链。
如果正向思考的话,我们需要处理每个点能够控制的点。
但是,对于本题,我们可以反向思考:对于每个点,处理能够控制它的所有节点。
设 u u u 能够控制 v v v,则显然 u u u 和 v v v 在一条链上,有
d i s t ( u , v ) = d i s t ( 1 , v ) − d i s t ( 1 , u ) dist(u,v) = dist(1,v) - dist(1,u) dist(u,v)=dist(1,v)−dist(1,u)
d i s t ( u , v ) ≤ a v ⇔ d i s t ( 1 , v ) − d i s t ( 1 , u ) ≤ a v ⇔ d i s t ( 1 , v ) − a v ≤ d i s t ( 1 , u ) \begin{aligned} &dist(u,v) \le a_v \\ \Leftrightarrow\text{ }& dist(1,v)-dist(1,u) \le a_v \\ \Leftrightarrow\text{ }& dist(1,v)-a_v \le dist(1,u) \end{aligned} ⇔ ⇔ dist(u,v)≤avdist(1,v)−dist(1,u)≤avdist(1,v)−av≤dist(1,u)
如果一个一个处理的话,时间复杂度显然过高,因此我们需要采用差分的思想。
设 d i f f i diff_i diffi 表示点 i i i 对应的差分值。
很显然,既然要进行差分,我们就要进行两遍 DFS:
第一遍 DFS 用于求出所有的 d i f f i diff_i diffi,第二遍 DFS 用于求 d i f f i diff_i diffi 的前缀和,即每个点对应的答案。
第二遍 DFS 很容易进行,这里不对此进行叙述,直接看第一遍 DFS。
设当前遍历到的节点为 i d id id,当前深度为 d e p t h depth depth,指向当前点的边权为 l e n len len。
设 f a t h e r i father_i fatheri 表示点 i i i 的父节点, i d d d idd_d iddd 表示当前链上深度为 d d d 的节点编号, p r e f i x d e p t h prefix_{depth} prefixdepth 表示当前链上截止到 d e p t h depth depth 深度的点的权值和.
根据上面的不等关系,我们可以利用 lower_bound
找到第一个大于等于 p r e f i x d e p t h − a i d prefix_{depth}-a_{id} prefixdepth−aid 的 p r e f i x d prefix_d prefixd,则 d d d 为当前链上能够控制点 i d id id 的最远的点的深度。
因此整个链上,深度 d d d 所对应的点到点 i d id id 所经过的所有点都可以控制点 i d id id,根据差分的思想,我们进行下列操作:
diff[id]++;
diff[father[idd[pos]]]--;
因此,第一遍 DFS 结束之后,所有的 d i f f i diff_i diffi 便都被计算出来。
对于每个点 i i i,最终的答案为 d i f f i − 1 diff_i-1 diffi−1(因为要把自身排除掉)。
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。
int n, cnt, head[maxn];
ll a[maxn], prefix[maxn], diff[maxn];
int father[maxn], idd[maxn];
struct edge
{
int v, nxt;
ll w;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void addedge(int u, int v, ll w)
{
Edge[cnt].v = v;
Edge[cnt].w = w;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs(int id, int fa, int depth, ll len)
{
father[id] = fa;
idd[depth] = id;
prefix[depth] = prefix[depth - 1] + len;
int pos = lower_bound(prefix + 1, prefix + depth + 1, prefix[depth] - a[id]) - prefix;
diff[id]++;
diff[father[idd[pos]]]--;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == fa) continue;
dfs(v, id, depth + 1, Edge[i].w);
}
}
void dfs2(int id, int fa)
{
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == fa) continue;
dfs2(v, id);
diff[id] += diff[v];
}
}
int main()
{
int u, v;
ll w;
scanf("%d", &n);
init();
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int v = 2; v <= n; v++)
{
scanf("%d%lld", &u, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dfs(1, 0, 1, 0);
dfs2(1, 0);
for(int i = 1; i <= n; i++)
{
if(i > 1) printf(" ");
printf("%lld", diff[i] - 1);
}
return 0;
}
给你一棵 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \le n \le 2 \cdot 10^5) n(1≤n≤2⋅105) 个节点,根节点为 1 1 1 的无向树。每个点 i i i 都有一个权值 a i ( 1 ≤ a i ≤ 2 ⋅ 1 0 5 ) a_i(1 \le a_i \le 2 \cdot 10^5) ai(1≤ai≤2⋅105)。
设 b e a u t y ( x ) beauty(x) beauty(x) 为从根节点到 x x x 的路径上所有点的最大公约数,且 b e a u t y ( 1 ) = a 1 beauty(1) = a_1 beauty(1)=a1。
现在你可以选择一个点,将其权值置为 0 0 0。
对于每个点 x x x,问其 b e a u t y ( x ) beauty(x) beauty(x) 可能的最大值。
对于每个点,须独立进行考虑。
对于两个正整数 a a a 和 b b b,满足
gcd ( a , b ) ∣ a gcd ( a , b ) ∣ b \text{gcd}(a,b) | a \\ \text{gcd}(a,b) | b gcd(a,b)∣agcd(a,b)∣b
而每个数的因子个数为 log \log log 级别的。
而在 DFS 的时候,我们只需考虑当前节点所在的链。
因此,我们可以利用 set
存储当前链上所有可能出现的 gcd \text{gcd} gcd,对整棵树进行一遍 DFS 即可。
时间复杂度: O ( n ⋅ log 2 n ) O(n \cdot \log^2 n) O(n⋅log2n)。
int a[maxn], head[maxn], n, cnt;
set<int> se[maxn];
int ans[maxn];
struct edge
{
int v, nxt;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
void dfs(int id, int fa, int GCD)
{
set<int> :: iterator iter;
iter = se[id].end();
--iter;
ans[id] = max((*iter), GCD);
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == fa) continue;
for(iter = se[id].begin(); iter != se[id].end(); ++iter)
{
int val = *iter;
se[v].insert(gcd(val, a[v]));
}
se[v].insert(gcd(GCD, a[v]));
dfs(v, id, gcd(GCD, a[id]));
}
}
int main()
{
int u, v;
scanf("%d", &n);
init();
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
se[1].insert(0);
se[1].insert(a[1]);
dfs(1, -1, 0);
for(int i = 1; i <= n; i++)
{
if(i > 1) printf(" ");
printf("%d", ans[i]);
}
return 0;
}
给你一棵有 n ( 3 ≤ n ≤ 1 0 6 ) n(3 \le n \le 10^6) n(3≤n≤106) 个节点的无向有根树,对于点 i i i,有权值 t i ( − 100 ≤ t i ≤ 100 ) t_i(-100 \le t_i \le 100) ti(−100≤ti≤100)。
现在问你是否有一种方法,将整棵树剪成三棵,使得每颗树的权值相同。
设 t r e e ( u ) tree(u) tree(u) 的权值 w u w_u wu 为 ∑ v ∈ t r e e ( u ) t v \sum_{v \in tree(u)}{t_v} ∑v∈tree(u)tv。
设根节点为 r o o t root root。
看完题目,我们会很自然地想到剪完之后,每棵树的权值为 w r o o t 3 \frac{w_{root}}{3} 3wroot。
最初,我想到的是枚举两棵上述权值的子树,并将其删掉。但后来发现我忽略了子树套子树的情况。
考虑了所有情况后,我们对 DFS 进行改造,当发现一个权值为 w r o o t 3 \frac{w_{root}}{3} 3wroot 的子树 u u u(树根不能为 r o o t root root)之后,将 u u u 的权值置为 0 0 0,这样可以直接枚举下一棵子树,并且将所有的情况都考虑到了。
时间复杂度: O ( n ) O(n) O(n)。
int n, cnt, sum, root, tot;
int head[maxn], t[maxn], dp[maxn], ans[3];
struct edge
{
int v, nxt;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
root = -1;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs(int id, int fa)
{
dp[id] = t[id];
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == fa) continue;
dfs(v, id);
dp[id] += dp[v];
}
if(dp[id] * 3 == sum && id != root)
{
if(tot >= 2) return;
dp[id] = 0;
ans[++tot] = id;
}
}
int main()
{
int u;
scanf("%d", &n);
init();
for(int v = 1; v <= n; v++)
{
scanf("%d%d", &u, &t[v]);
sum += t[v];
if(!u)
{
if(root != -1)
{
printf("-1\n");
return 0;
}
else root = v;
continue;
}
addedge(u, v);
addedge(v, u);
}
if(sum % 3)
{
printf("-1\n");
return 0;
}
dfs(root, -1);
if(tot <= 1) printf("-1\n");
else printf("%d %d\n", ans[1], ans[2]);
return 0;
}
现在有 n ( 1 ≤ n ≤ 1 0 3 ) n(1 \le n \le 10^3) n(1≤n≤103) 支球队,每支球队 i i i 有两个名字 n a m e i , 0 name_{i,0} namei,0 和 n a m e i , 1 name_{i,1} namei,1。每个球队必须在两个名字中选择其中一个,并且满足如下条件:
( 1 ) (1) (1) 对于球队 i i i,若其选择了 n a m e i , 1 name_{i, 1} namei,1,则对于任意的 j ( 1 ≤ j ≤ n , i ≠ j ) j(1 \le j \le n,i \ne j) j(1≤j≤n,i̸=j),若满足 n a m e i , 0 ≠ n a m e j , 0 name_{i,0} \ne name_{j,0} namei,0̸=namej,0,则不可选择 n a m e j , 0 name_{j,0} namej,0。
( 2 ) (2) (2) 不能有任意两支球队的名字相同。
问每支球队的取名方案。
本题为 2-SAT \text{2-SAT} 2-SAT 问题,需要建立一个 2 n 2n 2n 个点的有向图。
设 i , j ∈ [ 1 , n ] , i ≠ j i,j \in [1,n], i \ne j i,j∈[1,n],i̸=j,
则设点 i , j i,j i,j 分别表示 i i i 选择了 n a m e i , 0 name_{i,0} namei,0 和 j j j 选择了 n a m e j , 0 name_{j,0} namej,0;
设点 i + n , j + n i + n,j + n i+n,j+n 分别表示 i i i 选择了 n a m e i , 1 name_{i,1} namei,1 和 j j j 选择了 n a m e j , 1 name_{j,1} namej,1。
设有向边 < i , j > <i,j> <i,j> 表示若 i i i 选择了 n a m e i , 0 name_{i,0} namei,0,则 j j j 必须选择 n a m e j , 0 name_{j,0} namej,0。
其他情况同理。
我们可以在 [ 1 , n ] [1,n] [1,n] 范围内遍历 i i i 和 j ( i ≠ j ) j(i \ne j) j(i̸=j),根据题中条件有以下三种情况:
( 1 ) (1) (1) 若 n a m e i , 0 = n a m e j , 0 name_{i,0}=name_{j,0} namei,0=namej,0,则添加如下边:
< i , j + n > <i,j+n> <i,j+n> 及其逆否命题 < j , i + n > <j, i+n> <j,i+n>(条件 ( 1 ) (1) (1))
< i + n , j + n > <i+n, j+n> <i+n,j+n> 及其逆否命题 < j , i > <j, i> <j,i>(条件 ( 2 ) (2) (2))
( 2 ) (2) (2) 若 n a m e i , 1 = n a m e j , 1 name_{i,1}=name_{j,1} namei,1=namej,1,则添加如下边:
< i + n , j > <i+n, j> <i+n,j> 及其逆否命题 < j + n , i > <j+n, i> <j+n,i>(条件 ( 1 ) (1) (1))
( 3 ) (3) (3) 若 n a m e i , 0 = n a m e j , 1 name_{i,0}=name_{j,1} namei,0=namej,1,则添加如下边:
< i , j > <i, j> <i,j> 及其逆否命题 < j + n , i + n > <j+n, i+n> <j+n,i+n>(条件 ( 1 ) (1) (1))
建边之后,在整张图上面跑 Tarjan
,求出所有的强连通分量。
之后,判断每个 i ( 1 ≤ i ≤ n ) i(1 \le i \le n) i(1≤i≤n) 是否满足 i i i 和 i + n i + n i+n 在同一个强连通分量中,若满足,则表明若选择 n a m e i , 0 name_{i,0} namei,0,则必须选择 n a m e i , 1 name_{i,1} namei,1,这与题意矛盾,因此答案不存在。
否则,输出一种合法的答案。
时间复杂度: O ( 2 n ) O(2n) O(2n)。
int head[maxn], n, m, cnt, tot, top, num;
int dfn[maxn], low[maxn], st[maxn], col[maxn], rev[maxn], var[maxn];
bool instack[maxn];
struct edge
{
int v, nxt;
} Edge[8 * maxn * maxn];
void init()
{
for(int i = 0; i <= 2 * n; i++) head[i] = -1;
cnt = tot = top = num = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void tarjan(int id)
{
dfn[id] = low[id] = ++tot;
st[++top] = id;
instack[id] = true;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[id] = min(low[id], low[v]);
}
else if(instack[v])
low[id] = min(low[id], dfn[v]);
}
if(dfn[id] == low[id])
{
int v;
num++;
do
{
v = st[top--];
instack[v] = false;
col[v] = num;
} while(v != id);
}
}
string name[maxn][2];
int main()
{
string stra, strb;
scanf("%d", &n);
init();
for(int i = 1; i <= n; i++)
{
cin >> stra >> strb;
name[i][0] += stra[0];
name[i][0] += stra[1];
name[i][0] += stra[2];
name[i][1] += stra[0];
name[i][1] += stra[1];
name[i][1] += strb[0];
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
if(i == j) continue;
if(name[i][0] == name[j][0])
{
addedge(i, j + n);
addedge(j, i + n);
addedge(i + n, j + n);
addedge(j, i);
}
if(name[i][1] == name[j][1])
{
addedge(i + n, j);
addedge(j + n, i);
}
if(name[i][0] == name[j][1])
{
addedge(i, j);
addedge(j + n, i + n);
}
}
}
for(int i = 1; i <= 2 * n; i++)
{
if(!dfn[i]) tarjan(i);
}
bool isok = true;
for(int i = 1; i <= n; i++)
{
if(col[i] == col[i + n])
{
isok = false;
break;
}
rev[i] = i + n;
rev[i + n] = i;
}
if(!isok)
{
printf("NO\n");
return 0;
}
for(int i = 1; i <= 2 * n; i++)
{
var[i] = col[i] > col[rev[i]];
}
printf("YES\n");
for(int i = 1; i <= n; i++)
{
cout << name[i][var[i]] << endl;
}
return 0;
}
给你 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2≤n≤105) 扇门和 m ( 2 ≤ m ≤ 1 0 5 ) m(2 \le m \le 10^5) m(2≤m≤105) 个开关,每扇门恰好由两个开关控制,每个开关控制着若干个门。
对于门 i i i,若 r i = 0 r_i=0 ri=0 表明初始状态下门是锁着的,若 r i = 1 r_i=1 ri=1 表明初始状态下门是打开的。
触动开关会使其控制的每一扇门的状态取反。
问是否可以通过某种策略(即选择某些开关将其打开),使得所有的门都是打开的。
本题为 2-SAT \text{2-SAT} 2-SAT 问题。
设点 j ( 1 ≤ j ≤ m ) j(1 \le j \le m) j(1≤j≤m) 表示开关 j j j 处于关闭(初始)状态,设点 j + m j+m j+m 表示开关 j j j 处于开启状态。
考虑每扇门 i i i,有以下两种情况:
( 1 ) (1) (1) 若 r i = 0 r_i=0 ri=0,则控制这扇门的两个开关必须且只能开启其中一个。
因此建立有向边 < i + m , j > <i+m, j> <i+m,j> 和 < j , i + m > <j, i+m> <j,i+m> 以及它们的逆否命题 < j + m , i > <j+m, i> <j+m,i> 和 < i , j + m > <i, j + m> <i,j+m>。
( 2 ) (2) (2) 若 r i = 1 r_i=1 ri=1,则控制这扇门的两个开关要么全部打开,要么全部关闭。
因此建立有向边 < i + m , j + m > <i+m, j+m> <i+m,j+m> 和 < i , j > <i, j> <i,j> 以及它们的逆否命题 < j , i > <j, i> <j,i> 和 < j + m , i + m > <j+m, i+m> <j+m,i+m>。
图建完之后,跑一遍 2-SAT \text{2-SAT} 2-SAT 即可。
时间复杂度: O ( 2 m ) O(2m) O(2m)。
int head[maxn], n, m, cnt, tot, top, num;
int dfn[maxn], low[maxn], st[maxn], col[maxn];
bool instack[maxn];
int r[maxn];
struct edge
{
int v, nxt;
} Edge[8 * maxn];
void init()
{
for(int i = 0; i <= 2 * m; i++) head[i] = -1;
cnt = tot = top = num = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void tarjan(int id)
{
dfn[id] = low[id] = ++tot;
st[++top] = id;
instack[id] = true;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[id] = min(low[id], low[v]);
}
else if(instack[v])
low[id] = min(low[id], dfn[v]);
}
if(dfn[id] == low[id])
{
int v;
num++;
do
{
v = st[top--];
instack[v] = false;
col[v] = num;
} while(v != id);
}
}
vector<int> ve[maxn];
int main()
{
int num, v;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= n; i++) scanf("%d", &r[i]);
for(int i = 1; i <= m; i++)
{
scanf("%d", &num);
for(int j = 1; j <= num; j++)
{
scanf("%d", &v);
ve[v].pb(i);
}
}
for(int i = 1; i <= n; i++)
{
int a = ve[i][0], b = ve[i][1];
if(r[i])
{
addedge(a + m, b + m);
addedge(a, b);
addedge(b + m, a + m);
addedge(b, a);
}
else
{
addedge(a + m, b);
addedge(a, b + m);
addedge(b, a + m);
addedge(b + m, a);
}
}
for(int i = 1; i <= 2 * m; i++)
{
if(!dfn[i]) tarjan(i);
}
bool isok = true;
for(int i = 1; i <= m; i++)
{
if(col[i] == col[i + m])
{
isok = false;
break;
}
}
if(isok) printf("YES\n");
else printf("NO\n");
return 0;
}
构造一个每个点的度数恰好为 k ( 1 ≤ k ≤ 100 ) k(1 \le k \le 100) k(1≤k≤100),且至少有一个桥的无向图。
规定点数 n n n 和边数 m m m 均不能超过 1 0 6 10^6 106。
**握手定理:**设一个无向连通图 G G G 的点数为 n n n,每个点 i i i 的度数为 d i d_i di,边数为 m m m,则满足
∑ x ∈ G d x = 2 ⋅ m \sum_{x \in G}d_x=2\cdot m x∈G∑dx=2⋅m
设要求出的图的边双连通分量的数量为 b b b,则桥的数量为 b − 1 b-1 b−1。
设每个边双连通分量 i i i 的点数为 n i n_i ni,边数为 m i m_i mi,则根据握手定理,我们可以列出如下方程
k ( n 1 − 1 ) + ( k − 1 ) = 2 m 1 k ( n 2 − 1 ) + 2 ( k − 1 ) = 2 m 2 k ( n 3 − 1 ) + 2 ( k − 1 ) = 2 m 3 . . . k ( n b − 1 − 1 ) + 2 ( k − 1 ) = 2 m b − 1 k ( n b − 1 ) + ( k − 1 ) = 2 m b \begin{aligned} k(n_1-1)+(k-1)&=2m_1 \\ k(n_2-1)+2(k-1)&=2m_2 \\ k(n_3-1)+2(k-1)&=2m_3 \\ ...& \\ k(n_{b-1}-1)+2(k-1)&=2m_{b-1} \\ k(n_b-1)+(k-1)&=2m_b \end{aligned} k(n1−1)+(k−1)k(n2−1)+2(k−1)k(n3−1)+2(k−1)...k(nb−1−1)+2(k−1)k(nb−1)+(k−1)=2m1=2m2=2m3=2mb−1=2mb
整理得
k n 1 = 2 m 1 + 1 k n 2 = 2 m 2 + 2 k n 3 = 2 m 3 + 2 . . . k b − 1 = 2 m b − 1 + 2 k n b = 2 m b + 1 \begin{aligned} kn_1&=2m_1+1 \\ kn_2&=2m_2+2 \\ kn_3&=2m_3+2 \\ &... \\ k_{b-1}&=2m_{b-1}+2 \\ kn_b&=2m_b+1 \end{aligned} kn1kn2kn3kb−1knb=2m1+1=2m2+2=2m3+2...=2mb−1+2=2mb+1
因此,若 k k k 为偶数,则上述方程无法成立,即没有答案。
为了简单起见,我们设 b = 2 b=2 b=2。
因此,我们有
k n 1 = 2 m 1 + 1 k n 2 = 2 m 2 + 1 \begin{aligned} kn_1&=2m_1+1 \\ kn_2&=2m_2+1 \end{aligned} kn1kn2=2m1+1=2m2+1
下列条件天然满足:
m 1 = k n 1 − 1 2 ≤ ( n 1 − 1 ) ⋅ n 1 2 m_1=\frac{kn_1-1}{2}\le \frac{(n_1-1)\cdot n_1}{2} m1=2kn1−1≤2(n1−1)⋅n1
m 2 = k n 2 − 1 2 ≤ ( n 2 − 1 ) ⋅ n 2 2 m_2=\frac{kn_2-1}{2}\le \frac{(n_2-1)\cdot n_2}{2} m2=2kn2−1≤2(n2−1)⋅n2
所以,我们可以暴力枚举 n 1 n_1 n1,求出合适的 m 1 m_1 m1。
则 n 2 = n 1 n_2=n_1 n2=n1, m 2 = m 1 m_2=m_1 m2=m1。
因此, n = n 1 + n 2 n=n_1+n_2 n=n1+n2, m = m 1 + m 2 + 1 m=m_1+m_2+1 m=m1+m2+1。
建图策略如代码所示,这里不再赘述。
int deg[205];
bool G[205][205];
int n;
void printedge(int u, int v)
{
printf("%d %d\n", u, v);
printf("%d %d\n", u + n, v + n);
G[u][v] = G[v][u] = true;
G[u + n][v + n] = G[v + n][u + n] = true;
deg[u]++;
deg[v]++;
deg[u + n]++;
deg[v + n]++;
}
int main()
{
int k;
scanf("%d", &k);
if(k == 1)
{
printf("YES\n");
printf("2 1\n");
printf("1 2\n");
return 0;
}
if(k % 2 == 0)
{
printf("NO\n");
return 0;
}
n = 0;
ll m;
for(int i = 1; i <= 1000000; i += 2)
{
m = 1LL * k * i;
m -= 1;
m /= 2;
if(m <= 1LL * i * (i - 1) / 2)
{
n = i;
break;
}
}
printf("YES\n");
printf("%d %lld\n", 2 * n, 2 * m + 1);
int cnt = 0;
int pos = n - 1;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
//dbg2(i, pos + 1);
if(i == n && deg[i] >= k - 1) break;
if(i != n && deg[i] >= k) break;
pos = (pos + 1) % n;
int v = pos + 1;
if(G[i][v] || i == v) continue;
if(v != n && deg[v] >= k) continue;
if(v == n && deg[v] >= k - 1) continue;
printedge(i, v);
++cnt;
}
}
printf("%d %d\n", n, 2 * n);
return 0;
}
给你一个有 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \le n \le 2 \cdot 10^5) n(1≤n≤2⋅105) 个点和 m ( 1 ≤ m ≤ 2 ⋅ 1 0 5 ) m(1 \le m \le 2 \cdot 10^5) m(1≤m≤2⋅105) 条边的无向图。
每个点 i i i 都有其权值 a i ( 1 ≤ a i ≤ 1 0 12 ) a_i(1 \le a_i \le 10^{12}) ai(1≤ai≤1012)。
设 d ( i , j ) d(i,j) d(i,j) 表示点 i i i 到点 j j j 之间最短路径的长度。
对于每个 i ∈ [ 1 , n ] i \in [1,n] i∈[1,n],求
min j = 1 n { 2 ⋅ d ( i , j ) + a j } \min_{j=1}^{n}\left\{2\cdot d(i,j) + a_j\right\} j=1minn{2⋅d(i,j)+aj}
本题采用朴素做法,会 TLE。
对于 Input 中给定的边,按其边权二倍存边。
之后,开一个超级源点 0 0 0,向每个点 i i i 建边,权值为 a i a_i ai。
则点 i i i 的答案为超级源点 0 0 0 到 i i i 的最短路径的长度。
时间复杂度: O ( ( 2 n + m ) log n ) O((2n+m)\log n) O((2n+m)logn)。
int main()
{
int m, u, v;
ll w;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%lld", &u, &v, &w);
addedge(u, v, 2 * w);
addedge(v, u, 2 * w);
}
for(int i = 1; i <= n; i++)
{
scanf("%lld", &w);
addedge(0, i, w);
addedge(i, 0, w);
}
Dijkstra();
for(int i = 1; i <= n; i++)
{
if(i > 1) printf(" ");
printf("%lld", d[i]);
}
return 0;
}
给你 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2≤n≤105) 个单词。
单词之间存在两种关系 t t t:
若 t = 1 t=1 t=1,表示两个单词为同义词;若 t = 2 t=2 t=2,表示两个单词为反义词。
现在给出 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1≤m≤105) 个关系和 q ( 1 ≤ q ≤ 1 0 5 ) q(1 \le q \le 10^5) q(1≤q≤105) 个查询。
给出 m m m 个关系时,若当前给出的两个单词不存在关系,则建立新的关系;
若存在关系,则输出已经存在的关系与给出的关系是否相一致。
之后 q q q 个查询,对于每个查询,输出两个单词之间的关系或输出两个单词之间没有关系。
每个单词对应无向图中的一个点。
对于同义词关系,建立边权为 0 0 0 的无向边;
对于反义词关系,建立边权为 1 1 1 的无向边。
每两个点之间最多有一条边,且不存在环,因此每个连通分量构成一棵树。
两个单词为反义词当且仅当二者距离为奇数;
两个单词为同义词当且仅当二者距离为偶数;
用并查集维护两个单词之间是否存在关系,即两点是否在同一连通分量中。
因为两个单词之间的关系是唯一确定的,因此对于 m m m 个关系,若关系不存在,则按照上述方法添加关系,并输出 YES
,否则需要先将关系存储起来,待所有关系确定之后,再去判断是否一致。
图建立完成之后,通过 DFS 计算每个点 i i i 与根节点的距离 d i d_i di,则两点 u , v u,v u,v 之间的距离为 d u + d v − 2 ⋅ d L C A ( u , v ) d_u+d_v-2\cdot d_{LCA(u,v)} du+dv−2⋅dLCA(u,v).
按照上述思路处理剩余的过程。
时间复杂度: O ( m α ( n ) + ( m + q ) log n ) O(m\alpha(n)+(m+q)\log n) O(mα(n)+(m+q)logn)
int n, cnt, range;
int father[maxn], head[maxn], rank_[maxn], dp[maxn], depth[maxn], f[maxn][65];
struct edge
{
int v, nxt;
int w;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++)
{
head[i] = -1;
father[i] = i;
}
cnt = 0;
range = (int)log(n) / log(2) + 1;
}
void addedge(int u, int v, int w)
{
Edge[cnt].v = v;
Edge[cnt].w = w;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
int find(int id)
{
return father[id] == id ? id : father[id] = find(father[id]);
}
void merge(int a, int b)
{
a = find(a);
b = find(b);
if(a != b)
{
if(rank_[a] > rank_[b]) father[b] = a;
else
{
father[a] = b;
if(rank_[a] == rank_[b]) rank_[b]++;
}
}
}
unordered_map<string, int> mp;
bool verdict[maxn];
pair<pii, int> query[maxn];
void dfs(int id, int fa)
{
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
int w = Edge[i].w;
if(v == fa) continue;
dp[v] = dp[id] + w;
depth[v] = depth[id] + 1;
f[v][0] = id;
for(int j = 1; j <= range; j++) f[v][j] = f[f[v][j - 1]][j - 1];
dfs(v, id);
}
}
int lca(int a, int b)
{
if(depth[a] < depth[b]) swap(a, b);
for(int i = range; i >= 0; i--)
if(depth[f[a][i]] >= depth[b]) a = f[a][i];
if(a == b) return a;
for(int i = range; i >= 0; i--)
{
if(f[a][i] != f[b][i])
{
a = f[a][i];
b = f[b][i];
}
}
return f[a][0];
}
int main()
{
int m, q, op;
string str, stra, strb;
scanf("%d%d%d", &n, &m, &q);
init();
for(int i = 1; i <= n; i++)
{
cin >> str;
mp[str] = i;
}
for(int i = 1; i <= m; i++)
{
scanf("%d", &op);
cin >> stra >> strb;
int a = mp[stra];
int b = mp[strb];
if(find(a) != find(b))
{
verdict[i] = true;
addedge(a, b, op - 1);
addedge(b, a, op - 1);
merge(a, b);
}
query[i] = make_pair(pii(a, b), op - 1);
}
for(int i = 1; i <= n; i++)
{
if(!depth[i])
{
depth[i] = 1;
dfs(i, -1);
}
}
for(int i = 1; i <= m; i++)
{
if(verdict[i]) printf("YES\n");
else
{
int a = query[i].first.first;
int b = query[i].first.second;
int c = query[i].second;
int LCA = lca(a, b);
int len = dp[a] + dp[b] - 2 * dp[LCA];
len %= 2;
if(len == c) printf("YES\n");
else printf("NO\n");
}
}
for(int i = 1; i <= q; i++)
{
cin >> stra >> strb;
int a = mp[stra];
int b = mp[strb];
if(find(a) != find(b))
{
printf("3\n");
continue;
}
int LCA = lca(a, b);
int len = dp[a] + dp[b] - 2 * dp[LCA];
len %= 2;
printf("%d\n", len + 1);
}
return 0;
}
建立一个没有自环和重边,有 n ( 2 ≤ n ≤ 1 0 3 ) n(2 \le n \le 10^3) n(2≤n≤103) 个节点的无向图 G G G,使得从点 1 1 1 到点 2 2 2 的最短路径数量恰好为 k ( 1 ≤ k ≤ 1 0 9 ) k(1 \le k \le 10^9) k(1≤k≤109)。
对 k k k 进行二进制分解,发现 k k k 有若干个 2 2 2 的幂次组成。
因此,可以考虑 2 2 2 的每个幂次的贡献进行建图。
以 k = 9 k=9 k=9 为例,则 k k k 的二进制分解为:
k = 1 ⋅ 2 3 + 0 ⋅ 2 2 + 0 ⋅ 2 1 + 1 ⋅ 2 0 k = 1 \cdot 2^3 + 0 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^0 k=1⋅23+0⋅22+0⋅21+1⋅20
则我们可按如下图方式建图:
其中 13 13 13 到 2 2 2 的边在 2 0 2^0 20 的系数为 1 1 1 的情况下才连上。
k k k 为其他值的情况与上面同理。
bool G[maxn][maxn];
void addedge(int u, int v)
{
G[u][v] = G[v][u] = true;
}
int bit[55];
int main()
{
int k;
scanf("%d", &k);
//k--;
int cnt = 0;
while(k)
{
bit[cnt++] = k % 2;
k /= 2;
}
if(!cnt) addedge(1, 2);
else
{
int num = 3, last = 1;
for(int i = cnt - 1; i >= 1; i--)
{
addedge(last, num);
if(bit[i])
{
addedge(num, num + 1);
addedge(num, num + 2);
}
if(i >= 2)
{
addedge(num + 1, num + 4);
addedge(num + 1, num + 5);
addedge(num + 2, num + 4);
addedge(num + 2, num + 5);
}
else
{
addedge(num + 1, 2);
addedge(num + 2, 2);
}
last = num;
num += 3;
}
addedge(last, num + 6);
if(bit[0]) addedge(num + 6, 2);
}
printf("1000\n");
for(int i = 1; i <= 1000; i++)
{
for(int j = 1; j <= 1000; j++)
{
if(G[i][j]) printf("Y");
else printf("N");
}
printf("\n");
}
return 0;
}
共有 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 个细菌,细菌的种类数为 k ( 1 ≤ k ≤ 500 ) k(1 \le k \le 500) k(1≤k≤500)。
对于第 i i i 种细菌,共有 c i c_i ci 个,编号从 ( ∑ k = 1 i − 1 c k ) + 1 (\sum_{k=1}^{i-1}c_k)+1 (∑k=1i−1ck)+1 到 ∑ k = 1 i c k \sum_{k=1}^ic_k ∑k=1ick。
题目保证 ∑ i = 1 k c i = n \sum_{i=1}^kc_i=n ∑i=1kci=n。
另外,细菌和细菌之间存在 m ( 0 ≤ m ≤ 1 0 5 ) m(0 \le m\le 10^5) m(0≤m≤105) 种双向转化关系,从 u i u_i ui 号细菌转化为 v i v_i vi 号细菌需要 x i x_i xi 的代价。
现在要求每一种细菌转化为其他类型细菌的最小代价,并且规定,若同种细菌互转的最小代价如果大于 0 0 0,即为不合法。
首先对每一种细菌进行 DFS,若遇到访问过的点或者遇到边权非 0 0 0 的边,则跳过。
之后,判断所有的细菌是否都被访问过,若不是,证明同种细菌之间转化的最小代价非零,直接输出 No
。
否则,枚举每个点的出边,更新类型一次转换的最小代价(存储为二维数组 G G G)。
之后,利用 Floyd \text{Floyd} Floyd,对 G G G 进行传递闭包,即可得到最终答案。
需要注意的是,同种细菌之间的转化可以借助于其他种类的细菌,千万不要忘记这一点,否则会 Wrong Answer
。
时间复杂度: O ( n + m + k 3 ) O(n+m+k^3) O(n+m+k3)。
int n, m, k, cnt;
int c[505], type[maxn], head[maxn];
bool mark[maxn];
int vis[maxn];
int G[505][505];
struct edge
{
int v, w, nxt;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
memset(G, 0x3f, sizeof(G));
}
void addedge(int u, int v, int w)
{
Edge[cnt].v = v;
Edge[cnt].w = w;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs(int id, int ty)
{
if(type[id] == ty) mark[id] = true;
vis[id] = ty;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(vis[v] == ty || Edge[i].w != 0) continue;
dfs(v, ty);
}
}
int main()
{
int u, v, w;
scanf("%d%d%d", &n, &m, &k);
init();
for(int i = 1; i <= k; i++) scanf("%d", &c[i]);
int flag = 0;
for(int i = 1; i <= k; i++)
{
for(int j = 1; j <= c[i]; j++) type[++flag] = i;
}
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
int sum = 1;
for(int i = 1; i <= k; i++)
{
dfs(sum, i);
sum += c[i];
}
int cnt = 0;
for(int i = 1; i <= n; i++) cnt += mark[i];
if(cnt < n)
{
printf("No\n");
return 0;
}
printf("Yes\n");
for(int i = 1; i <= k; i++) G[i][i] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = head[i]; j != -1; j = Edge[j].nxt)
{
int v = Edge[j].v;
int w = Edge[j].w;
int ta = type[i];
int tb = type[v];
G[ta][tb] = min(G[ta][tb], w);
G[tb][ta] = min(G[tb][ta], w);
}
}
for(int l = 1; l <= k; l++)
{
for(int i = 1; i <= k; i++)
{
for(int j = 1; j <= k; j++)
{
G[i][j] = min(G[i][j], G[i][l] + G[l][j]);
}
}
}
for(int i = 1; i <= k; i++)
{
for(int j = 1; j <= k; j++)
{
if(j > 1) printf(" ");
printf("%d", G[i][j] == 0x3f3f3f3f ? -1 : G[i][j]);
}
printf("\n");
}
return 0;
}
现在有 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \le n \le 2 \cdot 10^5) n(1≤n≤2⋅105) 名雇员,其中 1 1 1 号雇员为主任,它们组成一棵有根无向树。
a a a 能够管理 b b b,即 b b b 是 a a a 的手下,当且仅当 b ∈ s u b t r e e ( a ) b \in subtree(a) b∈subtree(a)。
每名雇员 i i i 都有其工作效率 a i ( 1 ≤ a i ≤ 1 0 5 ) a_i(1 \le a_i \le 10^5) ai(1≤ai≤105)。
现在每名雇员都要选取偶数个手下,组成工作小组,最终整个公司组成一个大的工作小组。
问这个大的工作小组的最大效率之和。
设 d p [ i d ] [ 0 ] dp[id][0] dp[id][0] 为在 s u b t r e e ( i d ) subtree(id) subtree(id) 中选取偶数个手下,所可以得到的最大的权值和。
d p [ i d ] [ 1 ] dp[id][1] dp[id][1] 为在 s u b t r e e ( i d ) subtree(id) subtree(id) 中选取奇数个手下,所可以得到的最大的权值和。
状态转移方程见代码。
最终的答案为 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1]。
时间复杂度: O ( n ) O(n) O(n)。
int head[maxn], n, cnt;
ll a[maxn], dp[maxn][2];
struct edge
{
int v, nxt;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; 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, int fa)
{
if(head[id] == -1)
{
dp[id][0] = 0;
dp[id][1] = a[id];
return;
}
dp[id][0] = 0;
dp[id][1] = -0x3f3f3f3f3f3f3f3f;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == fa) continue;
dfs(v, id);
ll temp0 = dp[id][0];
ll temp1 = dp[id][1];
dp[id][0] = max(temp0 + dp[v][0], temp1 + dp[v][1]);
dp[id][1] = max(temp0 + dp[v][1], temp1 + dp[v][0]);
}
dp[id][1] = max(dp[id][1], dp[id][0] + a[id]);
}
int main()
{
int u, root;
scanf("%d", &n);
init();
for(int v = 1; v <= n; v++)
{
scanf("%d%lld", &u, &a[v]);
if(u == -1) root = v;
else
{
addedge(u, v);
addedge(v, u);
}
}
dfs(root, -1);
printf("%lld\n", dp[root][1]);
return 0;
}
现在有一个有 n ( 1 ≤ n ≤ 100 ) n(1 \le n \le 100) n(1≤n≤100) 个节点, m ( 1 ≤ m ≤ n ( n − 1 ) 2 ) m(1 \le m \le \frac{n(n-1)}{2}) m(1≤m≤2n(n−1)) 条边的无向图。
每条边都有边权 c i ( c i ∈ { 0 , 1 } ) c_i(c_i \in \left\{0,1 \right\}) ci(ci∈{0,1})。
现在可以进行若干次(可以为零)如下操作:选择一个点,将其所有出边的权值全部取反。
问是否可以通过若干次操作,使得所有边的权值为 1 1 1。
若可以,输出所要操作的节点编号。
题目保证两点之间最多有一条路径。
本题为 2-SAT \text{2-SAT} 2-SAT 问题。
对于每一条边 e d g e i edge_i edgei,若其权值为 1 1 1,则连接它的两点要么全部操作,要么全部不操作;
若其权值为 0 0 0,则连接它的两点需要且只能操作其中一个。
求解思路与 776D \text{776D} 776D 相同,这里不再赘述。
int head[maxn], n, m, cnt, tot, top, num;
int dfn[maxn], low[maxn], st[maxn], col[maxn], var[maxn];
bool instack[maxn];
struct edge
{
int v, nxt;
} Edge[8 * maxn * maxn];
void init()
{
for(int i = 0; i <= 2 * n; i++) head[i] = -1;
cnt = tot = top = num = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void tarjan(int id)
{
dfn[id] = low[id] = ++tot;
st[++top] = id;
instack[id] = true;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[id] = min(low[id], low[v]);
}
else if(instack[v])
low[id] = min(low[id], dfn[v]);
}
if(dfn[id] == low[id])
{
int v;
num++;
do
{
v = st[top--];
instack[v] = false;
col[v] = num;
} while(v != id);
}
}
vector<int> ans;
int main()
{
int u, v, w;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &u, &v, &w);
if(w)
{
addedge(u + n, v + n);
addedge(v, u);
addedge(v + n, u + n);
addedge(u, v);
}
else
{
addedge(u + n, v);
addedge(v + n, u);
addedge(u, v + n);
addedge(v, u + n);
}
}
for(int i = 1; i <= 2 * n; i++)
{
if(!dfn[i]) tarjan(i);
}
bool isok = true;
for(int i = 1; i <= n; i++)
{
if(col[i] == col[i + n])
{
isok = false;
break;
}
}
if(!isok)
{
printf("Impossible\n");
return 0;
}
for(int i = 1; i <= n; i++)
{
var[i] = col[i] > col[i + n];
if(var[i]) ans.pb(i);
}
printf("%d\n", (int)ans.size());
for(int i = 0; i < ans.size(); i++)
{
if(i > 0) printf(" ");
printf("%d", ans[i]);
}
return 0;
}
定义函数 g : { 1 , 2 , . . . , n } → { 1 , 2 , . . . , n } g:\left\{ 1,2,...,n\right\} \rightarrow \left\{ 1,2,...,n\right\} g:{1,2,...,n}→{1,2,...,n} 为幂等函数,当且仅当对于任意的 x ∈ { 1 , 2 , . . . , n } x \in \left\{ 1,2,...,n\right\} x∈{1,2,...,n},满足 g ( g ( x ) ) = g ( x ) g(g(x))=g(x) g(g(x))=g(x)。
设 f ( 1 ) ( x ) = f ( x ) f^{(1)}(x)=f(x) f(1)(x)=f(x), f ( k ) ( x ) = f ( f ( k − 1 ) ( x ) ) f^{(k)}(x)=f(f^{(k-1)}(x)) f(k)(x)=f(f(k−1)(x)),对于任意的 k > 1 k>1 k>1。
现在给你每一个 f ( x ) f(x) f(x) 的值,求最小的 k k k,使得 f ( k ) ( x ) f^{(k)}(x) f(k)(x) 为幂等函数。
数据范围: 1 ≤ n ≤ 200 1 \le n \le 200 1≤n≤200。
对于每一个 x ∈ { 1 , 2 , . . . , n } x \in \left\{ 1,2,...,n\right\} x∈{1,2,...,n},建立有向边 < x , f ( x ) > <x,f(x)> <x,f(x)>。
对于每一个连通分量 G i G_i Gi,若答案存在,则其中必有环,其最小的 k i k_i ki 值满足以下条件:
( 1 ) (1) (1) 大于等于离环最远的点到环的最短距离;
( 2 ) (2) (2) 是环长度 T i T_i Ti 的倍数。
设连通分量的数量为 m m m, LCM = lcm ( T 1 , T 2 , . . . T m ) \text{LCM}=\text{lcm}(T_1, T_2,...T_m) LCM=lcm(T1,T2,...Tm)。
则最终的答案为
k = max 1 ≤ i ≤ m { ⌈ k i LCM ⌉ ⋅ LCM } k=\max_{1 \le i \le m} \left\{\lceil \frac{k_i}{\text{LCM}} \rceil \cdot \text{LCM} \right\} k=1≤i≤mmax{⌈LCMki⌉⋅LCM}
int n;
int dfn[maxn], nxt[maxn], indeg[maxn], vis[maxn];
ll ans[maxn];
ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a % b);
}
ll lcm(ll a, ll b)
{
return a / gcd(a, b) * b;
}
pll dfs(int id, int depth, int col)
{
dfn[id] = depth;
vis[id] = col;
int v = nxt[id];
if(vis[v] == col)
{
ll T = 1LL * (dfn[id] - dfn[v] + 1);
return make_pair(max(1LL * dfn[id] / T * T, T), T);
}
return dfs(v, depth + 1, col);
}
ll divv(ll a, ll b)
{
if(a % b == 0) return a / b;
return a / b + 1;
}
int main()
{
int v;
scanf("%d", &n);
for(int u = 1; u <= n; u++)
{
scanf("%d", &v);
nxt[u] = v;
indeg[v]++;
}
ll LCM = 1;
int pos = 0;
for(int i = 1; i <= n; i++)
{
if(!vis[i])
{
pii res = dfs(i, 0, i);
LCM = lcm(LCM, res.Se);
ans[++pos] = res.Fi;
}
}
ll answer = 1;
for(int i = 1; i <= pos; i++)
{
answer = max(answer, divv(ans[i], LCM) * LCM);
}
printf("%lld\n", answer);
return 0;
}
给你一个有 n ( 2 ≤ n ≤ 3 ⋅ 1 0 5 ) n(2 \le n \le 3 \cdot 10^5) n(2≤n≤3⋅105) 个节点和 m ( 1 ≤ m ≤ 3 ⋅ 1 0 5 ) m(1 \le m \le 3 \cdot 10^5) m(1≤m≤3⋅105) 条边的既有无向边,又有有向边的图。
现在需要你给出两套方案,将所有的无向边都进行定向,分别使从点 s s s 能够通达的点最多和最少。
对无向边和有向边进行标记,并将无向边的两个方向进行标记,以区分是否改变方向。
之后,进行两遍 DFS,分别求最多的点和最少的点。
第一遍 DFS,遇到无向边就走;第二遍 DFS,遇到无向边的话就跳过。
在 DFS 的过程中,对每条无向边进行标记,即可得到答案。
时间复杂度: O ( n ) O(n) O(n)。
int head[maxn], n, cnt;
bool vis[maxn], vis2[2 * maxn];
int ans[2 * maxn][2];
int tot[2];
struct edge
{
int v, nxt;
int type, parity;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void addedge(int u, int v, int type, int parity = 0)
{
Edge[cnt].v = v;
Edge[cnt].parity = parity;
Edge[cnt].type = type;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs(int id)
{
vis[id] = true;
tot[0]++;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(Edge[i].type == 2)
{
if(Edge[i].parity == 0)
{
if(vis2[i] || vis2[i + 1]) continue;
vis2[i] = vis2[i + 1] = true;
ans[i][0] = ans[i + 1][0] = Edge[i].parity;
}
else
{
if(vis2[i] || vis2[i - 1]) continue;
vis2[i] = vis2[i - 1] = true;
ans[i][0] = ans[i - 1][0] = Edge[i].parity;
}
}
if(vis[v]) continue;
dfs(v);
}
}
void dfs2(int id)
{
vis[id] = true;
tot[1]++;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(Edge[i].type == 2)
{
if(Edge[i].parity == 0)
{
if(vis2[i] || vis2[i + 1]) continue;
vis2[i] = vis2[i + 1] = true;
ans[i][1] = ans[i + 1][1] = 1 - Edge[i].parity;
}
else
{
if(vis2[i] || vis2[i - 1]) continue;
vis2[i] = vis2[i - 1] = true;
ans[i][1] = ans[i - 1][1] = 1 - Edge[i].parity;
}
}
else if(!vis[v]) dfs2(v);
}
}
int main()
{
int m, s, u, v, type;
scanf("%d%d%d", &n, &m, &s);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &type, &u, &v);
if(type == 1)
{
addedge(u, v, 1);
}
else
{
addedge(u, v, 2, 0);
addedge(v, u, 2, 1);
}
}
dfs(s);
memset(vis, false, sizeof(vis));
memset(vis2, false, sizeof(vis2));
dfs2(s);
printf("%d\n", tot[0]);
for(int i = 0; i < cnt; i++)
{
if(Edge[i].type == 1) continue;
if(ans[i][0] == 0) printf("+");
else printf("-");
i++;
}
printf("\n");
printf("%d\n", tot[1]);
for(int i = 0; i < cnt; i++)
{
if(Edge[i].type == 1) continue;
if(ans[i][1] == 0) printf("+");
else printf("-");
i++;
}
return 0;
}
给你一个 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1≤n≤105) 个点, m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1≤m≤105) 条边的有向图。每个点 i i i 都有一个权值 v i ( 0 ≤ v i ≤ 2 ) v_i(0 \le v_i \le 2) vi(0≤vi≤2)。
定义一个点是完美的,当且仅当这个点在一条以权值为 1 1 1 的点开始的,中间不出现权值为 1 1 1 的点,且以权值为 2 2 2 的点结束的路径上。
对于每个点,判断其是否完美。
对整个图进行两遍 DFS,第一遍 DFS 遍历以权值为 1 1 1 的点开始,不经过权值为 1 1 1 的点的路径。
第二遍 DFS 遍历以权值为 2 2 2 的点开始,遍历到权值为 1 1 1 的点结束。
这样,两次 DFS 都经过的点,就是完美的点。
int n, f[maxn];
bool vis[maxn], vis2[maxn];
bool ans[maxn];
vector<int> G[maxn];
vector<int> G2[maxn];
void dfs(int id)
{
vis[id] = true;
for(auto v: G[id])
{
if(vis[v] == true || f[v] == 1) continue;
dfs(v);
}
}
void dfs2(int id)
{
vis2[id] = true;
for(auto v: G2[id])
{
if(f[v] == 1) vis2[v] = true;
if(vis2[v] == true) continue;
vis2[v] = true;
dfs2(v);
}
}
int main()
{
int m, u, v;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &f[i]);
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
G[u].pb(v);
G2[v].pb(u);
}
for(int i = 1; i <= n; i++)
{
if(f[i] == 1) dfs(i);
}
for(int i = 1; i <= n; i++)
{
if(f[i] == 2) dfs2(i);
}
for(int i = 1; i <= n; i++)
{
if(vis[i] && vis2[i]) printf("1\n");
else printf("0\n");
}
return 0;
}