2024.7.4

2024.7.4 【又苦又甜,也挺好嘛,很像生活】

Thursday 五月廿九


<theme = oi-“graph theory”>


P2865 [USACO06NOV] Roadblocks G

主要就是求一个严格次短路,但是有一定条件,

道路可以连续走

我们先求解出最短路,

基于“次短路与最短路一定只有一条边不同”

我们对起点和终点都做一次最短路,

之后枚举每一条没有使用过的边

因为可以重复走,所以还应该将整条最短路上的最短道路乘三(来-回-来)比较

P4926 [1007] 倍杀测量者

答案显然可二分

也是很明显的差分约束思想

我们可以得到一下不等式

{ 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(kiT)×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(kiT)+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;
} 

P4180 [BJWC2010] 严格次小生成树

非严格最小生成树中,

我们将最小生成树求出后,

枚举不在树上的每条边,

此时我们就得到了一个不是树的树

此时枚举树。。。这个图上的最大边,删去即是次小生成树了

那么,严格的呢?

只要找到次小的边删去即可

图题中使用倍增/树剖/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;
}

P2483 【模板】k 短路 / [SDOI2010] 魔法猪学院

我们定义 f(x) 为最优状态,

g(x) 为从初始节点到当前点的局部最优代价,h(x) 为从当前节 点到目标状态的最佳路径的估计代价,

我们定义 f(x) = g(x) + h(x) 为估价函数 用优先队列维护所有的 (x, f(x)) ,

每次取 f(x) 最小的节点来拓展。

终止节点被访问 2 次时,它的 g(x) 就是次短路。

同理,终止节点被访问 k 次时,它的 g(x) 就是 k 短路。

P3403 跳楼机

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;
}

P2371 [国家集训队] 墨墨的等式

和上面一样,

就是将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;
}

[ABC077D] Small Multiple

依旧考虑同余最短路

发现每个数都可以从 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;
}

P5304 [GXOI/GZOI2019] 旅行者

考虑
集合 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求解miniS,jT{disi,j}
我们设虚点A,B

将A都链接S,B都链接T

求解A,B最短路即可

我们在给定的点中,使用二进制分组,

在两个0,1组中,求解最短路,可以保证必然有一次两点分别在两组中

P4768 [NOI2018] 归程

先考虑暴力做法,

预处理出1号结点的最短路

每次在终点进行bfs,求出能到的最远几个点

那么考虑优化bfs

使用kruskal重构树,

依据kruskal重构树的优秀性质,求解能到的最后几个结点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值