P3916 图的遍历+P3907 圈的异或+P1730 最小密度路径+P1768 天路

P3916 图的遍历
简单的题
缩点在dfs
但t了。。。。尬

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int M=210000; 
int h[M],to[M],nex[M],tot,a[M],dfn[M],low[M],strack[M],top,num,vis[M],col_num,col[M];
int n,m,x[M],y[M],r[M];
void add(int x,int y){
    nex[++tot]=h[x];
    to[tot]=y;h[x]=tot;
}
void tarjan(int x){
    dfn[x]=low[x]=++num;
    strack[++top]=x,vis[x]=1;
    for(int i=h[x],tmp;i;i=nex[i]){
        if(!dfn[tmp=to[i]]) tarjan(tmp),low[x]=min(low[x],low[tmp]);
        else if(vis[tmp]) low[x]=min(low[x],dfn[tmp]);
    }
    if(dfn[x]==low[x]){
        ++col_num; 
        while(strack[top+1]!=x&&top){
            col[strack[top]]=col_num;
            a[col_num]=max(a[col_num],strack[top]); 
            vis[strack[top--]]=0; 
        }
    }
} 
void dfs(int x,int fa){
    for(int i=h[x],tmp;i;i=nex[i])
    if((tmp=to[i])!=fa) dfs(tmp,x),a[x]=max(a[x],a[tmp]);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]),add(x[i],y[i]);
    for(int i=1;i<=n;i++) if(!dfn[i])  tarjan(i);
    tot=0;memset(h,0,sizeof h);
    for(int i=1;i<=m;i++) if(col[x[i]]!=col[y[i]]) add(col[x[i]],col[y[i]]),r[col[y[i]]]++;
    for(int i=1;i<=col_num;i++) if(r[i]==0) dfs(i,i);
    for(int i=1;i<=n;i++) printf("%d ",a[col[i]]);
}

P3907 圈的异或
正解是倍增+判小环
不过数据很弱,暴力过了。。。


#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int M=400; 
int h[M],to[M],nex[M],tot,flag,cos[M],vis[M];
int n,m,T;
void add(int x,int y,int z){
    nex[++tot]=h[x];
    to[tot]=y;cos[tot]=z;h[x]=tot;
}
void dfs(int x,int zz,int s){
    if(flag==1) return ;
    for(int i=h[x],tmp;i<=tot&&i>0;i=nex[i])
    if(!vis[tmp=to[i]]) vis[tmp]=1,dfs(tmp,zz,s^cos[i]);
    else if(((s^cos[i])!=0)&&(tmp==zz)) flag=1;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);flag=0;tot=0;memset(h,0,sizeof h);
        for(int i=1,x,y,z;i<=m;i++) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
        for(int i=1;i<=n;i++) if(!flag) {memset(vis,0,sizeof vis);vis[i]=1;dfs(i,i,0);}else break;
        if(!flag) printf("Yes\n");else printf("No\n");
    }

}

P1730 最小密度路径
floy水过,01分数规划没法做

考虑转化成我们熟悉的问题解决。

由于都是求最小,很容易想到和此题类似的一个问题,求任两点间的最短路,能否借鉴Floyd算法来解决呢?

本题不同点在于,还要除以一个边数。因为这个除法的缘故,使得Floyd算法的最优子结构性质被破坏,假设存在路径i -> k -> j,它的最小密度路径并不一定是i -> k的最小密度路径加上k -> j的最小密度路径。

例:设[A, B]表示路径的权值和为A,通过了B条边。假设从i -> k存在着两条路径L1[2, 3]以及L2[8, 10],从k -> j存在着两条路径L3[1, 2]以及L4[51, 100],很明显i -> k的最小密度路径是L1,k -> j的最小密度路径是L3,但是i -> k -> j的最小密度路径却是L1 + L4。

有否办法去掉这个除法的影响?

回到问题特性,是有向无环图,一条路径最多只能经过N-1条边,于是我们可以对边数进行枚举,即把答案的分母枚举了,剩下的就是让答案的分子最小化(答案是 权值和/边数),这就回到我们熟悉的问题:求最短。

在Floyd的基础上重新划分阶段定义状态:

第k个阶段表示恰好通过k条边两点间的最短路,这样的话最优子结构以及无后效性都满足(k的阶段的最优取值一定需要靠之前阶段的最优值,当然也不可能影响到之前阶段的取值了。)

定义状态f(i,j,k)表示从i到j恰好经过k条边的最短路,类似Floyd的算法得出DP方程:

f(i,j,k)=Min{f(i,h,g)+f(h,j,k-g)}。

这个方程是5维的,会超时,如何减小维数呢?

考虑在何处重复决策。注意到f(i,j,k)的选择路径V1-V2-…-Vk,实际上我们只要找到这里的一个点决策即可,而不需每个点都判断过去。这样就很容易想到在最后一个点进行决策。

f(i,j,k)=Min{f(i,h,k-1)+f(h,j,1)}。

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int M=1100,N=55; 

int n,m,q,f[N][N][M];

int main(){
    scanf("%d%d",&n,&m);memset(f,127/3,sizeof f);
    for(int i=1,x,y,z;i<=m;i++) scanf("%d%d%d",&x,&y,&z),f[x][y][1]=min(f[x][y][1],z);
    for(int l=2;l<=m;l++)for(int k=1;k<=n;k++)
      for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
        f[i][j][l]=min(f[i][j][l],f[i][k][l-1]+f[k][j][1]);
    scanf("%d",&q);
    while(q--){
        int x,y;double ans=f[0][0][0];
        scanf("%d%d",&x,&y);
        for(int l=1;l<=m;l++) if(f[x][y][l]!=f[0][0][0])ans=min(ans,(double)f[x][y][l]/l);
        if(ans!=f[0][0][0])printf("%.3f\n",ans);else printf("OMG!\n");
    }  

}

P1768 天路
01分数规划spfa判负环

题目可以总结为:存在一个环,使sum(v)/sum(w)==ans,求最小的ans。式子整理得sum(v)-ans*sum(w)<=0。

然后我就错误地开始了以下尝试——

二分ans,并用(dfs式)SPFA判负环。如果存在负环,则减小答案,否则增大答案。

然而那是<=,不是<!

所以正确的式子应该是ans*sum(w)-sum(v)>=0,即不存在负环。

#include<cstdio>
#include<cstring>
const int M=21100,N=7100;int nex[M],head[N],to[M],tot,n,m;
double cos[M],l=0,r=200.0,dis[N],cost[M],p,v;bool flag,vis[N];
void add(int x,int y,double z,double w){
    nex[++tot]=head[x]; to[tot]=y;
    cos[tot]=z; cost[tot]=w,head[x]=tot;
}
void spfa(int x,double w){
    if(flag)return ;vis[x]=1;
    for(int i=head[x],tmp;i;i=nex[i])
    if(dis[tmp=to[i]]>dis[x]+cost[i]*w-cos[i]){dis[tmp]=dis[x]+cost[i]*w-cos[i];
        if(!vis[tmp]) spfa(tmp,w);
        else {flag=1;return;}
    }vis[x]=0;
}
bool check(double w){
    memset(dis,0,sizeof dis);memset(vis,0,sizeof vis);flag=0;
    for(int i=1;i<=n;i++){
        spfa(i,w);if(flag) break;
    }return flag;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,x,y;i<=m;i++) scanf("%d%d%lf%lf",&x,&y,&p,&v),add(x,y,p,v);
    while(l+0.01<r){ double mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    if(l)printf("%.1lf",r);
    else printf("-1");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值