AC
之后不写题解,赛前火葬场!!
题目链接:https://codeforces.com/contest/507/problem/E
给你一个 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n\le 10^5) n(2≤n≤105) 个点和 m ( 0 ≤ m ≤ min ( n ( n − 1 ) 2 , 1 0 5 ) ) m(0 \le m\le \min(\frac{n(n-1)}{2},10^5)) m(0≤m≤min(2n(n−1),105)) 条边的无向连通图,保证没有自环和重边。所有边的长度均为 1 1 1,每一条边 i i i 都有一个状态 w i ( w i ∈ [ 0 , 1 ] ) w_i(w_i \in [0,1]) wi(wi∈[0,1]),状态为 0 0 0 表明这条边未在工作,状态为 1 1 1 表明这条边正在工作。
你可以进行若干次操作,每次操作可以将一条边的状态改变。
现在要从 1 1 1 点走到 n n n 点,使得最短路上面的边都处于工作状态,最短路之外的边都处于非工作状态。
问最少的操作次数。
显然,本题的所有操作需要在保证最短路的基础上进行。
因此我们通过 Dijkstra 跑最短路,记录两个变量 d [ i ] d[i] d[i] 表示 1 1 1 到 i i i 的最短路距离, l e n [ i ] len[i] len[i] 表示从 1 1 1 走最短路走到 i i i,最大的处于工作状态的道路数量。
之后在松弛 ( u , v , w ) (u,v,w) (u,v,w) 时,分两种情况:
松弛时记录每个点的前驱边,进行完 Dijkstra 之后直接处理答案即可。
时间复杂度: O ( ( n + m ) log n ) O((n+m) \log n) O((n+m)logn)。
int head[maxn], n, cnt, d[maxn], from[maxn];
int len[maxn];
bool mark[2 * maxn], vis[maxn];
struct edge
{
int u, v, nxt;
int w;
} Edge[2 * maxn];
struct node
{
int d, id, len;
node (int _d, int _id, int _len):
d(_d), id(_id), len(_len) {}
const bool operator < (const node b) const
{
return d > b.d || (d == b.d && len < b.len);
}
};
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void addedge(int u, int v, int w)
{
Edge[cnt].u = u;
Edge[cnt].v = v;
Edge[cnt].w = w;
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, len[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;
if(d[v] > d[now.id] + 1)
{
d[v] = d[now.id] + 1;
len[v] = len[now.id] + Edge[i].w;
que.push(node(d[v], v, len[v]));
from[v] = i;
}
else if(d[v] == d[now.id] + 1)
{
if(len[v] < len[now.id] + Edge[i].w)
{
len[v] = len[now.id] + Edge[i].w;
from[v] = i;
}
}
}
}
}
vector<pair<pii, int> > ans;
int main()
{
int m, u, v, w;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
Dijkstra();
int pos = n;
while(pos != 1)
{
int id = from[pos];
mark[id] = mark[id ^ 1] = true;
pos = Edge[id].u;
}
for(int i = 0; i < cnt; i += 2)
{
u = Edge[i].u;
v = Edge[i].v;
w = Edge[i].w;
if(w == 0 && mark[i]) ans.pb(make_pair(pii(u, v), 1));
else if(w == 1 && mark[i] == false) ans.pb(make_pair(pii(u, v), 0));
}
printf("%d\n", (int)ans.size());
for(int i = 0; i < ans.size(); i++)
{
pair<pii, int> now = ans[i];
printf("%d %d %d\n", now.first.first, now.first.second, now.second);
}
return 0;
}
题目链接:https://codeforces.com/problemset/problem/915/D
给你一个 n ( 2 ≤ n ≤ 500 ) n(2 \le n \le 500) n(2≤n≤500) 个点和 1 ≤ m ≤ min ( n ( n − 1 ) , 1 0 5 ) 1 \le m\le \min(n(n-1),10^5) 1≤m≤min(n(n−1),105) 条边的有向图,保证不含自环和重边。
现在允许你最多删除一条边,问是否可以使整个图形成一个有向无环图。
判定有向无环图,只需通过拓扑排序,若拓扑排序成功,证明为有向无环图。
一个显然的思路是,枚举要删的边,之后跑一遍拓扑排序来 check,但这样做的时间复杂度是 O ( m ⋅ ( n + m ) ) O(m \cdot (n+m)) O(m⋅(n+m)) 的,显然会超时,因此我们考虑换一种策略。
删除一条边,等价于使一个点的入度减 1 1 1。而我们注意到 n n n 很小,因此我们直接枚举入度减 1 1 1 的点,将该点的入度减 1 1 1 之后去 check,时间复杂度为 O ( n ⋅ ( n + m ) ) O(n \cdot (n+m)) O(n⋅(n+m)),是可以接受的。
注意要在不删边的情况下先跑一遍拓扑排序。
int head[maxn], n, cnt, indeg[maxn], indeg2[maxn];
int topo[maxn];
struct edge
{
int v, nxt;
ll length;
} Edge[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++;
}
bool toposort()
{
int pos = 0;
queue<int> que;
for(int i = 1; i <= n; i++)
{
if(!indeg[i]) que.push(i);
}
while(!que.empty())
{
int now = que.front();
que.pop();
topo[++pos] = now;
for(int i = head[now]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == now) continue;
--indeg[v];
if(!indeg[v]) que.push(v);
}
}
return pos == n;
}
int main()
{
int m, u, v;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
indeg[v]++;
}
memcpy(indeg2, indeg, sizeof(indeg));
bool isok = toposort();
for(int j = 1; j <= n; j++) indeg[j] = indeg2[j];
for(int i = 1; i <= n; i++)
{
indeg[i]--;
if(toposort())
{
isok = true;
break;
}
for(int j = 1; j <= n; j++) indeg[j] = indeg2[j];
}
if(isok) printf("YES\n");
else printf("NO\n");
return 0;
}
题目链接:https://codeforces.com/problemset/problem/788/B
给你一个 n ( 1 ≤ n ≤ 1 0 6 ) n(1 \le n \le 10^6) n(1≤n≤106) 个点和 m ( 1 ≤ m ≤ 1 0 6 ) m (1 \le m \le 10^6) m(1≤m≤106) 条边的无向图,保证没有重边,但可能有自环。
一条路径被称为好的,当且仅当这条路径经过 m − 2 m-2 m−2 条边两次,并经过 2 2 2 条边恰好一次。
问好的路径的方案数。
如果我们把每一条边都复制一份的话,会发现每个点的度数一定为偶数,因此所有的边构成了一个欧拉回路。
此时问题就转化为在一个欧拉回路上删掉两条边,使得剩下的边构成一个欧拉通路。
一个图构成一个欧拉通路,当且仅当奇度顶点的数量为 0 0 0 或 2 2 2。
设自环的数量为 n u m num num。
枚举要删除的第一个边 i i i,分几种情况:
这时我们会发现,每种方案都被计算了两次,因此最终的答案为方案数除以 2 2 2。
特别地,如果图不连通,则答案一定为 0 0 0。
时间复杂度: O ( n + m ) O(n+m) O(n+m)。
int n, cnt;
int head[maxn];
struct edge
{
int u, 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].u = u;
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
int deg[maxn];
bool vis[maxn];
void dfs(int id)
{
vis[id] = true;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(vis[v]) continue;
dfs(v);
}
}
int main()
{
int m, u, v;
scanf("%d%d", &n, &m);
init();
int circle = 0;
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
vis[u] = vis[v] = true;
if(u != v)
{
addedge(v, u);
deg[v]++;
deg[u]++;
}
if(u == v) circle++;
}
int num = 0;
for(int i = 1; i <= n; i++) num += vis[i];
for(int i = 1; i <= n; i++)
{
if(vis[i])
{
memset(vis, false, sizeof(vis));
dfs(i);
break;
}
}
int tot = 0;
for(int i = 1; i <= n; i++) tot += vis[i];
if(tot < num)
{
printf("0\n");
return 0;
}
ll ans = 0;
for(int i = 0; i < cnt; i++)
{
u = Edge[i].u;
v = Edge[i].v;
if(u == v) ans += 1LL * (m - 1);
else
{
ans += 1LL * circle;
ans += 1ll * (deg[u] - 1);
ans += 1ll * (deg[v] - 1);
i++;
}
}
printf("%lld\n", ans / 2);
return 0;
}
题目链接:https://codeforces.com/problemset/problem/11/D
给你一个 n ( 1 ≤ n ≤ 19 ) n(1 \le n \le 19) n(1≤n≤19) 个点和 m ( m ≥ 0 ) m(m \ge 0) m(m≥0) 条边的无向图,保证不含自环和重边。
计算图中简单环的个数。
简单环定义为不含重复的顶点和边的环。
首先考虑一个 n n n 元环可以有 n n n 个起点,为了防止重复计算,我们设一个环的起点为其内部编号最小的点。
因为 n n n 很小,所以可以考虑状压。
设 d p [ S ] [ i ] dp[S][i] dp[S][i] 表示以 l o w b i t ( S ) lowbit(S) lowbit(S) 对应的点为起点, i i i 为终点的路径的方案数。
如果 i i i 是以 1 1 1 为起始下标的话,边界值为 d p [ 1 < < ( i − 1 ) ] [ i ] = 1 dp[1 << (i-1)][i] = 1 dp[1<<(i−1)][i]=1。
之后枚举 S S S 和当前终点 u u u 进行转移,在此基础上枚举 u u u 的邻接点 v v v,如果出现 v = l o w b i t ( S ) v=lowbit(S) v=lowbit(S),证明找到了环,将 d p [ S ] [ u ] dp[S][u] dp[S][u] 加入到答案中;否则进行转移 d p [ S ∣ ( 1 < < ( v − 1 ) ] [ v ] = d p [ S ∣ ( 1 < < ( v − 1 ) ] [ v ] + d p [ S ] [ u ] dp[S | (1 << (v-1)][v] = dp[S | (1 << (v-1)][v] + dp[S][u] dp[S∣(1<<(v−1)][v]=dp[S∣(1<<(v−1)][v]+dp[S][u]。
时间复杂度: O ( 2 n ⋅ ( n + m ) ) O(2^n \cdot (n+m)) O(2n⋅(n+m))。
int n, m, cnt;
int head[maxn];
ll dp[1 << 20][25];
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 lowbit(int x)
{
return x & (-x);
}
int main()
{
int u, v;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
ll ans = 0;
for(int i = 1; i <= n; i++) dp[1 << (i - 1)][i] = 1;
for(int i = 0; i < (1 << n); i++)
{
for(int j = 1; j <= n; j++)
{
if(!((i >> (j - 1)) & 1)) continue;
for(int k = head[j]; k != -1; k = Edge[k].nxt)
{
int v = Edge[k].v;
if((1 << (v - 1)) < lowbit(i)) continue;
if(((i >> (v - 1)) & 1) && ((1 << (v - 1)) == lowbit(i))) ans += dp[i][j];
else dp[i + (1 << (v - 1))][v] += dp[i][j];
}
}
}
printf("%lld\n", (ans - m) / 2);
return 0;
}
题目链接:https://codeforces.com/problemset/problem/715/B
给你一个 n ( 0 ≤ n ≤ 1000 ) n(0 \le n \le 1000) n(0≤n≤1000) 个点和 1 ≤ m ≤ 1 0 4 1 \le m \le 10^4 1≤m≤104 条边的无向图,保证没有自环和重边。
每条边都有边权 w i ( 1 ≤ w i ≤ 1 0 9 ) w_i(1 \le w_i \le 10^9) wi(1≤wi≤109),但一些边权丢失了(标记为 0 0 0)。
现在要对丢失边权的边重新分配一个正的边权,问是否存在一种方案,使得从 s s s 到 t t t 的最短路的长度恰好为 L L L。
先假设丢失边权的边不存在,从 s s s 出发跑一遍 Dijkstra。
之后如果 d [ t ] < L d[t] < L d[t]<L,证明无论如何分配边权都无法达到要求,输出无解。
之后从 t t t 出发倒着跑 Dijkstra,贪心地将边权分配即可。
因为每条边的边权至少为 1 1 1,所以极有可能导致 d ′ [ s ] > L d'[s]>L d′[s]>L,此时依然无解。
否则输出每条边的分配情况即可。
时间复杂度: O ( ( n + m ) log n ) O((n+m)\log n) O((n+m)logn)。
int n, cnt, s, t;
int head[maxn];
bool vis[maxn], vis2[maxn];
ll d[maxn], d2[maxn], l;
struct edge
{
int u, v, nxt;
ll w;
} Edge[20 * 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;
cnt = 0;
}
void addedge(int u, int v, ll w)
{
Edge[cnt].u = u;
Edge[cnt].v = v;
Edge[cnt].w = w;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
priority_queue<node> que;
void Dijkstra()
{
memset(d, 0x3f, sizeof(d));
d[s] = 0;
que.push(node(d[s], s));
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(!w) continue;
if(d[v] > d[now.id] + w)
{
d[v] = d[now.id] + w;
que.push(node(d[v], v));
}
}
}
}
void Dijkstra2()
{
memset(d2, 0x3f, sizeof(d2));
memset(vis, false, sizeof(vis));
d2[t] = 0;
que.push(node(d2[t], t));
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(!w)
{
w = Edge[i].w = Edge[i ^ 1].w = max(1LL, l - d[v] - d2[now.id]);
}
if(d2[v] > d2[now.id] + w)
{
d2[v] = d2[now.id] + w;
que.push(node(d2[v], v));
}
}
}
}
int main()
{
int m, u, v;
ll w;
scanf("%d%d%lld%d%d", &n, &m, &l, &s, &t);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%lld", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
Dijkstra();
if(d[t] < l)
{
printf("NO\n");
return 0;
}
Dijkstra2();
if(d2[s] != l) printf("NO\n");
else
{
printf("YES\n");
for(int i = 0; i < cnt; i += 2)
{
printf("%d %d %lld\n", Edge[i].u, Edge[i].v, Edge[i].w);
}
}
return 0;
}
题目链接:https://codeforces.com/problemset/problem/1100/E
给你一个 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2≤n≤105) 个点和 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1≤m≤105) 条边的有向图。
现在你需要选择一些边,将其方向反转,这个代价为边权 w i w_i wi。
总的代价为所操作边的代价的最大值。
问最小的代价,使得整个图为有向无环图。
同时需要输出反转边的方案。
总的代价为所操作边的代价的最大值。
这个条件促使我们向二分答案的方向去想。
二分所操作边的代价的最大值 x x x,之后将代价大于 x x x 的边加入,跑一遍拓扑排序,如果发现这个残余的图中没有环,证明这个代价可行(因为那些没有加入的边可以随意调整方向直到不存在环);如果有环,证明这个代价不可行,它没有能力使那些代价大于 x x x 的边反转。
之后就要考虑如何输出方案了。
我们基于这个二分出的 x x x,将代价大于 x x x 的边加入,再跑一遍拓扑排序。
之后枚举代价不超过 x x x 的边 < u , v > <u,v>,如果发现 u u u 的拓扑序大于 v v v 的拓扑序,证明如果不经反转便加入 < u , v > <u,v>,会形成一个有向环,所以此时 < u , v > <u,v> 必须反转;否则 < u , v > <u,v> 不反转。
时间复杂度: O ( n log m ) O(n \log m) O(nlogm)。
int head[maxn], n, m, cnt;
int indeg[maxn], outdeg[maxn], topo[maxn], pos[maxn];
struct edge
{
int u, v, w, nxt;
} Edge[maxn], input[maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
memset(indeg, 0, sizeof(indeg));
memset(topo, 0, sizeof(topo));
cnt = 0;
}
void addedge(int u, int v, int w)
{
Edge[cnt].v = v;
indeg[v]++;
Edge[cnt].w = w;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
bool toposort()
{
queue<int> que;
for(int i = 1; i <= n; i++)
{
if(!indeg[i]) que.push(i);
}
int tot = 0;
while(!que.empty())
{
int now = que.front();
que.pop();
topo[++tot] = now;
for(int i = head[now]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(--indeg[v] == 0)
{
que.push(v);
}
}
}
return tot == n;
}
bool check(int x)
{
init();
for(int i = 1; i <= m; i++)
{
if(input[i].w > x) addedge(input[i].u, input[i].v, input[i].w);
}
return toposort();
}
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);
input[i] = (edge){u, v, w, -1};
}
int res = 0x3f3f3f3f;
int left = 0, right = 1000000000;
while(left <= right)
{
int mid = left + (right - left) / 2;
if(check(mid))
{
res = min(res, mid);
right = mid - 1;
}
else left = mid + 1;
}
init();
for(int i = 1; i <= m; i++)
{
if(input[i].w > res) addedge(input[i].u, input[i].v, input[i].w);
}
toposort();
for(int i = 1; i <= n; i++)
{
pos[topo[i]] = i;
}
for(int i = 1; i <= m; i++)
{
u = input[i].u;
v = input[i].v;
if(input[i].w <= res)
{
if(pos[u] > pos[v]) ans.pb(i);
}
}
printf("%d %d\n", res, (int)ans.size());
for(int i = 0; i < ans.size(); i++)
{
if(i > 0)
printf(" ");
printf("%d", ans[i]);
}
return 0;
}
题目链接:https://codeforces.com/problemset/problem/1029/E
给你一棵 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \le n \le 2 \cdot 10^5) n(2≤n≤2⋅105) 个点的无向树,现在你需要添加一些边,使得 1 1 1 到任意点的最短路长度不超过 2 2 2。
问最少添加的边数。
先进行一波 DFS/BFS,处理出 1 1 1 到每个点的距离。
之后将那些距离大于 2 2 2 的点丢进一个 set
中,按照距离大的优先。
每次从 set
中拿出一个元素,答案加上 1 1 1,将其邻接的点全部移出 set
。
处理完 set
之后,便得到了答案。
int n, cnt, ans;
int head[maxn], p[maxn], d[maxn];
struct edge
{
int v, nxt;
} Edge[2 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0, ans = 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)
{
p[id] = fa;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == fa) continue;
d[v] = d[id] + 1;
dfs(v, id);
}
}
struct node
{
int id;
node (int _id):
id(_id) {}
const bool operator < (const node b) const
{
return d[id] > d[b.id] || (d[id] == d[b.id] && id < b.id);
}
};
set<node> se;
int main()
{
int u, v;
scanf("%d", &n);
init();
for(int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dfs(1, -1);
for(int i = 1; i <= n; i++)
{
if(d[i] > 2)
{
se.insert(node(i));
}
}
while(!se.empty())
{
node now = *se.begin();
int id = p[now.id];
if(se.find(node(id)) != se.end()) se.erase(node(id));
ans++;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(se.find(node(v)) != se.end()) se.erase(node(v));
}
}
printf("%d\n", ans);
return 0;
}
题目链接:https://codeforces.com/problemset/problem/1133/F2
给你一个 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \le n \le 2 \cdot 10^5) n(2≤n≤2⋅105) 个点和 n − 1 ≤ m ≤ min ( 2 ⋅ 1 0 5 , n ( n − 1 ) 2 ) n-1 \le m \le \min(2 \cdot 10^5,\frac{n(n-1)}{2}) n−1≤m≤min(2⋅105,2n(n−1)) 条边的无向图,保证没有自环和重边。
现在需要你构造一棵生成树,使得 1 1 1 点的度数恰好为 D ( 1 ≤ D < n ) D(1 \le D < n) D(1≤D<n),或者说明无解。
首先,如果原图中 1 1 1 点的度数小于 D D D 的话,一定无解。
我们将 1 1 1 暂时抠掉,去跑连通块,记录每一个连通块与 1 1 1 连边的点的个数以及这些点的集合。
如果连通块的个数大于 D D D,证明如果 1 1 1 的度数为 D D D 的话,无法使图联通。
否则,我们现将每个连通块中的一个点与 1 1 1 连边,如果 1 1 1 的度数不够 D D D 的话,再随便连接其他涉及到 1 1 1 的边。上述连边过程需要使用并查集维护联通情况。
最后按照传统的生成树思路,利用并查集连接剩下的边。
时间复杂度: O ( n α ( n ) ) O(n\alpha(n)) O(nα(n))。
int head[maxn], father[maxn], n, cnt, belong[maxn], tot, deg[maxn], perm[maxn], rankk[maxn];
bool vis[maxn];
vector <int> conn[maxn];
bool cmp(int a, int b)
{
return deg[a] < deg[b];
}
struct edge
{
int u, v, nxt;
} Edge[2 * maxn];
void init()
{
memset(head, -1, sizeof(head));
memset(belong, 0, sizeof(belong));
for(int i = 0; i <= n; i++) father[i] = i;
cnt = 0, tot = 0;
}
int query(int id)
{
return father[id] == id ? id : query(father[id]);
}
void merge(int x, int y)
{
x = query(x);
y = query(y);
if(x != y)
{
if(rankk[x] > rankk[y]) father[y] = x;
else
{
father[x] = y;
if(rankk[x] == rankk[y]) rankk[y]++;
}
}
}
void addedge(int u, int v)
{
Edge[cnt].u = u;
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void dfs(int id)
{
if(vis[id]) return;
vis[id] = true;
belong[id] = tot;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == 1)
{
deg[tot]++;
conn[tot].pb(id);
continue;
}
dfs(v);
}
}
int main()
{
int m, u, v, d;
scanf("%d%d%d", &n, &m, &d);
init();
int degone = 0;
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
degone += (u == 1 || v == 1);
}
for(int i = 2; i <= n; i++)
{
if(!vis[i])
{
tot++;
dfs(i);
}
}
if(tot > d || degone < d)
{
printf("NO\n");
return 0;
}
for(int i = 1; i <= tot; i++) perm[i] = i;
int num = 0;
printf("YES\n");
for(int i = 1; i <= tot; i++)
{
int id = perm[i];
num++;
printf("%d %d\n", 1, conn[id][0]);
merge(1, conn[id][0]);
if(num == d) break;
}
for(int i = 1; i <= tot; i++)
{
int id = perm[i];
for(int j = 1; j < conn[id].size(); j++)
{
if(num >= d) break;
num++;
printf("%d %d\n", 1, conn[id][j]);
merge(1, conn[id][j]);
}
if(num >= d) break;
}
for(int i = 1; i <= cnt; i++)
{
u = Edge[i].u;
v = Edge[i].v;
if(u == 1 || v == 1) continue;
if(query(u) != query(v))
{
printf("%d %d\n", u, v);
merge(u, v);
}
}
return 0;
}
题目链接:https://codeforces.com/contest/723/problem/F
给你一个 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \le n \le 2 \cdot 10^5) n(2≤n≤2⋅105) 个点和 m ( 1 ≤ m ≤ min ( 4 ⋅ 1 0 5 , n ( n − 1 ) 2 ) ) m(1 \le m \le \min(4 \cdot 10^5, \frac{n(n-1)}{2})) m(1≤m≤min(4⋅105,2n(n−1))) 条边的无向连通图,保证没有自环和重边。
现在需要你构造一棵生成树,使得 s s s 的度数不超过 d s d_s ds, t t t 的度数不超过 d t d_t dt,或者说明答案不存在。
这类题的思路一般都是将 s s s 和 t t t 暂时抠掉,然后跑连通块。
因为原图连通,所以所有的不连通都是 s s s 或 t t t 造成的。
遍历所有的边,分几种情况:
之后枚举所有的连通块,又分几种情况:
在这个过程中,如果发现 d s d_s ds 或 d t d_t dt 小于 0 0 0,则直接输出无解。
之后再次枚举连通块,这一次就是按照之前规定的方案进行连边(用并查集合并,并将这条边存入答案中)了。
最后,如果发现 s s s 和 t t t 仍然不连通,就需要连接 s s s 和 t t t 了,但如果发现这条边并不存在,则直接输出无解。
这之后,还需要判断是否恰好连接了 n − 1 n-1 n−1 条边,如果不是,输出无解。
否则,输出全部的连边即可。
时间复杂度: O ( n α ( n ) ) O(n \alpha (n)) O(nα(n))。
int n, m, cnt;
int s, t, ds, dt;
int head[maxn], col[maxn], father[maxn], rank_[maxn];
bool vis[2][maxn];
vector<int> stat[maxn];
vector<pii> ans;
int point[2][maxn];
int ist[maxn];
struct edge
{
int v, nxt;
} Edge[4 * maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1, father[i] = i;
cnt = 0;
}
void addedge(int u, int v)
{
Edge[cnt].v = v;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
int findfather(int id)
{
return father[id] == id ? id : findfather(father[id]);
}
void merge(int x, int y)
{
ans.pb(pii(x, y));
x = findfather(x);
y = findfather(y);
if(x != y)
{
if(rank_[x] > rank_[y]) father[y] = x;
else
{
father[x] = y;
if(rank_[x] == rank_[y]) rank_[y]++;
}
}
}
void dfs(int id, int c)
{
col[id] = c;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(v == s || v == t) continue;
if(col[v]) continue;
dfs(v, c);
}
}
int main()
{
int u, v;
scanf("%d%d", &n, &m);
init();
bool mark = false;
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
scanf("%d%d%d%d", &s, &t, &ds, &dt);
int numc = 0;
for(int i = 1; i <= n; i++)
{
if(i == s || i == t) continue;
if(!col[i]) dfs(i, ++numc);
}
for(int i = 0; i < cnt; i += 2)
{
u = Edge[i ^ 1].v;
v = Edge[i].v;
if (u == s && v == t) mark = true;
if (u == t && v == s) mark = true;
if(findfather(u) == findfather(v)) continue;
if((!col[u]) && (!col[v])) continue;
if(col[u] == col[v]) merge(u, v);
else
{
if(!col[v]) swap(u, v);
if(u == s)
{
if(!vis[0][col[v]])
{
stat[col[v]].pb(u);
vis[0][col[v]] = true;
point[0][col[v]] = v;
}
}
else
{
if(!vis[1][col[v]])
{
stat[col[v]].pb(u);
vis[1][col[v]] = true;
point[1][col[v]] = v;
}
}
}
}
for(int i = 1; i <= numc; i++)
{
if(stat[i].size() == 1)
{
int id = stat[i][0];
if(id == s) ds--;
else
{
dt--;
ist[i] = true;
}
}
if(ds < 0 || dt < 0)
{
printf("No\n");
return 0;
}
}
bool iscon = false;
for(int i = 1; i <= numc; i++)
{
if(stat[i].size() == 2)
{
if(!iscon)
{
iscon = true;
ds--;
dt--;
ist[i] = -1;
}
else
{
if(ds > 0) ds--;
else
{
dt--;
ist[i] = true;
}
}
}
if(ds < 0 || dt < 0)
{
printf("No\n");
return 0;
}
}
for(int i = 1; i <= numc; i++)
{
if(ist[i] == 0) merge(s, point[0][i]);
else if(ist[i] == 1) merge(t, point[1][i]);
else
{
merge(s, point[0][i]);
merge(t, point[1][i]);
}
}
if(findfather(s) != findfather(t))
{
if (!mark) return 0 * puts("No");
merge(s, t);
ds--;
dt--;
}
if(ds < 0 || dt < 0)
{
printf("No\n");
return 0;
}
if(ans.size() < n - 1) printf("No\n");
else
{
printf("Yes\n");
for(int i = 0; i < ans.size(); i++)
{
printf("%d %d\n", ans[i].first, ans[i].second);
}
}
return 0;
}
题目链接:https://codeforces.com/problemset/problem/260/D
给出每一个点 i i i 的颜色 c i ( c i ∈ [ 0 , 1 ] ) c_i(c_i \in [0,1]) ci(ci∈[0,1]),现在需要你构造一棵 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2≤n≤105) 个节点的树,并满足以下条件:
题目保证答案一定存在。
首先你要有一个这样的意识:一条权值为 w w w 的边 ( u , v ) (u,v) (u,v) 会将 s u s_u su 和 s v s_v sv 都减去 w w w。
将不同颜色的点放入不同的桶中,对每一个桶按照 s s s 值从大到小进行排序。
之后枚举颜色为 0 0 0 的点 u u u,我们的目的是贪心地用颜色为 1 1 1 的点 v v v 的 s v s_v sv 来凑 u u u 的 s u s_u su。
因此我们用循环之外的 p o s pos pos 记录当前正在访问的颜色为 1 1 1 的点 v v v 在桶 1 1 1 中的位置,然后利用 p o s pos pos 枚举点 v v v,分两种情况:
这波操作完成之后,我们已经达到了题中所给的两个条件,但是整个图可能还是不连通,这时我们就要利用一下权值为 0 0 0 的边了。
这时我们需要利用颜色为 0 0 0 的第一个点与颜色为 1 1 1 的未联通的点连接权值为 0 0 0 的边。同理我们需要利用颜色为 1 1 1 的第一个点与颜色为 0 0 0 的未联通的点连接权值为 0 0 0 的边。这个过程仍然利用并查集维护,并在建边的过程中存储答案。
最终输出连边方案即可。
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。
int n;
vector<pii> ve[2];
bool cmp(pii a, pii b)
{
return a.second > b.second;
}
struct edge
{
int u, v, w;
edge (int _u, int _v, int _w):
u(_u), v(_v), w(_w) {}
};
vector<edge> ans;
bool vis[maxn];
int father[maxn], rank_[maxn];
void init()
{
for(int i = 0; i <= n; i++) father[i] = i;
}
int findfather(int id)
{
return father[id] == id ? id : father[id] = findfather(father[id]);
}
void merge(int a, int b)
{
a = findfather(a);
b = findfather(b);
if(a != b)
{
if(rank_[a] > rank_[b]) father[b] = a;
else
{
father[a] = b;
if(rank_[a] == rank_[b]) rank_[b]++;
}
}
}
int main()
{
int type, w;
scanf("%d", &n);
init();
for(int i = 1; i <= n; i++)
{
scanf("%d%d", &type, &w);
ve[type].pb(pii(i, w));
}
for(int i = 0; i < 2; i++)
{
sort(ve[i].begin(), ve[i].end(), cmp);
}
int pos = 0;
for(int i = 0; i < ve[0].size(); i++)
{
int val = ve[0][i].second;
vis[ve[0][i].first] = true;
while(val)
{
if(ve[1][pos].second <= val)
{
vis[ve[1][pos].first] = true;
val -= ve[1][pos].second;
ans.pb(edge(ve[0][i].first, ve[1][pos].first, ve[1][pos].second));
merge(ve[0][i].first, ve[1][pos].first);
ve[1][pos].second = 0;
pos++;
}
else
{
vis[ve[1][pos].first] = true;
ans.pb(edge(ve[0][i].first, ve[1][pos].first, val));
merge(ve[0][i].first, ve[1][pos].first);
ve[1][pos].second -= val;
val = 0;
}
}
}
for(int i = 0; i < ve[1].size(); i++)
{
if(findfather(ve[0][0].first) != findfather(ve[1][i].first))
{
merge(ve[0][0].first, ve[1][i].first);
ans.pb(edge(ve[0][0].first, ve[1][i].first, 0));
}
}
for(int i = 0; i < ve[0].size(); i++)
{
if(findfather(ve[1][0].first) != findfather(ve[0][i].first))
{
merge(ve[1][0].first, ve[0][i].first);
ans.pb(edge(ve[1][0].first, ve[0][i].first, 0));
}
}
for(int i = 0; i < n - 1; i++)
{
printf("%d %d %d\n", ans[i].u, ans[i].v, ans[i].w);
}
return 0;
}