2026牛客寒假训练营-Day2 JEDC

Problem J ------ 终于再见
- 城市的等级至多有 sqrt(m)sqrt(m)sqrt(m) 种
- 故枚举城市等级后用bfs求距离,总复杂度 O(n∗sqrt(m))O(n*sqrt(m))O(n∗sqrt(m))
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int n,m;
cin >> n >> m;
vector<vector<int>> v(n+1);
vector<int> deg(n+1);
for(int i = 1; i <= m; i ++){
int x,y;
cin >> x >> y;
deg[x] ++,deg[y] ++;
v[x].push_back(y);
v[y].push_back(x);
}
vector<int> dist(n+1,1e18),ans(n+1,1e18);
vector<vector<int>> p(m+1);
vector<int> k;
queue<int> q;
for(int i = 1; i <= n; i ++){
p[deg[i]].push_back(i);
}
for(int i = m; i >= 1; i --){
if(p[i].size() == 0) continue;
fill(dist.begin(),dist.end(),1e18);
k.insert(k.end(),p[i].begin(),p[i].end());
queue<int> q;
for(int j : k){
dist[j] = 0;
q.push(j);
}
while(q.size()){
int t = q.front();
q.pop();
for(int j : v[t]){
if(dist[j] > dist[t] + 1){
dist[j] = dist[t] + 1;
q.push(j);
}
}
}
for(int j = 1; j <= n; j ++){
if(deg[j] < i){
ans[j] = min(ans[j],dist[j]);
}
}
}
for(int i = 1; i <= n; i ++){
if(ans[i] < 1e17){
cout << ans[i] << ' ';
}else{
cout << -1 << ' ';
}
}
cout << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
//cin >> t;
while(t --){
solve();
}
}
Problem E ------ 01矩阵
-
一种做法是直接构造,可行的矩阵类似下图
00
01000
011
0100000
0111
0100
010100000
01111
01000
01011
01010
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cout << "10"[min(i, j) & 1];
}
cout << endl;
}
}
-
另一种做法是找到一个大小为 nnn 的基矩阵后,进行拓展,拓展方法有两种。
- 在基矩阵的最右边添加一列全为1的列,然后再最下方添加全0的行
- 在基矩阵的最下方添加一列全为1的行,然后再最右边添加全0的列
-
拓展的例子如下:
n = 6
101111
000001
101011
000011
100011
000000n = 7
1011110
0000010
1010110
0000110
1000110
0000000
1111110n = 8
10111101
00000101
10101101
00001101
10001101
00000001
11111101
00000000n = 9
101111010
000001010
101011010
000011010
100011010
000000010
111111010
000000000
111111110
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[6][6] = {
{1,0,1,1,1,1},
{0,0,0,0,0,1},
{1,0,1,0,1,1},
{0,0,0,0,1,1},
{1,0,0,0,1,1},
{0,0,0,0,0,0},
};
void solve(){
int n;
cin >> n;
if(n == 1){
cout << 0 << '\n';
}else if(n == 2){
cout << "01\n00\n";
}else if(n == 3){
cout << "000\n101\n001\n";
}else if(n == 4){
cout << "0001\n0000\n0011\n1011\n";
}else if(n == 5){
cout << "10000\n10010\n00000\n11010\n11011\n";
}else{
vector<vector<int>> ans(n+1,vector<int>(n+1));
for(int i = 1; i <= 6; i ++)
for(int j = 1; j <= 6; j ++){
ans[i][j] = a[i-1][j-1];
}
bool clk = 1;
for(int i = 7; i <= n; i ++){
if(clk){
for(int j = 1; j <= i - 1; j ++){
ans[i][j] = 1;
}
for(int j = 1; j <= i;j ++){
ans[j][i] = 0;
}
}else{
for(int j = 1; j <= i; j ++){
ans[i][j] = 0;
}
for(int j = 1; j <= i - 1;j ++){
ans[j][i] = 1;
}
}
clk = !clk;
}
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
cout << ans[i][j];
}
cout << '\n';
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
//cin >> t;
while(t --){
solve();
}
}
Problem D ------ 数字积木
-
本题的粗略思路是:
- 求 mex = k 的连通子图个数。
- 枚举 mex=1,2,3...nmex = 1,2,3...nmex=1,2,3...n , 按步骤 111 的方法求出子图数,并求和。
- 对于步骤1,解决方法是先删掉点k,然后求出"最小连通骨架",即求找到一个包含点 111 ~ 点 k−1k-1k−1所有点的最小连通子图,从该子图中中删除任何一个点,mex 都不再是 k。
- 求"最小连通骨架"的方法是,固定一个始终在连通骨架中的点(即点1)为根,新加入一个点到骨架中时,从新加入的点开始,往根移动,路上经过的所有点都要被加入连通骨架。
- 那么骨架中的点必须在最后的联通子图中,非骨架中的点可有可无,按树形dp统计非骨架中的点,有多少种可能的形状即可。
- 但是这个做法是 O(n2)O(n^2)O(n2)的,需要降复杂度。
- 降复杂度的方法是利用 单调性。
-
本题的最终思路:
- 从1~n枚举mex时,"连通骨架"的大小有只会随着点的加入越来越大,具有 单调性
- 新加入的点,会导致部分的点从"非骨架"中,被纳入"连通骨架"从而使得子图个数数减少。
- 重点关注本轮新纳入的点,除掉它们对答案的贡献,即可通过上一轮的答案得到本轮的答案
- 总复杂度 O(n)O(n)O(n)
-
注意处理 0的逆元 ,方法是单独开数组记录 0 因子个数,子树有 0 因子时,不计算贡献。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int qmi(int a,int b){
int ret = 1;
while(b){
if(b & 1) ret = (1ll * ret * a) % mod;
b >>= 1;
a = (1ll * a * a) % mod;
}
return ret;
}
void solve(){
int n;
cin >> n;
vector<int> a(n+1),ap(n+1);
for(int i = 1; i <= n; i ++){
cin >> a[i];
ap[a[i]] = i;
}
vector<vector<int>> v(n+1);
for(int i = 1; i <= n - 1; i ++){
int x,y;
cin >> x >> y;
v[x].push_back(y);
v[y].push_back(x);
}
vector<int> dp(n+1),fa(n+1),zero(n+1);
int root = ap[0];
auto dfs = [&](auto &&self,int u,int p)->void{
fa[u] = p;
dp[u] = 1;
zero[u] = 0;
for(int i : v[u]){
if(i != p){
self(self,i,u);
if((dp[i] + 1) % mod == 0){
zero[u] ++;
}else if(zero[i] == 0){
dp[u] *= (dp[i] + 1);
dp[u] %= mod;
}
}
}
return;
};
dfs(dfs,root,-1);
vector<int> done(n+1),g(n+2);
done[ap[0]] = 1;
int cnt = dp[root] % mod;
int sum0 = zero[root];
for(int i = 1; i <= n - 1; i ++){
if(sum0 == 0) g[i] = cnt;
else g[i] = 0;
if(done[ap[i]]) continue;
int p = ap[i];
while(!done[p]){
done[p] = 1;
if((dp[p] + 1) % mod == 0){
sum0 --;
}else if(zero[p] == 0){
cnt = (cnt * qmi(((dp[p] + 1) % mod),mod-2)) % mod;
}
sum0 += zero[p];
cnt = (cnt * dp[p]) % mod;
p = fa[p];
}
}
if(sum0 == 0) g[n] = cnt;
else g[n] = 0;
int ret = 0;
for(int i = 1; i <= n; i ++){
ret += (g[i] - g[i+1] + mod) % mod * i;
ret %= mod;
}
cout << ret << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
//cin >> t;
while(t --){
solve();
}
}
Problem C ------ 炮火轰炸
- 重点关注和炮火点相邻的点,避免复杂度超限。
- 本题的做法是,把没有形成包围圈的无害炮火点,标记出来(实际上标记的无害炮火点周围的点,但不重要)。
- 如何标记无害炮火点周围的点?通过DFS实现,如果能从一个安全点走到该点,那么该点也是安全的。那么只需要找到第一个安全的点,再DFS即可。
- 如何找到第一个安全的点?假设全局最右下角的一个炮火点坐标为 (i,j)(i,j)(i,j) , 那么点 (i+1,j+1)(i+1,j+1)(i+1,j+1) 肯定是安全的。
- 单纯的DFS是超时的,如何优化? 遍历时,只关心与炮火点相邻的点。我们需要标记无害炮火点,如果一个点不和炮火点相邻,那么他不具备标记作用,不走到该点。
- 对于一个询问,如果能走直线走到无穷远,或者能走到被标记过的无害的炮火点(周围),那么答案是"NO",否则就走到了有害炮火点,答案是是"YES"。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
vector<int> dx = {-1,-1,-1,0,0,1,1,1},dy = {-1,0,1,-1,1,-1,0,1};
vector<int> dx2 = {1,-1,0,0},dy2 = {0,0,1,-1};
void solve(){
int n,q;
cin >> n >> q;
vector<PII> a(n+1);
vector<map<int,int>> mp1(1e6+1,map<int,int>()),mp2(1e6+1,map<int,int>());
set<pair<int,int>> boom;
auto check = [&](int x,int y) -> bool{
auto p1 = mp1[x].lower_bound(y),p2= mp2[y].lower_bound(x);
if(p1 == mp1[x].end() || p2 == mp2[y].end()) return true;
if(p1->second == 0 && p2 -> second == 0) return false;
else return true;
};
auto dfs = [&](auto &&self,int x,int y)->void{
if(mp1[x].find(y) != mp1[x].end()) return;
mp1[x][y] = 1;
mp2[y][x] = 1;
for(int i = 0; i < 4; i ++){
int nx = x + dx2[i],ny = y + dy2[i];
for(int j = 0; j < 8; j ++){
int nnx = nx + dx[j],nny = ny + dy[j];
if(boom.find({nnx,nny}) != boom.end()){
self(self,nx,ny);
break;
}
}
}
};
for(int i = 1; i <= n; i++){
cin >> a[i].first >> a[i].second;
mp1[a[i].first][a[i].second] = 0;
mp2[a[i].second][a[i].first] = 0;
boom.insert(a[i]);
}
sort(a.begin()+1,a.end(),greater<PII>());
for(int i = 1; i <= n; i ++){
//cout << i << endl;
auto [x,y] = a[i];
if(check(x+1,y+1)) dfs(dfs,x+1,y+1);
}
while(q --){
int x,y;
cin >> x >> y;
if(check(x,y)){
cout << "NO\n";
}else{
cout << "YES\n";
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
//cin >> t;
while(t --){
solve();
}
}