构造题练习
P12041 [USTCPC 2025] 图上交互题 2 / Constructive Minimum Mex Path
显然 \(f(u,v)\) 只有 \(0\) 和 \(1\) 两种取值。
当 \(f(u,v)=1\) 时,显然有 \(w(u,v)=0\),且 \(u\to v\) 的任意路径上,都存在边权为 \(0\) 的边。记删掉所有边权为 \(0\) 的边后新图为 \(G\),则 \(G(u,v)\) 不连通。
当 \(f(u,v)=0\) 时, 即 \(u\to v\) 的任意路径上,存在至少一条路径满足不存在边权为 \(0\) 的边,此时 \(G(u,v)\) 连通,另 \(w(u,v)=1\) 不会使图 \(G\) 的连通性更优。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int kN = 1e5 + 7;
int n, m;
bool vis[kN];
struct node{
int u, v, f, w;
}e[kN];
struct DSU{
int fa[kN], siz[kN];
void init(){
for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;
}
int find(int x){
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
void merge(int x, int y){
x = find(x), y = find(y);
if(x ^ y){
if(siz[x] > siz[y]) swap(x, y);
fa[x] = y;
siz[y] += siz[x];
}
}
}f;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
f.init();
bool fg = 1;
for(int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v >> e[i].f;
if(e[i].f > 1) fg = 0;
e[i].w = e[i].f ^ 1;
if(e[i].w) f.merge(e[i].u, e[i].v);
}
if(!fg){
cout << "NO\n"; return 0;
}
for(int i = 1; i <= m; i++){
if(e[i].f == 1){
if(f.find(e[i].u) == f.find(e[i].v)){
cout << "NO\n"; return 0;
}
}
}
cout << "YES\n";
for(int i = 1; i <= m; i++) cout << e[i].w << ' ';
cout << '\n';
return 0;
}
P12042 [USTCPC 2025] 图上交互题 3 / Constructive Maximum Mex Path
由于可以经过重复的边,所以同一个连通块中任意两点的 \(u,v\) 的函数值 \(f(u,v)\) 应该相等。
假设 \(f(u,v)=k\),那这个连通块中要出现边权为 \([0,k)\) 且不能出现边权为 \(k\) 的边。于是我们随便赋值即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int kN = 1e5 + 7;
int n, m, a[kN];
struct edge{
int u, v, f, w;
}e[kN];
vector<int> pev[kN];
struct DSU{
int fa[kN], siz[kN];
void init(){
for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;
}
int find(int x){
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
void merge(int x, int y){
x = find(x), y = find(y);
if(x ^ y){
if(siz[x] > siz[y]) swap(x, y);
fa[x] = y, siz[y] += siz[x];
}
}
}f;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
f.init();
for(int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v >> e[i].f;
f.merge(e[i].u, e[i].v);
}
for(int i = 1; i <= n; i++) a[i] = -1;
for(int i = 1; i <= m; i++){
if(a[f.find(e[i].u)] == -1) a[f.find(e[i].u)] = e[i].f;
else{
if(a[f.find(e[i].u)] != e[i].f){
cout << "NO\n"; return 0;
}
}
pev[f.find(e[i].u)].push_back(i);
}
for(int i = 1; i <= n; i++){
if(f.find(i) != i) continue;
if(a[i] > (int)pev[i].size()){
cout << "NO\n"; return 0;
}
int v = 0;
for(int j : pev[i]){
if(a[i]) e[j].w = v, v = (v + 1) % a[i];
else e[j].w = 1;
}
}
cout << "Yes\n";
for(int i = 1; i <= m; i++) cout << e[i].w << ' ';
cout << '\n';
return 0;
}
P12043 [USTCPC 2025] 图上交互题4 / Constructive Shortest Path
结论:\(w(i,j)=f(i,j)\)
证明:考虑图 \(G'=(u_i,v_i,f(u_i,v_i))\)。
因为 \(G'_{x,y}=f(x,y)\),所以 \(dis_{G'}(x,y)\le G'(x,y)=f(x,y)\)。
由三角形不等式得:\(dis(x,z)+dis(z,y)\ge dis(x,y)\)。而 \(G'\) 中任意两点的距离都为原图中两点的最短距离,所以 \(G'\) 中任意一条路径 \(x\to \dots \to y\) 的长度大于等于原图中两点 \((x,y)\) 的最短距离,即 \(dis_{G'}(x,y)\ge dis_G(x,y)=f(x,y)\)
所以 \(dis_{G'}(x,y)=f(x,y)\)。
这是一个经典结论,可以记下来。
经典结论
对于无负环的有向图或无向图,令新图为原图将边的权值改为两点之间的最短距离(有向图注意方向),新图中任意两点的最短距离等于原图中同样两点的最短距离。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int kN = 510;
const int kM = 1e5 + 7;
int n, m, d[kN][kN], f[kN][kN];
struct edge{
int u, v, f;
}e[kM];
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) d[i][j] = f[i][j] = 1e9;
for(int i = 1; i <= n; i++) f[i][i] = 0;
for(int i = 1, u, v, w; i <= m; i++){
cin >> u >> v >> w;
if(u == v && w){
cout << "NO\n"; return 0;
}
e[i] = {u, v, w};
d[u][v] = d[v][u] = f[u][v] = f[v][u] = min(f[u][v], w);
}
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
if(i != k){
for(int j = 1; j <= n; j++){
if(i != j && k != j) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
for(int i = 1; i <= m; i++){
if(e[i].f != d[e[i].u][e[i].v]){
cout << "NO\n"; return 0;
}
}
cout << "YES\n";
for(int i = 1; i <= m; i++) cout << e[i].f << ' ';
cout << '\n';
return 0;
}
从部分分 \(k=10^9\) 入手,此时从原图中随便拉出一棵以 \(1\) 为根的外向树,树边边权为 \(1\),非树边边权为 \(k\)。
考虑将上述做法拓展到一般情况,我们依然要从原图中拉出一棵以 \(1\) 为根的外向树,树边边权为 \(1\),非树边边权为 \(k\)。但是我们能随便拉出一棵树吗?
显然是不能的。
Hack
.in
4 5 2
1 2
1 3
2 4
3 4
1 4
.out
Yes
1 1 2 2 1
发现错因在 \(k\) 不够大,于是我们可以拉出一棵 \(bfs\) 生成树,在跑上面的做法就可以了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define mp(a, b) make_pair(a, b)
const int kN = 3e5 + 7;
int n, m, k, w[kN], vis[kN], dis[kN];
struct edge{
int u, v, w;
}e[kN];
vector<pii> ve[kN];
void Pre(int x){
queue<int> que;
que.push(x);
while(!que.empty()){
int u = que.front();
que.pop();
vis[u] = 1;
for(auto j : ve[u]){
if(vis[j.first]) continue;
vis[j.first] = 1;
w[j.second] = 1;
que.push(j.first);
}
}
}
priority_queue<pii, vector<pii>, greater<pii> > que;
bool bfs(int x){
for(int i = 1; i <= n; i++) dis[i] = 2e9, vis[i] = 0;
while(!que.empty()) que.pop();
que.push(mp(0, x));
while(!que.empty()){
pii u = que.top();
que.pop();
if(vis[u.second]){
if(u.first == dis[u.second]) return false;
continue;
}
vis[u.second] = 1;
dis[u.second] = u.first;
for(auto j : ve[u.second]){
que.push(mp(dis[u.second] + w[j.second], j.first));
}
}
return true;
}
void solve(){
cin >> n >> m >> k;
for(int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v;
ve[e[i].u].push_back(mp(e[i].v, i));
w[i] = k;
}
Pre(1);
if(bfs(1)){
cout << "Yes\n";
for(int i = 1; i <= m; i++) cout << w[i] << ' ';
cout << '\n';
}
else cout << "No\n";
for(int i = 1; i <= n; i++) ve[i].clear(), vis[i] = 0;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1; cin >> T;
while(T--) solve();
return 0;
}
当 \(n=1\) 时,若 \(m\ne 1\),无解;否则,简单。
当 \(2\mid n\) 时,则正序降序接替放。
当 \(2\nmid n\) 时,考虑每个元素在序列中的位置和为 \(\frac {(m+1)n}{2}\)。
-
当 \(2\mid m\) 时,\(\frac {(m+1)n}{2}\) 不为整数,肯定无解。
-
当 \(2\nmid m\) 时
-
当 \(n = 3\) 时,第一排正序放。
这时我们 注意 到第 \(i\) 个数与第 \(i+1\) 个数的位置和之差为 \(1\),第 \(i\) 个数与第 \(i+2\) 个数的位置和之差为 \(2\)。
如何弥补这个 \(1\) 和 \(2\) 呢?
关键来了,我们 注意 到 \(i\) 与 \(i+1\) 的奇偶性不同,\(i\) 与 \(i+2\) 的奇偶性相同。考虑将奇数看作一组,偶数看作一组。
为了弥补 \(2\),我们 注意 到可以采取的一种方法是剩余两排都将奇数组和偶数组从大到小排放在连续的位置。
我们 注意 到奇数组的数的个数比偶数组的数的个数多 \(1\),奇数组中最大的数为偶数组中最大的数加 \(1\)。
这时候,我们 又惊奇地注意到 剩余两排中一排奇数组放在前面,另一排偶数组放在前面可以完美符合要求。
-
当 \(n > 3\) 时,都可以通过正序降序接替放归纳到 \(n=3\) 的情况。
-
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
void solve(){
cin >> n >> m;
if(n == 1){
if(m == 1) cout << 1 << '\n';
else cout << -1 << '\n';
return ;
}
if(m == 1){
for(int i = 1; i <= n; i++) cout << 1 << '\n';
return ;
}
if(n % 2 == 0){
for(int i = 1; i <= n; i += 2){
for(int j = 1; j <= m; j++) cout << j << ' ';
cout << '\n';
for(int j = m; j >= 1; j--) cout << j << ' ';
cout << '\n';
}
return ;
}
if(m % 2 == 0){
cout << -1 << '\n';
return ;
}
while(n > 3){
for(int j = 1; j <= m; j++) cout << j << ' ';
cout << '\n';
for(int j = m; j >= 1; j--) cout << j << ' ';
cout << '\n';
n -= 2;
}
for(int i = 1; i <= m; i++) cout << i << ' ';
cout << '\n';
for(int i = m; i >= 1; i -= 2) cout << i << ' ';
for(int i = m - 1; i >= 2; i -= 2) cout << i << ' ';
cout << '\n';
for(int i = m - 1; i >= 2; i -= 2) cout << i << ' ';
for(int i = m; i >= 1; i -= 2) cout << i << ' ';
cout << '\n';
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1; cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号