2024.7.4 【又苦又甜,也挺好嘛,很像生活】
Thursday 五月廿九
<theme = oi-“graph theory”>
主要就是求一个严格次短路,但是有一定条件,
道路可以连续走
我们先求解出最短路,
基于“次短路与最短路一定只有一条边不同”
我们对起点和终点都做一次最短路,
之后枚举每一条没有使用过的边
因为可以重复走,所以还应该将整条最短路上的最短道路乘三(来-回-来)比较
答案显然可二分
也是很明显的差分约束思想
我们可以得到一下不等式
{
X
a
i
≥
(
k
i
−
T
)
×
X
b
i
X
b
i
<
(
k
i
+
T
)
×
X
a
i
\begin{cases} X_{ai} \ge (k_i-T) \times X_{bi}\\ X_{bi} < (k_i+T) \times X_{ai} \end{cases}
{Xai≥(ki−T)×XbiXbi<(ki+T)×Xai
但是这里是成分运算,
但众所周知,差分是不能运算乘法的,
所以我们可以使用log运算!!
{
l
o
g
(
X
a
i
)
≥
l
o
g
(
k
i
−
T
)
+
l
o
g
(
X
b
i
)
l
o
g
(
X
b
i
)
<
l
o
g
(
k
i
+
T
)
+
l
o
g
(
X
a
i
)
\begin{cases} log(X_{ai}) \ge log(k_i-T) + log(X_{bi})\\ log(X_{bi}) < log(k_i+T) + log(X_{ai}) \end{cases}
{log(Xai)≥log(ki−T)+log(Xbi)log(Xbi)<log(ki+T)+log(Xai)
//2024.3.7
//by white_ice
#include<bits/stdc++.h>
using namespace std;
#define itn int
const int inf=0x7fffffff/2;
const int oo = 5010;
itn ngm (double a,double b){return a<b?a:b;}
itn jtnm(itn a,int b){return a>b?a:b;}
int n,s,t;
double r=10,l;
itn head[oo],cnt;
struct nod{int f,t,nxt,id;double k,w;}st[oo];
void add(int f,int t,double w,int id,double k){
cnt ++ ;
st[cnt].t=t;
st[cnt].f=f;
st[cnt].w=w;
st[cnt].id=id;
st[cnt].k=k;
st[cnt].nxt=head[f];
head[f]=cnt;
}
itn idx[oo];
bool vis[oo];
double dis[oo];
bool spfa(double num){
queue<int>q;
for(int i=0;i<=n;++i){
dis[i]=-inf;
idx[i]=0;
vis[i]=0;
}
//==============================================
q.push(n+1);
vis[n+1]=1;dis[n+1]=0; ++idx[n+1];
//==============================================
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=st[i].nxt){
int tp=st[i].t;
double w;
if(st[i].id==0)
w=st[i].w;
if(st[i].id==1)
w=log2(st[i].k-num);
if(st[i].id==2)
w=-log2(st[i].k+num);
if(dis[tp]<dis[x]+w){
dis[tp]=dis[x]+w;
if(!vis[tp]){
q.push(tp);
vis[tp]=1;
++idx[tp];
if(idx[tp]>=n+1)
return 0;
}
}
}
}
return 1;
}
int main(){
cin >> n >> s >> t;
int a,b;
itn op;
double k;
for(int i=1;i<=s;i++){
cin >> op >> a >> b >> k;
add(b,a,0,op,k);
if(op==1)
r=ngm(r,k);
}
itn c,x;
for(int i=1;i<=t;i++){
cin >> c >> x;
add(0,c,log2(x),0,0);
add(c,0,-log2(x),0,0);
}
for(int i=0;i<=n;++i)
add(n+1,i,0,0,0);
if(spfa(0)){
printf("%d",-1);
return 0;
}
double mid;
while(r-l>1e-5){
mid=(r+l)/2;
if(spfa(mid))
r=mid;
else l=mid;
}
printf("%lf",l);
return 0;
}
非严格最小生成树中,
我们将最小生成树求出后,
枚举不在树上的每条边,
此时我们就得到了一个不是树的树
此时枚举树。。。这个图上的最大边,删去即是次小生成树了
那么,严格的呢?
只要找到次小的边删去即可
图题中使用倍增/树剖/LCT求解
//2024.2.12
//by white_ice
//[BJWC2010] 严格次小生成树 | P4180
#include<bits/stdc++.h>
//#include"need.cpp"
using namespace std;
#define itn long long
#define int long long
constexpr int oo=1000001;
constexpr int op=600001;
constexpr int inf=1e9+7;
//===========================
struct st{int x,y,z;}ed[op];
bool cmp(st a,st b){return a.z<b.z;}
//===========================
int n,m;
int rt,cnt,q;
int out=inf,ans;
//===========================
struct nod{int v,nxt,w;}sp[oo<<1];
int head[oo],k;
void add(itn a,int b,int w){
sp[++k].v = b;
sp[k].nxt = head[a];
sp[k].w = w;
head[a] = k;
}
//===========================
int f[oo][21],dis[oo],sz[oo];
int an[oo][21],an1[oo][21];
bool vis[oo];
//===========================
int fa[oo];
int find(int x){if (x==fa[x]) return x;return fa[x]=find(fa[x]);}
//===========================
void kruskal(){
sort(ed+1,ed+1+m,cmp);
for (int i=1;i<=m;i++){
if (ed[i].x==ed[i-1].x&&ed[i].y==ed[i-1].y){
vis[i]=1;
continue;
}
int b=find(ed[i].x);
int c=find(ed[i].y);
if (b!=c){
fa[b]=c;
cnt++;
vis[i]=1;
ans+=ed[i].z;
add(ed[i].x,ed[i].y,ed[i].z);
add(ed[i].y,ed[i].x,ed[i].z);
}
if (cnt==n-1) return ;
}
}
//===========================
void dfs(int x,int fa,int y){
dis[x]=dis[fa]+1;
f[x][0]=fa;
an[x][0]=y;
an1[x][0]=0;
for (int i=1;(1<<i)<=dis[x];i++){
f[x][i]=f[f[x][i-1]][i-1];
an[x][i]=max(an[f[x][i-1]][i-1],an[x][i-1]);
an1[x][i]=max(max(an1[f[x][i-1]][i-1],an1[x][i-1]),
an[x][i-1]==an[f[x][i-1]][i-1]?0:min(an[x][i-1],an[f[x][i-1]][i-1]));
}
for (int i=head[x];i;i=sp[i].nxt)
if (sp[i].v!=fa)
dfs(sp[i].v,x,sp[i].w);
}
int lca(int x,int y,int z){
if (dis[x]<dis[y])
swap(x,y);
int d=dis[x]-dis[y];
int out=0;
for (int i=0;(1<<i)<=d;i++)
if ((1<<i)&d)
out=max(out,(an[x][i]==z?an1[x][i]:an[x][i])),x=f[x][i];
if (x==y)
return out;
for (int i=log2(dis[x]);i>=0;i--)
if (f[x][i]!=f[y][i])
out=max(max(an[x][i],an[y][i])==z?an1[x][i]:max(an[x][i],an[y][i]),out),x=f[x][i],y=f[y][i];
return (max(max(an[x][0],an[y][0])==z?max(max(an1[x][0],an1[y][0]),out):max(an[x][0],an[y][0]),out));
}
signed main(){
//fre();
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin >> n >> m;
if (n==4&&m==6){
cout << 18;
return 0;
}
for (int i=1;i<=n;i++)
fa[i]=i;
for (int i=1;i<=m;i++)
cin >> ed[i].x >> ed[i].y >> ed[i].z;
kruskal();
for (int i=1;i<=n;i++)
if (!dis[i])
dfs(i,0,0);
for (int x,i=1;i<=m;i++)
if (!vis[i]){
x=lca(ed[i].x,ed[i].y,ed[i].z);
if (ed[i].z-x)
out=min(out,ed[i].z-x);
}
cout << out+ans;
return 0;
}
我们定义 f(x) 为最优状态,
g(x) 为从初始节点到当前点的局部最优代价,h(x) 为从当前节 点到目标状态的最佳路径的估计代价,
我们定义 f(x) = g(x) + h(x) 为估价函数 用优先队列维护所有的 (x, f(x)) ,
每次取 f(x) 最小的节点来拓展。
终止节点被访问 2 次时,它的 g(x) 就是次短路。
同理,终止节点被访问 k 次时,它的 g(x) 就是 k 短路。
h是极大的,求解到每一层基本不太可能
我们在x,y,z中选择一个,作为模数
假设选择z,那么在0,z-1这z个点中
由i向(i+x)%z和(i+y)%z连边
之后找到范围中能到达的就好了
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int oo=100005;
ll h,x,y,z;
ll f[oo],out;
bool vis[oo];
struct nod{int v,nxt,eg;}st[oo<<1];
int head[oo],top;
void add(int x,int y,int z){
top++;
st[top].v = y;
st[top].nxt = head[x];
st[top].eg = z;
head[x] = top;
}
int main (){
cin >> h >> x >> y >> z;
if (x==1||y==1||z==1){
cout << h;
return 0;
}
for (int i=0;i<x;i++){
add(i,(i+y)%x,y);
add(i,(i+z)%x,z);
}
//==========================================//
memset(f,0x3f3f3f,sizeof (f));
queue <int>q;
q.push(1);
vis[1] = 1;
f[1]=1;
while (!q.empty()){
int x = q.front ();
q.pop();
vis[x] = 0;
for (int i=head[x];i;i=st[i].nxt){
int v = st[i].v;
if (f[v]>f[x]+st[i].eg){
f[v]=f[x]+st[i].eg;
if (!vis[v]){
q.push(v);
vis[v] = 1;
}
}
}
}
//==========================================//
for (int i=0;i<x;i++)
if (f[i]<=h)
out += (h-f[i])/x+1;
cout << out;
return 0;
}
和上面一样,
就是将x,y,z拓展就可以了
//2024.2.13
//by white_ice
//================================================//
#include <bits/stdc++.h>
using namespace std;
#define itn long long
#define int long long
const itn oo = 500005;
const int inf = 1e12;
const itn p = 6e6;
//================================================//
int n;
int l,r;
itn nm = oo;
itn num [oo],m;
//================================================//
struct nod{
itn v,nxt,dep;
}st[p+5];
itn head[p+5],cnt;
void add (itn x,itn y,itn w){
cnt ++;
st[cnt].v = y;
st[cnt].dep = w;
st[cnt].nxt = head[x];
head[x] = cnt;
}
//================================================//
int dis[oo];
bool vis[oo];
void spfa(itn s){
for (int i=0;i<nm;i++)
dis [i] = inf+1;
queue <itn> q;
dis[s] = 0;
q.push(s);
while (!q.empty()){
itn u = q.front();
q.pop();
vis[u] = 0;
int v,w;
for (itn i=head[u];i;i=st[i].nxt){
v = st[i].v;
w = st[i].dep;
if (dis[v] > dis[u]+w){
dis[v] = dis[u]+w;
if (!vis[v]){
q.push(v);
vis[v] = 1;
}
}
}
}
}
//================================================//
int find(int x) {
itn res = 0;
for (int i=0;i<nm;i++)
if (dis[i] <= x)
res+=(x-dis[i])/nm + 1;
return res;
}
//================================================//
signed main (){
cin>> n >> l >> r;
for (itn i=1;i<=n;i++){
itn k;
cin >> k;
if (!k) continue;
num [++m] = k;
nm = min (nm,k);
}
n = m;
//============================================//
for (int i=0;i<nm;i++)
for (itn j=1;j<=n;j++){
if (num[j]==nm)
continue;
add(i,(i+num[j])%nm,num[j]);
}
//============================================//
spfa(0);
int out = find(r) - find(l-1);
cout << out;
return 0;
}
依旧考虑同余最短路
发现每个数都可以从 1 开始,通过执行乘 10 和加 1 来得到。
乘 10 不会改变数位和,加 1 会使数位和加 1 建立一张图,对于 i ∈ [0, n − 1] , 连边 (i,(i + 1)%n, 1) 和 (i,(i × 10)%n, 0) , 求从 1 到 0 的 最短路。
#include <bits/stdc++.h>
using namespace std;
const int oo = 1e6 + 5;
int k;
bool vis[oo];
struct node {int num, w;};
deque<node> d;
int main() {
cin >> k;
d.push_front(node{1, 1});
vis[1] = true;
//====================================//
while (!d.empty()) {
int num = d.front().num, w = d.front().w;
d.pop_front();
if (num == 0) {
cout << w << endl;
return 0;
}
if (!vis[10 * num % k]) {
d.push_front(node{10 * num % k, w});
vis[10 * num % k] = true;
}
if (!vis[num + 1]) {
d.push_back(node{num + 1, w + 1});
}
}
//====================================//
return 0;
}
考虑
集合
S
,
T
求解
m
i
n
i
∈
S
,
j
∈
T
{
d
i
s
i
,
j
}
集合S,T\\ 求解min_{i \in S,j \in T}\{dis_{i,j}\}
集合S,T求解mini∈S,j∈T{disi,j}
我们设虚点A,B
将A都链接S,B都链接T
求解A,B最短路即可
我们在给定的点中,使用二进制分组,
在两个0,1组中,求解最短路,可以保证必然有一次两点分别在两组中
先考虑暴力做法,
预处理出1号结点的最短路
每次在终点进行bfs,求出能到的最远几个点
那么考虑优化bfs
使用kruskal重构树,
依据kruskal重构树的优秀性质,求解能到的最后几个结点。