图论做题记录1
图论做题记录
前言:大概是记录本人打比赛或者做题碰到的图论的部分题。
所有题目均已省以下宏:
// QwQ
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
#define fi first
#define se second
typedef pair<int,int> PII;
typedef long long LL;
typedef unsigned long long ull;
template <typename T> void inline read(T& x)
{
x=0; int f=1; char ch;
while((ch=getchar())>'9' || ch<'0') if(ch=='-') f=-1;
while(ch>='0' && ch<='9') x=x*10+(ch^'0'),ch=getchar();
x*=f;
}
T1
ABC277E
题意:
给定一个
分析:
考虑很难直接维护边权状态,考虑分层图。
具体的,对于初始值为
答案在
for(int i = 1;i <= m;i++)
{
int u,v,kkk; read(u); read(v); read(kkk);
if(kkk) adde(u,v,1),adde(v,u,1);
else adde(u+n,v+n,1),adde(v+n,u+n,1);
}
for(int i = 1;i <= t;i++)
{
int x; read(x);
adde(x,x+n,0); adde(x+n,x,0);
}
T2
CF1715D
题意:
给定
分析:
直接维护
对于一个限制
然后这样做可能会导致最后得到的序列不满足条件。
可以考虑建图,把
然后枚举所有二进制位
- 边权
的第 位为 ,则当前这一位一定不能为 。 - 设
为另一个顶点。 - 若
,则另一个顶点一定已经被搜过了,如果 的第 为 ,那么 的第 位一定要设为 才能满足条件。 - 否则,
,如果 为 ,说明 不能填 ,但为了满足条件, 的第 位必须为 。
然后特判一下
int n,q,a[N],g[N][31];
bool dfs(int u,int j)
{
for(int i = head[u];~i;i=e[i].nxt)
{
int v = e[i].to;
if(!(e[i].w>>j&1)) continue;
if(u < v && g[v][j]) return 1;
else if(u >= v && !(a[v]>>j&1)) return 1;
}
return 0;
}
int main()
{
read(n); read(q); memset(head,-1,sizeof(head));
for(int i = 1;i <= q;i++)
{
int x,y,z; read(x); read(y); read(z);
for(int j = 0;j <= 30;j++) g[x][j] |= !(z>>j&1),g[y][j] |= !(z>>j&1);
if(x == y) a[x] = z;
else adde(x,y,z),adde(y,x,z);
}
for(int j = 0;j <= 30;j++)
for(int i = 1;i <= n;i++)
if(dfs(i,j))
a[i] |= (1<<j);
for(int i = 1;i <= n;i++) printf("%d%c",a[i]," \n"[i == n]);
return (0-0); // QwQ
}
T3
CF1715E
题意:
有
现在你最多可以坐
分析:
这题最短路是少不了的,难处理的是航班。但是可以列出方程:
我们可以拆一下式子:
移项:
然后发现这个东西跟斜率优化长得很像,可以设
然后就可以跑
需要注意的几个点:
- 本题斜率优化与普通不同,没有
的限制,因此需要把所有的 加入队列中,再转移。 - 在第
遍斜率优化时,需要用第 次得到的 数组去转移。
const int N = 1e5+5;
struct graph
{
int to,nxt,w;
graph(int tt,int nn,int ww)
{
to=tt;nxt=nn;w=ww;
}
graph(){
}
}e[N<<1];
int head[N],idx;
void adde(int u,int v,int w) {e[idx]=graph(v,head[u],w);head[u]=idx++;}
struct pos
{
int x; LL v;
friend bool operator < (pos a,pos b)
{
return a.v > b.v;
}
};
priority_queue <pos> q;
int n,m,k;
bool vis[N];
LL dis[N],qq[N<<1],f[N];
LL Y(int i) {return f[i]+1LL*i*i;}
LL K(int i) {return 2LL*i;}
LL B(int i) {return f[i]-1LL*i*i;}
LL get(int i,int j) {return 1LL*(i-j)*(i-j);}
double slope(int i,int j)
{
if(i == j) return 1e8;
return 1.0*(Y(i)-Y(j))/(i-j);
}
void dij()
{
for(int i = 1;i <= n;i++) vis[i] = 0;
while(!q.empty())
{
pos tmp = q.top(); q.pop();
if(vis[tmp.x]) continue;
vis[tmp.x] = 1;
for(int i = head[tmp.x];~i;i = e[i].nxt)
{
int j = e[i].to;
if(dis[j] > dis[tmp.x]+e[i].w)
{
dis[j] = dis[tmp.x]+e[i].w;
q.push(pos{j,dis[j]});
}
}
}
while(!q.empty()) q.pop();
}
int main()
{
read(n); read(m); read(k);
memset(head,-1,sizeof(head));
for(int i = 1;i <= m;i++)
{
int u,v,w; read(u); read(v); read(w);
adde(u,v,w); adde(v,u,w);
}
memset(dis,0x3f,sizeof(dis));
dis[1] = 0; q.push(pos{1,0LL});
dij();
while(k--)
{
int hh = 1,tt = 0;
qq[++tt] = 0;
for(int i = 1;i <= n;i++) f[i] = dis[i];
for(int i = 1;i <= n;i++)
{
while(hh < tt && slope(qq[tt-1],qq[tt]) >= slope(qq[tt-1],i)) --tt;
qq[++tt] = i;
}
for(int i = 1;i <= n;i++)
{
while(hh < tt && slope(qq[hh],qq[hh+1]) <= K(i)) ++hh;
int vv = qq[hh];
if(dis[i] > f[vv]+get(i,vv))
{
dis[i] = f[vv]+get(i,vv);
q.push(pos{i,dis[i]});
}
}
dij();
}
for(int i = 1;i <= n;i++) printf("%lld%c",dis[i]," \n"[i == n]);
return (0-0); // QwQ
}
T4
CF1844E
题意:
有一个
分析:
由题目天然约束条件,可以得到在任意
在
首先,我们定义一个网格中的数变成相邻网格中的数所要改变的数值为贡献。
1.大概类似这样
1 | 2 |
---|---|
0 | 1 |
容易发现横着
2.大概类似这样
2 | 1 |
---|---|
1 | 0 |
容易发现横竖都是
而行列贡献只能为
方法不止一种,还可以用带权并查集或者并查集扩展域,当然
我写的是带权并查集。
int n,m,k,fa[N],d[N];
int find(int x) // 这里还得继承
{
if(x == fa[x]) return x;
int rt = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = rt;
}
bool merge(int v,int x,int y)
{
int r1 = find(x),r2 = find(y);
if(r1 != r2)
{
d[r1] = d[y]+v-d[x];
fa[r1] = r2;
return 1;
}
return ((d[y]+v-d[x])%2 == 0); // 二分图
}
void solve()
{
read(n); read(m); read(k);
for(int i = 1;i <= n+m;i++) fa[i] = i,d[i] = 0;
int f = 1;
for(int i = 1;i <= k;i++)
{
int a1,b1,a2,b2; // /不等 \相等
read(a1); read(b1); read(a2); read(b2);
if(b1 < b2) f &= merge(1,a1,b1+n-1);
else f &= merge(0,a1,b2+n-1);
}
puts(f?"Yes":"No");
}
T5
Luogu P6833
题意:
给你一个
分析:
此题有个错误解法:先求出起点到所有点的最短路,记录前驱,再便利求点权。
因为起点到两点总距离确定,但重复的路径一定是最短路,而我们要让路径总和最短,应该是最大化这个重复的路段(减的更多),所以做法错误。
可以枚举分叉点,然后对起点和两个终点分别跑一次最短路,得到三个
这样做复杂度是
由于是网格图,此题可以不用建图。
int main()
{
memset(head,-1,sizeof(head));
read(n); read(m); read(a); read(b); read(c);
for(int i = n;i;i--)
for(int j = 1;j <= m;j++)
read(s[i][j]);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
vv[exc(i,j)] = s[i][j];
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
for(int k = 0;k < 4;k++)
{
int x = i+nxt[k][0],y = j+nxt[k][1];
if(x < 1 || x > n || y < 1 || y > m) continue;
adde(exc(i,j),exc(x,y),s[x][y]);
}
dij(exc(n,a),dis1);
dij(exc(1,b),dis2);
dij(exc(1,c),dis3);
LL ret = 2e16;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
ret = min(ret,dis1[exc(i,j)]+dis2[exc(i,j)]+dis3[exc(i,j)]-2*s[i][j]);
printf("%lld\n",ret);
return (0-0); // QwQ
}
T6
Luogu P7515[省选联考 2021 A 卷] 矩阵游戏
题意:
有一个
分析:
如果
要使
但是,当
倾定行为奇数的,就把
然后发现,矩阵每一项都是两项差的形式了,这样就可以跑差分约束了。
输出的时候再判一下哪些修改了就行了。
const int N = 305;
vector <PII> g[N<<1];
int n,m,b[N][N],vis[N<<1],cnt[N<<1];
LL dis[N<<1],a[N][N];
void adde(int u,int v,int c){g[u].push_back({v,c});}
bool spfa()
{
queue <int> q;
while(!q.empty()) q.pop();
for(int i = 1;i <= n+m;i++) dis[i] = 2e16,vis[i] = cnt[i] = 0;
dis[1] = 0,vis[1] = 1;
q.push(1);
while(!q.empty())
{
int u = q.front(); q.pop();
vis[u] = 0;
for(auto x:g[u])
{
int j = x.fi,c = x.se;
if(dis[j] > dis[u]+c)
{
dis[j] = dis[u]+c;
cnt[j] = cnt[u]+1;
if(cnt[j] >= n+m+1) return 1;
if(!vis[j])
{
vis[j] = 1;
q.push(j);
}
}
}
}
return 0;
}
void solve()
{
read(n); read(m);
for(int i = 1;i <= n+m;i++) g[i].clear();
for(int i = 2;i <= n;i++)
for(int j = 2;j <= m;j++)
read(b[i][j]);
for(int i = 1;i <= n;i++) a[i][1] = 0;
for(int i = 1;i <= m;i++) a[1][i] = 0;
for(int i = 2;i <= n;i++)
for(int j = 2;j <= m;j++)
a[i][j] = b[i][j]-a[i-1][j-1]-a[i-1][j]-a[i][j-1];
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
{
int l = -a[i][j],r = 1e6-a[i][j];
if((i+j)&1) adde(i,j+n,r),adde(j+n,i,-l);
else adde(j+n,i,r),adde(i,j+n,-l);
}
if(spfa()) puts("NO");
else
{
puts("YES");
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
{
if((i+j)&1) a[i][j] -= dis[i];
else a[i][j] += dis[i];
if((i+j)&1) a[i][j] += dis[j+n];
else a[i][j] -= dis[j+n];
}
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
printf("%lld%c",a[i][j]," \n"[j == m]);
}
}
T7
Luogu P2474 [SCOI2008] 天平
题意:
有 +
表示砝码 -
表示砝码 =
表示砝码 ?
表示两者关系未知。
现在把两个砝码
分析:
我们考虑把重量相同的砝码缩点,把重量轻的点向重量重的点连边,那么建出来的图回事一个最长链为
然后考虑枚举放在右边的砝码
通过枚举我们知道了一些砝码的重量,对于其他的砝码,容易知道,当一个点的入度为
时间复杂度
const int N = 55;
int n,fa[N],rd[N],cd[N],st[3],ans[3],cl[N];
char op[N][N];
vector <int> g[N];
int find(int x){return fa[x] = (x == fa[x]?x:find(fa[x]));}
bool merge(int x,int y)
{
int r1 = find(x),r2 = find(y);
if(r1 == r2) return 0;
fa[r1] = r2;
return 1;
}
int calc(int a,int b)
{
if(a == b) return 1;
if(a > b) return 0;
return 2;
}
bool check()
{
for(int i = 1;i <= n;i++)
if(cl[i])
{
if(!cl[find(i)]) cl[find(i)] = cl[i];
else if(cl[find(i)] != cl[i]) return 0;
}
for(int i = 1;i <= n;i++)
if(find(i) == i && !cl[i])
{
if(!rd[i]) cl[i] = 1;
else if(!cd[i]) cl[i] = 3;
else cl[i] = 2;
}
for(int i = 1;i <= n;i++) for(auto x:g[i]) if(cl[i] >= cl[x]) return 0;
return 1;
}
int main()
{
int x,y; read(n); read(x); read(y);
for(int i = 1;i <= n;i++) scanf("%s",op[i]+1);
for(int i = 1;i <= n;i++) fa[i] = i;
for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) if(op[i][j] == '=') merge(i,j);
for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) if(op[i][j] == '+') g[find(j)].push_back(find(i)),rd[find(i)]++,cd[find(j)]++;
for(int i = 1;i <= n;i++)
for(int j = i+1;j <= n;j++)
{
if(i == x || i == y || j == x || j == y) continue;
for(int k = 0;k < 3;k++) st[k] = 0;
for(int a = 1;a <= 3;a++)
for(int b = 1;b <= 3;b++)
for(int c = 1;c <= 3;c++)
for(int d = 1;d <= 3;d++)
{
for(int k = 1;k <= n;k++) cl[k] = 0;
cl[x] = a,cl[y] = b,cl[i] = c,cl[j] = d;
st[calc(a+b,c+d)] |= check();
}
if(st[0]+st[1]+st[2] == 1) for(int k = 0;k < 3;k++) ans[k] += st[k];
}
for(int i = 0;i < 3;i++) printf("%d%c",ans[i]," \n"[i == 2]);
return (0-0); // QwQ
}
T8
Luogu P1646 [国家集训队] happiness
题意:
有一个
三倍经验:P4313,P1361
分析:
非常经典的最小割题目,答案是奖励和减去最小割。
一个点只能选黑白一种颜色,那么就相当于二选一,我们建
对于同选黑点的每个奖励,我们新建一个点
同选白点同理,连向
memset(head,-1,sizeof(head));
read(n); read(m);
S = 0,T = 5*n*m+1;
int ret = 0;
for(int i = 1;i <= n;i++) for(int j = 1;j <= m;j++) read(x),adde(S,exc(i,j),x),ret += x;
for(int i = 1;i <= n;i++) for(int j = 1;j <= m;j++) read(x),adde(exc(i,j),T,x),ret += x;
for(int i = 1;i < n;i++) for(int j = 1;j <= m;j++) read(x),adde(S,exc(i,j)+1*n*m,x),adde(exc(i,j)+1*n*m,exc(i,j),1e7),adde(exc(i,j)+1*n*m,exc(i+1,j),1e7),ret += x;
for(int i = 1;i < n;i++) for(int j = 1;j <= m;j++) read(x),adde(exc(i,j)+2*n*m,T,x),adde(exc(i,j),exc(i,j)+2*n*m,1e7),adde(exc(i+1,j),exc(i,j)+2*n*m,1e7),ret += x;
for(int i = 1;i <= n;i++) for(int j = 1;j < m;j++) read(x),adde(S,exc(i,j)+3*n*m,x),adde(exc(i,j)+3*n*m,exc(i,j),1e7),adde(exc(i,j)+3*n*m,exc(i,j+1),1e7),ret += x;
for(int i = 1;i <= n;i++) for(int j = 1;j < m;j++) read(x),adde(exc(i,j)+4*n*m,T,x),adde(exc(i,j),exc(i,j)+4*n*m,1e7),adde(exc(i,j+1),exc(i,j)+4*n*m,1e7),ret += x;
printf("%d\n",ret-dinic());
T9
Luogu P4001 [ICPC-Beijing 2006] 狼抓兔子
题意:
给定一个
一条边需要消耗代价才能被删除,求使这张图不连通的最小代价。
分析:
考虑把边串起来,然后跑最小割(因为我不知道咋分析),注意建n双向边。
memset(head,-1,sizeof(head));
read(n); read(m);
S = exc(1,1),T = exc(n,m);
for(int i = 1;i <= n;i++) for(int j = 1;j < m;j++) read(x),adde(exc(i,j),exc(i,j+1),x);
for(int i = 1;i < n;i++) for(int j = 1;j <= m;j++) read(x),adde(exc(i,j),exc(i+1,j),x);
for(int i = 1;i < n;i++) for(int j = 1;j < m;j++) read(x),adde(exc(i,j),exc(i+1,j+1),x);
printf("%lld\n",dinic());
当然也可以平面图转对偶图跑最短路,具体的,边把这个网格分割为若干平面,我们可以把这些平面看做点,连接平面的边看作边,跑最短路即可。 `
void build()
{
S = 0,T = (n-1)*(m-1)*2+1;
for(int i = 1;i < n;i++)
for(int j = 1;j < m;j++)
{
adde(exc(i,j,0),exc(i,j,1),c[i][j]);
if(i == 1) adde(exc(i,j,1),T,a[i][j]);
else adde(exc(i,j,1),exc(i-1,j,0),a[i][j]);
if(i == n-1) adde(S,exc(i,j,0),a[i+1][j]);
if(j == 1) adde(S,exc(i,j,0),b[i][j]);
if(j == m-1) adde(exc(i,j,1),T,b[i][j+1]);
else adde(exc(i,j,1),exc(i,j+1,0),b[i][j+1]);
}
}
int main()
{
memset(head,-1,sizeof(head));
read(n); read(m);
for(int i = 1;i <= n;i++) for(int j = 1;j < m;j++) read(a[i][j]);
for(int i = 1;i < n;i++) for(int j = 1;j <= m;j++) read(b[i][j]);
for(int i = 1;i < n;i++) for(int j = 1;j < m;j++) read(c[i][j]);
build();
dij();
printf("%lld\n",dis[T]);
return (0-0); // QwQ
}
T10
CF1900E
题意:
有一个
对他进行操作:若存在三元组
当上述操作完成后,请计算图中最长的 不经过相同的点 的路径长度,并计算这些最长路径中,路径上的点权值和 最小 是多少。
分析:
如果有一个三元组
因此题目中的三元组加边后,走这些新加的边的答案一定不会比走原图的边优,因此这些操作完全可以不执行。
因此这题就是一个经典的,缩点,跑拓扑排序的题目了。
const int N = 2e5+5;
struct graph
{
int to,nxt;
graph(int tt,int nn)
{
to = tt;nxt = nn;
}
graph(){
}
}e[N];
int head[N],idx;
void adde(int u,int v){e[idx] = graph(v,head[u]),head[u] = idx++;}
int n,m,a[N],dfn[N],low[N],id[N],vis[N],tim,scc,rd[N],cd[N],f[N],siz[N];
LL dp[N],val[N];
stack <int> st;
map <PII,int> mp;
vector <int> g[N];
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;
st.push(u);
vis[u] = 1;
for(int i = head[u];~i;i = e[i].nxt)
{
int j = e[i].to;
if(!dfn[j])
{
tarjan(j);
low[u] = min(low[u],low[j]);
}
else if(vis[j]) low[u] = min(low[u],dfn[j]);
}
if(dfn[u] == low[u])
{
int y;
scc++;
do
{
y = st.top(); st.pop();
vis[y] = 0;
id[y] = scc;
siz[scc]++;
val[scc] += a[y];
}while(y != u);
}
}
void init()
{
for(int i = 1;i <= n;i++) dfn[i] = low[i] = id[i] = vis[i] = 0;
for(int i = 1;i <= scc;i++) val[i] = f[i] = rd[i] = cd[i] = siz[i] = 0,dp[i] = 1e15,g[i].clear();
while(!st.empty()) st.pop();
scc = tim = 0;
mp.clear();
}
void solve()
{
read(n); read(m);
for(int i = 1;i <= n;i++) head[i] = -1;
for(int i = 0;i < idx;i++) e[i] = graph(0,0);
idx = 0;
for(int i = 1;i <= n;i++) read(a[i]);
for(int i = 1;i <= m;i++)
{
int u,v; read(u); read(v);
adde(u,v);
}
for(int i = 1;i <= n;i++) if(!dfn[i]) tarjan(i);
for(int i = 1;i <= n;i++)
for(int j = head[i];~j;j = e[j].nxt)
{
int k = e[j].to;
if(id[i] == id[k]) continue;
if(!mp.count({id[i],id[k]}))
{
g[id[i]].push_back(id[k]);
mp[{id[i],id[k]}] = 1;
rd[id[k]]++;
cd[id[i]]++;
}
}
queue <int> q;
for(int i = 1;i <= scc;i++) if(!rd[i]) q.push(i),dp[i] = val[i],f[i] = siz[i];
while(!q.empty())
{
int u = q.front(); q.pop();
for(auto x:g[u])
{
if(f[x] < f[u]+siz[x])
{
f[x] = f[u]+siz[x];
dp[x] = dp[u]+val[x];
}
else if(f[x] == f[u]+siz[x]) dp[x] = min(dp[x],dp[u]+val[x]);
--rd[x];
if(!rd[x]) q.push(x);
}
}
int r1 = 0;
LL r2 = 1e15;
for(int i = 1;i <= scc;i++)
if(!cd[i])
{
if(f[i] > r1) r1 = f[i],r2 = dp[i];
else if(f[i] == r1) r2 = min(r2,dp[i]);
}
printf("%d %lld\n",r1,r2);
init();
}
T11
CF852D
题意:
有一个 -1
。
分析:
将一个团队从
然后如果存在一种方案,使得在时间
那么我们可以二分最大时间,对于
const int N = 605;
int n,m,n1,m1,a[N],dis[N][N],vis[N],match[N];
vector <int> g[N];
bool dfs(int u)
{
for(auto x:g[u])
if(!vis[x])
{
vis[x] = 1;
if(match[x] == 0 || dfs(match[x])){match[x] = u;return 1;}
}
return 0;
}
int check(int x,int cnt = 0)
{
for(int i = 1;i <= n;i++) g[i].clear(),match[i] = 0;
for(int i = 1;i <= n;i++) for(int j = 1;j <= n1;j++) if(dis[i][a[j]] <= x) g[i].push_back(j);
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n1;j++) vis[j] = 0;
cnt += dfs(i);
}
return cnt;
}
int main()
{
read(n); read(m); read(n1); read(m1);
for(int i = 1;i <= n1;i++) read(a[i]);
for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) dis[i][j] = 1e9;
for(int i = 1;i <= n;i++) dis[i][i] = 0;
for(int i = 1;i <= m;i++)
{
int u,v,w; read(u); read(v); read(w);
dis[u][v] = dis[v][u] = min(dis[u][v],w);
}
for(int k = 1;k <= n;k++) for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) if(i != j && i != k && j != k) dis[i][j] = min(dis[i][j],dis[i][k]+dis[k][j]);
int l = 0,r = 1e7;
while(l < r)
{
int mid = (l+r)/2;
if(check(mid) >= m1) r = mid;
else l = mid+1;
}
if(check(l) >= m1) printf("%d\n",l);
else puts("-1");
return (0-0); // QwQ
}
T12
CF1239D
题意:
有
要求选出
如果存在方案,请输出方案,否则报告无解。
分析:
可以建一条
那么有这样的构造,找到一个没有出度的连通分量作为人,其他都是猫。
有余
const int N = 1e6+5;
struct graph
{
int to,nxt;
graph(int tt,int nn)
{
to = tt;nxt = nn;
}
graph(){
}
}e[N];
int head[N],idx;
void adde(int u,int v){e[idx] = graph(v,head[u]),head[u] = idx++;}
int n,m,dfn[N],low[N],scc,id[N],siz[N],vis[N],tot;
stack <int> st;
void tarjan(int u)
{
dfn[u] = low[u] = ++tot;
vis[u] = 1;
st.push(u);
for(int i = head[u];~i;i = e[i].nxt)
{
int j = e[i].to;
if(!dfn[j])
{
tarjan(j);
low[u] = min(low[u],low[j]);
}
else if(vis[j]) low[u] = min(low[u],dfn[j]);
}
if(dfn[u] == low[u])
{
int y;
++scc;
do
{
y = st.top(); st.pop();
vis[y] = 0;
id[y] = scc;
siz[scc]++;
}while(y != u);
}
}
void solve()
{
read(n); read(m);
for(int i = 1;i <= n;i++) head[i] = -1;
for(int i = 0;i <= idx;i++) e[i] = graph(0,0);
for(int i = 1;i <= n;i++) dfn[i] = low[i] = id[i] = vis[i] = 0;
for(int i = 1;i <= scc;i++) siz[i] = 0;
while(!st.empty()) st.pop();
tot = idx = scc = 0;
for(int i = 1;i <= m;i++)
{
int u,v; read(u); read(v);
if(u == v) continue;
adde(u,v);
}
for(int i = 1;i <= n;i++) if(!dfn[i]) tarjan(i);
if(scc == 1) puts("No");
else
{
printf("Yes\n%d %d\n",siz[1],n-siz[1]);
int c = 0;
for(int i = 1;i <= n;i++) if(id[i] == 1) printf("%d%c",i," \n"[++c == siz[1]]);
for(int i = 1;i <= n;i++) if(id[i] != 1) printf("%d%c",i," \n"[++c == n]);
}
}
T13
CF888G
题意:
给定一个
分析:
看到完全图,考虑
合并两个连通块可以使用类似线段树合并的方式合并。
const int N = 2e5+5;
class trie01
{
public:
int ch[N*2*30][2],tot,siz[N*2*30],id[N*2*30];
void insert(int &rt,int dep,int i,int x)
{
if(!rt) rt = ++tot;
if(dep < 0)
{
id[rt] = i;
siz[rt] = 1;
return ;
}
int c = x>>dep&1;
insert(ch[rt][c],dep-1,i,x);
siz[rt]++;
}
PII query(int x,int pre,int w)
{
int ret = 0;
for(int i = 30;i >= 0;i--)
{
int c = w>>i&1;
if(ch[x][c] && siz[ch[x][c]]-siz[ch[pre][c]]) x = ch[x][c],pre = ch[pre][c];
else ret |= (1<<i),x = ch[x][c^1],pre = ch[pre][c^1];
}
return {ret,id[x]};
}
void merge(int &x,int y)
{
if(!x || !y) x |= y;
else
{
merge(ch[x][0],ch[y][0]);
merge(ch[x][1],ch[y][1]);
siz[x] = siz[ch[x][0]]+siz[ch[x][1]];
id[x] = id[y];
}
}
}ss;
int n,a[N],rt[N],fa[N],mn[N],ne[N];
int find(int x){return fa[x] = (x == fa[x]?x:find(fa[x]));}
int main()
{
read(n);
for(int i = 1;i <= n;i++) read(a[i]),fa[i] = i;
sort(a+1,a+1+n);
n = unique(a+1,a+1+n)-a-1;
for(int i = 1;i <= n;i++) ss.insert(rt[0],30,i,a[i]),ss.insert(rt[i],30,i,a[i]);
LL ret = 0;
while(1)
{
memset(mn,0x3f,sizeof(mn));
memset(ne,0,sizeof(ne));
int f = 0;
for(int i = 1;i <= n;i++)
{
int x = find(i);
auto now = ss.query(rt[0],rt[x],a[i]);
int y = find(now.se);
if(x != y)
{
if(now.fi < mn[x]) mn[x] = now.fi,ne[x] = y;
if(now.fi < mn[y]) mn[y] = now.fi,ne[y] = x;
}
}
for(int i = 1;i <= n;i++)
if(ne[i] && find(i) != find(ne[i]))
{
ret += mn[i];
ss.merge(rt[find(i)],rt[find(ne[i])]);
f = 1;
fa[find(ne[i])] = find(i);
}
if(!f) break;
}
printf("%lld\n",ret);
return (0-0); // QwQ
}
T14
CF1253F
题意:
给一张
一个机器人在图中行走,假设机器人的电池容量为
有
分析:
首先考虑最短路,所有充电中心都是起点,然后同时跑最短路,求出任意一个充电中心到某个点的最短距离,记到第
然后求
这个问题很经典,可以根据这些最小代价建立
const int N = 1e5+5;
struct graph
{
int to,nxt,w;
graph(int tt,int nn,int ww)
{
to = tt;nxt = nn;w = ww;
}
graph(){
}
}e[N<<3];
int head[N],idx;
void adde(int u,int v,int w){e[idx] = graph(v,head[u],w),head[u] = idx++;}
struct pos
{
int x; LL v;
friend bool operator < (pos a,pos b)
{
return a.v > b.v;
}
};
struct edge
{
int u,v; LL w;
friend bool operator < (edge a,edge b)
{
return a.w < b.w;
}
}E[N];
int n,m,k,q,fa[N],dep[N],f[N][19];
LL dis[N],dp[N][19];
bool vis[N];
vector <pair<int,LL> > g[N];
int find(int x){return fa[x] = (x == fa[x]?x:find(fa[x]));}
bool merge(int x,int y)
{
int r1 = find(x),r2 = find(y);
if(r1 == r2) return 0;
fa[r1] = r2;
return 1;
}
void dij()
{
priority_queue <pos> q;
while(!q.empty()) q.pop();
for(int i = 1;i <= n;i++) dis[i] = 1e18;
for(int i = 1;i <= k;i++) dis[i] = 0,q.push({i,0});
while(!q.empty())
{
auto p = q.top(); q.pop();
int u = p.x;
if(vis[u]) continue;
vis[u] = 1;
for(int i = head[u];~i;i = e[i].nxt)
{
int j = e[i].to;
if(dis[j] > dis[u]+e[i].w)
{
dis[j] = dis[u]+e[i].w;
q.push({j,dis[j]});
}
}
}
}
void dfs(int u,int fa)
{
dep[u] = dep[fa]+1;
f[u][0] = fa;
for(auto x:g[u])
{
int j = x.fi;
if(j == fa) continue;
dfs(j,u);
dp[j][0] = x.se;
}
}
LL calc(int u,int v)
{
if(dep[v] > dep[u]) swap(u,v);
LL ret = 0;
for(int i = 18;i >= 0;i--) if(dep[f[u][i]] >= dep[v]) ret = max(ret,dp[u][i]),u = f[u][i];
if(u == v) return ret;
for(int i = 18;i >= 0;i--) if(f[u][i] != f[v][i]) ret = max(ret,max(dp[u][i],dp[v][i])),u = f[u][i],v = f[v][i];
ret = max(ret,max(dp[u][0],dp[v][0]));
return ret;
}
int main()
{
memset(head,-1,sizeof(head));
read(n); read(m); read(k); read(q);
for(int i = 1;i <= m;i++)
{
int u,v,w; read(u); read(v); read(w);
adde(u,v,w),adde(v,u,w);
}
dij();
for(int i = 0;i < idx;i += 2)
{
int v = e[i].to,u = e[i^1].to;
LL w = dis[v]+dis[u]+e[i].w;
E[i/2+1] = {u,v,w};
}
sort(E+1,E+1+m);
for(int i = 1;i <= n;i++) fa[i] = i;
int cnt = 0;
for(int i = 1;i <= m;i++)
{
int u = E[i].u,v = E[i].v;
if(merge(u,v))
{
g[u].push_back({v,E[i].w});
g[v].push_back({u,E[i].w});
cnt++;
if(cnt == n-1) break;
}
}
dfs(1,0);
for(int j = 1;j <= 18;j++)
for(int i = 1;i <= n;i++)
{
dp[i][j] = max(dp[i][j-1],dp[f[i][j-1]][j-1]);
f[i][j] = f[f[i][j-1]][j-1];
}
while(q--)
{
int u,v; read(u); read(v);
printf("%lld\n",calc(u,v));
}
return (0-0); // QwQ
}
T15
CF95C
题意:
给定一张
给定起点和终点,求最小花费,无解输出 -1
。
分析:
可以先
int n,m,vis[N],st,ed;
LL dis[N];
vector <PII> g[N],e[N];
int main()
{
read(n); read(m); read(st); read(ed);
for(int i = 1;i <= m;i++)
{
int u,v,w; read(u); read(v); read(w);
g[u].push_back({v,w});
g[v].push_back({u,w});
}
for(int i = 1;i <= n;i++)
{
dij(i);
int t,c; read(t); read(c);
for(int j = 1;j <= n;j++) if(dis[j] <= t) e[i].push_back({j,c});
}
for(int i = 1;i <= n;i++) g[i] = e[i];
dij(st);
if(dis[ed] == 1e15) dis[ed] = -1;
printf("%lld\n",dis[ed]);
return (0-0); // QwQ
}
T16
CF95E
题意:
给定一个
无解输出 -1
。
分析:
可以先用并查集合并求出若干连通块他们包含的结点数,那么题目就转化为最少由多少个连通块可以组合出题目所需要的
这是个很显然的背包问题,
但是发现
时间复杂度:
const int N = 1e5+5;
int n,m,siz[N],fa[N],cnt[N],dp[N];
vector <PII> g;
int find(int x){return fa[x] = (x == fa[x]?x:find(fa[x]));}
bool merge(int x,int y)
{
int r1 = find(x),r2 = find(y);
if(r1 == r2) return 0;
fa[r1] = r2;
siz[r2] += siz[r1];
return 1;
}
bool check(int x)
{
while(x)
{
if(x%10 != 4 && x%10 != 7) return 0;
x /= 10;
}
return 1;
}
int main()
{
read(n); read(m);
for(int i = 1;i <= n;i++) fa[i] = i,siz[i] = 1;
for(int i = 1;i <= m;i++)
{
int u,v; read(u); read(v);
merge(u,v);
}
for(int i = 1;i <= n;i++) if(find(i) == i) cnt[siz[i]]++;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= cnt[i];j *= 2) g.push_back({j,i}),cnt[i] -= j;
if(cnt[i]) g.push_back({cnt[i],i});
}
memset(dp,0x3f,sizeof(dp));
dp[0] = 0;
for(auto x:g)
for(int i = n;i >= x.fi*x.se;i--)
dp[i] = min(dp[i],dp[i-x.fi*x.se]+x.fi);
int ret = 1e8;
for(int i = 1;i <= n;i++) if(check(i)) ret = min(ret,dp[i]-1);
if(ret == 1e8) ret = -1;
printf("%d\n",ret);
return (0-0); // QwQ
}
T17
JOI 2014 Final」飞天鼠
题意:
有 -1
。
分析:
如果小鼠能通过某条边从
如果小鼠能通过某条边从
如果存在两棵树
然后就按照上述说法跑最短路就行了。
const int N = 1e5+5,M = 3e5+5;
struct graph
{
int to,nxt,w;
graph(int tt,int nn,int ww)
{
to = tt;nxt = nn;w = ww;
}
graph(){
}
}e[M<<1];
int head[N],idx;
void adde(int u,int v,int w){e[idx] = graph(v,head[u],w),head[u] = idx++;}
struct pos
{
int x,h; LL v;
friend bool operator < (pos a,pos b)
{
return a.v > b.v;
}
};
int n,m,sx,a[N],vis[N];
priority_queue <pos> q;
int main()
{
memset(head,-1,sizeof(head));
read(n); read(m); read(sx);
for(int i = 1;i <= n;i++) read(a[i]);
for(int i = 1;i <= m;i++)
{
int u,v,w; read(u) ;read(v); read(w);
adde(u,v,w),adde(v,u,w);
}
LL ret = 2e18;
q.push({1,sx,0});
while(!q.empty())
{
auto p = q.top(); q.pop();
int u = p.x,h = p.h;
if(u == n) ret = min(ret,p.v+a[n]-h);
if(vis[u]) continue;
vis[u] = 1;
for(int i = head[u];~i;i = e[i].nxt)
{
int j = e[i].to,t = e[i].w;
if(h-t >= 0 && h-t <= a[j]) q.push({j,h-t,p.v+t});
else if(h-t < 0 && a[u] >= t) q.push({j,0,p.v+t-h+t});
else if(h-t > a[j]) q.push({j,a[j],p.v+h-t-a[j]+t});
}
}
if(ret == 2e18) ret = -1;
printf("%lld\n",ret);
return (0-0); // QwQ
}
T18
CF1887B
题意:
给定一张
分析:
首先有一个做法,就是枚举已经走的时间,然后用数组记录能走到哪些点,但是这样是
对每个边集存能走他的时间,然后在跑最短路的时候,
const int N = 2e5+5;
struct pos
{
int x,v;
friend bool operator < (pos a,pos b)
{
return a.v > b.v;
}
};
int n,t,dis[N],vis[N];
vector <PII> g[N];
vector <int> ee[N];
void dij()
{
priority_queue <pos> q;
while(!q.empty()) q.pop();
for(int i = 1;i <= n;i++) dis[i] = 1e9;
dis[1] = 0;
q.push({1,0});
while(!q.empty())
{
auto p = q.top(); q.pop();
int u = p.x;
if(vis[u]) continue;
vis[u] = 1;
for(auto x:g[u])
{
int j = x.fi,v = x.se;
auto now = upper_bound(ee[v].begin(),ee[v].end(),dis[u]);
if(now == ee[v].end()) continue;
if(*now < dis[j])
{
dis[j] = *now;
q.push({j,dis[j]});
}
}
}
}
int main()
{
read(n); read(t);
for(int i = 1;i <= t;i++)
{
int x; read(x);
for(int j = 1;j <= x;j++)
{
int u,v; read(u); read(v);
g[u].push_back({v,i});
g[v].push_back({u,i});
}
}
int x; read(x);
for(int i = 1;i <= x;i++)
{
int v; read(v);
ee[v].push_back(i);
}
dij();
if(dis[n] == 1e9) dis[n] = -1;
printf("%d\n",dis[n]);
return (0-0); // QwQ
}
T19
ABC317G
题意:
给定一个
分析:
可以建一条
时间复杂度:
const int N = 105;
int n,m,g[N][N],vis[N],match[N],ans[N][N],c[N];
bool dfs(int u)
{
for(int i = 1;i <=n;i++)
if(!vis[i] && g[u][i] > 0)
{
vis[i] = 1;
if(!match[i] || dfs(match[i])){match[i] = u;return 1;}
}
return 0;
}
int main()
{
read(n); read(m);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
{
int x; read(x);
g[i][x]++;
}
for(int i = 1;i <= m;i++)
{
int cnt = 0;
for(int j = 1;j <= n;j++) match[j] = 0;
for(int j = 1;j <= n;j++)
{
for(int k = 1;k <= n;k++) vis[k] = 0;
cnt += dfs(j);
}
if(cnt == n) for(int j = 1;j <= n;j++) g[match[j]][j]--,ans[match[j]][++c[j]] = j;
else {puts("No");return (0-0);}
}
puts("Yes");
for(int i = 1;i <= n;i++) for(int j = 1;j <= m;j++) printf("%d%c",ans[i][j]," \n"[j == m]);
return (0-0); // QwQ
}
T20
CF1005F
题意:
给定一张
分析:
先求出一棵最短路径树,然后暴力枚举每个节点可能得父亲,时间复杂度:
const int N = 2e5+5;
struct graph
{
int to,nxt,w;
graph(int tt,int nn,int ww)
{
to = tt;nxt = nn;w = ww;
}
graph(){
}
}e[N<<1];
int head[N],idx;
void adde(int u,int v,int w){e[idx] = graph(v,head[u],w),head[u] = idx++;}
struct pos
{
int x; LL v;
friend bool operator < (pos a,pos b)
{
return a.v > b.v;
}
};
priority_queue <pos> q;
int n,m,k,vis[N<<1],pre[N],sum,dis[N],ret = 1;
vector <int> g[N];
void dij()
{
q.push({1,0});
for(int i = 1;i <= n;i++) dis[i] = 1e9;
dis[1] = 0;
while(!q.empty())
{
auto p = q.top(); q.pop();
int u = p.x;
if(vis[u]) continue;
vis[u] = 1;
for(int i = head[u];~i;i = e[i].nxt)
{
int j = e[i].to;
if(dis[j] > dis[u]+e[i].w)
{
dis[j] = dis[u]+e[i].w;
g[j].clear();
g[j].push_back(i/2+1);
q.push({j,dis[j]});
}
else if(dis[j] == dis[u]+e[i].w) g[j].push_back(i/2+1);
}
}
}
void dfs(int u)
{
if(u == n+1)
{
for(int i = 1;i <= m;i++) printf("%d",vis[i]);
puts("");
--ret;
if(!ret) exit(0);
return ;
}
for(auto x:g[u]) vis[x] = 1,dfs(u+1),vis[x] = 0;
}
int main()
{
memset(head,-1,sizeof(head));
read(n); read(m); read(k);
for(int i = 1;i <= m;i++)
{
int u,v; read(u); read(v);
adde(u,v,1), adde(v,u,1);
}
dij();
memset(vis,0,sizeof(vis));
for(int i = 2;i <= n;i++)
if(1LL*ret*(int)g[i].size() > k) {ret = k;break;}
else ret *= (int)g[i].size();
printf("%d\n",ret);
dfs(2);
return (0-0); // QwQ
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话