【题解】2020 年电子科技大学 ACM-ICPC 暑假前集训 - 图论

比赛地址

A - 软件工程

对于一条边\(i\to j\)

最早的开始时间有递推关系\(f[j]=max\{f[i]+w[i]\}\),正向跑一边DAG即可

最晚的开始时间有递推关系\(g[i]=min\{g[j]-w[i]\}\),反向跑一边DAG即可

#include <cstdio>
#include <iostream>
#include <climits>
using namespace std;
typedef long long ll;
const ll N=5*1e5+2,M=1e6+1;
ll n,m,a,b,c,ans,t[N],s[N],d[N],sz,sz2,vis[N],vis2[N],head[N],head2[N];
struct E{
    ll next,to;
}e[M*2],e2[M*2];
void insert(ll a,ll b){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    sz2++;
    e2[sz2].next=head2[b];
    head2[b]=sz2;
    e2[sz2].to=a;
}
void dfs(ll x){
    vis[x]=1;
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to;
        if (!vis[v]) dfs(v);
        s[x]=max(s[x],s[v]+t[v]);
    }
}
void dfs2(ll x){
    vis2[x]=1;
    for (ll i=head2[x];i;i=e2[i].next){
        ll v=e2[i].to;
        if (!vis2[v]) dfs2(v);
        d[x]=min(d[x],d[v]-t[x]);
    }
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++) scanf("%lld",&t[i]);
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld",&a,&b);
        insert(b,a);
    }
    for (ll i=1;i<=n;i++) insert(i,0),insert(n+1,i),d[i]=LONG_LONG_MAX;
    dfs(n+1);
    t[n+1]=0,d[n+1]=s[n+1];
    dfs2(0);
    for (ll i=1;i<=n;i++) ans+=d[i]-s[i];
    printf("%lld\n%lld",s[n+1],ans);
}

B - 国际城市设计大赛

次小生成树

树上倍增,维护向上\(2^i\)个点的祖先\(fa\),以及过程中的最长边\(f1\)和次长边\(f2\)\(f1\)\(f2\)的转移方法和吉司机线段树相同

每次尝试把一条不在MST中的边\((i,j,w)\)加到树中,并删去原来MST中\((i,j)\)路径上的最长边

由于要求的是严格次小生成树,所以如果最长边长度和\(w\)相等,就删去原来MST中\((i,j)\)路径上的次长边

#include <cstdio>
#include <algorithm>
#include <climits>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=3*1e5+1,K=20;
ll n,m,a,b,c,sz,head[N],fa[N][K],vis[N],f1[N][K],f2[N][K],d[N],mn,mx,siz,ans=LONG_LONG_MAX;
struct P{
    ll a,b,w,use;
    P(){}
    P(ll _a,ll _b,ll _w){a=_a,b=_b,w=_w,use=0;}
    friend bool operator<(P a,P b){
        return a.w<b.w;
    }
}p[N];
struct E{
    ll next,to,w;
}e[2*N];
void insert(ll a,ll b,ll w){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].w=w;
}
ll find(ll x){
    return fa[x][0]==x?x:fa[x][0]=find(fa[x][0]);
}
void mst(){
    sort(p+1,p+m+1);
    for (ll i=1;i<=n;i++) fa[i][0]=i;
    for (ll i=1;i<=m;i++){
        ll ta=find(p[i].a),tb=find(p[i].b);
        if (ta!=tb){
            p[i].use=1;
            fa[tb][0]=ta;
            siz+=p[i].w;
            insert(p[i].a,p[i].b,p[i].w);
            insert(p[i].b,p[i].a,p[i].w);
        }
    }
}
void dfs(ll x){
    for (ll i=1;i<K;i++){
        fa[x][i]=fa[fa[x][i-1]][i-1];
        if (f1[x][i-1]==f1[fa[x][i-1]][i-1]){
            f1[x][i]=f1[x][i-1];
            f2[x][i]=max(f2[x][i-1],f2[fa[x][i-1]][i-1]);
        }else if (f1[x][i-1]>f1[fa[x][i-1]][i-1]){
            f1[x][i]=f1[x][i-1];
            f2[x][i]=max(f2[x][i-1],f1[fa[x][i-1]][i-1]);
        }else{
            f1[x][i]=f1[fa[x][i-1]][i-1];
            f2[x][i]=max(f1[x][i-1],f2[fa[x][i-1]][i-1]);
        }
    }
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to;
        if (!d[v]){
            d[v]=d[x]+1;
            fa[v][0]=x;
            f1[v][0]=e[i].w;
            dfs(v);
        }
    }
}
void update(ll x){
    if (x>mx){
        mn=mx;
        mx=x;
    }
    if (mx>x&&x>mn){
        mn=x;
    }
}
void lca(ll a,ll b){
    mn=mx=0;
    if (d[a]<d[b]) swap(a,b);
    for (ll i=K-1;i>=0;i--)
        if (d[fa[a][i]]>=d[b]){
            update(f1[a][i]);update(f2[a][i]);
            a=fa[a][i];
        }
    if (a==b) return;
    for (ll i=K-1;i>=0;i--)
        if (fa[a][i]!=fa[b][i])
        {
            update(f1[a][i]);update(f2[a][i]);
            update(f1[b][i]);update(f2[b][i]);
            a=fa[a][i],b=fa[b][i];
        }
    update(f1[a][0]);update(f2[a][0]);
    update(f1[b][0]);update(f2[b][0]);
}
void solve(){
    for (ll i=1;i<=m;i++)
        if (!p[i].use){
            lca(p[i].a,p[i].b);
            if (p[i].w!=mx){
                ans=min(ans,siz+p[i].w-mx);
            }else if (mn!=0){
                ans=min(ans,siz+p[i].w-mn);
            }
        }
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        p[i]=P(a,b,c);
    }
    mst();
    d[1]=1;
    fa[1][0]=0;
    dfs(1);
    solve();
    printf("%lld",ans);
}

C - 宁王我好兄弟

D - 17 张牌你能秒我

对不等式两侧取对数,把乘法转化成加法,把不等式限制转化为最短路关系

\(a-b\leq x_1\\b-c\leq x_2\\c-a\leq x_3\)

三个不等式相加有

\(0\leq x_1+x_2+x_3\)

\(0>x_1+x_2+x_3\)时,即图中道路存在负环,此时约束关系无解

用Bellman-Ford求最短路,如果整个图被更新了\(n\)次,则说明图中存在负环

#include <cstdio>
#include <cmath>
#include <cstring>
const int N=5*1e3+1;
int n,m,a,b,c,head[N],sz;
double d,f[N];
struct E{
    int next,to;
    double w;
}e[2*N];
void insert(int a,int b,double w){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].w=w;
}
bool bellmanford(){
    for (int i=1;i<=n;i++){
        bool t=0;
        for (int j=1;j<=n;j++)
            for (int k=head[j];k;k=e[k].next){
                int v=e[k].to;
                if (f[v]>f[j]+e[k].w){
                    f[v]=f[j]+e[k].w;
                    t=1;
                    if (i==n) return true;
                }
            }
        if (!t) break;
    }
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        scanf("%d%d%d%lf",&a,&b,&c,&d);
        if (a==1) insert(b,c,-log(d));
        if (a==2) insert(c,b,log(d));
        if (a==3) insert(b,c,-log(d)),insert(c,b,log(d));
    }
    if (bellmanford())
        printf("Delicious");
    else
        printf("DEDEDEDEDEDEDEDEDEDEDEDE");
}

E - 密室逃脱

对于每一行都建立一个源点,对于每一行都建立一个汇点

同时在容量上做限制,保证每一行每一列的流量都为\(1\)

矩阵中的每个点都与所在行的源点和所在列的汇点连接,设置对应的负费用

再建一个超级源点和一个超级汇点,用MCMF跑最小费用流即可,每次增广时在残存网络上跑SPFA找最短路即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <climits>
using namespace std;
const int N=102*102;
int n,sz=1,head[N],s,t,w,pre[N],dis[N],incf[N],vis[N];
queue<int> que;
struct E{
    int next,to,w,c;
}e[N*4];
void insert(int a,int b,int w,int c){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].w=w;
    e[sz].c=c;
}
void flow(int a,int b,int w,int c){
    insert(a,b,w,c);
    insert(b,a,0,-c);
}
bool spfa(){
    memset(dis,0x7f,sizeof(dis));
    que.push(s);dis[s]=0;incf[s]=INT_MAX,incf[t]=0;
    while(!que.empty()){
        int x=que.front();que.pop();
        vis[x]=0;
        for (int i=head[x];i;i=e[i].next){
            int v=e[i].to,w=e[i].w,c=e[i].c;
            if (!w||dis[v]<=dis[x]+c) continue;
            dis[v]=dis[x]+c;
            incf[v]=min(w,incf[x]);
            pre[v]=i;
            if (!vis[v]) que.push(v),vis[v]=1;
        }
    }
    return incf[t];
}
int maxflow,mincost;
void update(){
    maxflow+=incf[t];
    for (int i=t;i!=s;i=e[pre[i]^1].to){
        e[pre[i]].w-=incf[t];
        e[pre[i]^1].w+=incf[t];
        mincost+=incf[t]*e[pre[i]].c;
    }
}
int main(){
    scanf("%d",&n);
    s=0;t=(n+1)*(n+1);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++){
            scanf("%d",&w);
            flow(i,i*(n+1)+j,1,-w);
            flow(i*(n+1)+j,j*(n+1),1,0);
        }
    for (int i=1;i<=n;i++){
        flow(s,i,1,0);
        flow(i*(n+1),t,1,0);
    }
    while(spfa()) update();
    printf("%d",-mincost);
}

F - 酒厂

最小树形图,朱刘算法

#include <cstdio>
#include <cmath>
#include <cstring>
#include <climits>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=1e3+1,M=1e4+1;
ll n,m,a,b,c,siz,in[N],pos,u,v,vis[N],id[N],pre[N],sum;
struct P{
    ll a,b,w;
}p[M+N];
void mdst(ll rt,ll n,ll m){
    siz=0;
    while(1){
        for (ll i=0;i<=n;i++) in[i]=LLONG_MAX;
        for (ll i=1;i<=m;i++){
            ll a=p[i].a,b=p[i].b,w=p[i].w;
            if (a!=b&&w<in[b]){
                in[b]=w;
                pre[b]=a;
                if (a==rt) pos=i;
            }
        }
        for (ll i=0;i<=n;i++){
            if (i==rt) continue;
            if (in[i]==LLONG_MAX){
                siz=LLONG_MAX;
                return;
            }
        }
        ll cnt=0;
        in[rt]=0;
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        for (ll i=0;i<=n;i++){
            siz+=in[i];
            ll b=i;
            while(vis[b]!=i&&id[b]==-1&&b!=rt){
                vis[b]=i;
                b=pre[b];
            }
            if (b!=rt&&id[b]==-1){
                for (ll a=pre[b];a!=b;a=pre[a])
                    id[a]=cnt;
                id[b]=cnt++;
            }
        }
        if (cnt==0) break;
        for (ll i=0;i<=n;i++)
            if (id[i]==-1) id[i]=cnt++;
        for (ll i=1;i<=m;i++){
            ll a=p[i].a,b=p[i].b,w=p[i].w;
            p[i].a=id[a];
            p[i].b=id[b];
            if (id[a]!=id[b])
                p[i].w-=in[b];
        }
        n=cnt-1;
        rt=id[rt];
    }
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        p[i].a=a,p[i].b=b,p[i].w=c;
        sum+=c;
    }
    sum++;
    for (ll i=1;i<=n;i++){
        p[m+i].a=0,p[m+i].b=i,p[m+i].w=sum;
    }
    mdst(0,n,m+n);
    if (siz>=2*sum){
        printf("-1");
    }else{
        printf("%lld %lld",siz-sum,pos-m);
    }
}

G - Stardust

2-SAT

在一个期望不满足的情况下,另外两个期望必须被满足

\((a,b,c)\)就转化为了

\(\neg a\to b,\neg a\to c\\\neg b\to a,\neg b\to c\\\neg c\to a,\neg c\to b\)

对于每个三元关系连六条边即可

跑一边tarjan判断是否存在\(a\)\(\neg a\)位于同一强连通分量,即存在\(a\)\(\neg a\)能够互推的情况,只有这时是不存在可行解的

#include <cstdio>
#include <iostream>
using namespace std;
const int N=2*(1e5+1);
int n,m,a,b,c,xa,xb,xc,head[N],sz,bl[N],cnt,ssz,dfn[N],vis[N],low[N],sta[N];
struct E{
    int next,to;
}e[3*N];
void insert(int a,int b){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
}
void tarjan(int x){
    dfn[x]=low[x]=++cnt;
    vis[x]=1;
    sta[++ssz]=x;
    for (int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if (!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }else{
            if (vis[v])
                low[x]=min(low[x],dfn[v]);
        }
    }
    if (dfn[x]==low[x]){
        do{
            bl[sta[ssz]]=x;
            vis[sta[ssz]]=0;
        }while(sta[ssz--]!=x);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        scanf("%d%d%d%d%d%d",&a,&xa,&b,&xb,&c,&xc);
        insert(a*2+xa^1,b*2+xb);insert(a*2+xa^1,c*2+xc);
        insert(b*2+xb^1,a*2+xa);insert(b*2+xb^1,c*2+xc);
        insert(c*2+xc^1,a*2+xa);insert(c*2+xc^1,b*2+xb);
    }
    for (int i=1;i<=2*n;i++)
        if (!dfn[i])
            tarjan(i);
    bool t=1;
    for (int i=1;i<=n;i++)
        if (bl[i*2]==bl[i*2+1]) t=0;
    if (t)
        printf("YES");
    else
        printf("NO");
}

H - 快乐水偷运计划

点对\((i,j)\)产生的贡献

\(W=x\otimes i\otimes j\)

其中\(x\)是比较好求,把仙人掌中的每个叶子拆开转化成树,再求树上两点间最短边即可

考虑枚举所有点对\((i,j)\)复杂度过高,并且\(x\otimes i\)没有很好的方法合并统计

所以考虑单从\(x\)入手,按照边的长度从大到小加到途中,这样新加入的边一定是连接两侧点路径上的最短边,对于两侧的点分别用并查集维护即可

由于位运算中每一位之间是独立的,所以对每一位都开一个对应的并查集,维护连通块所有点编号中\(0\)\(1\)数量,这样就可以快速计算每次加边时带来的贡献了

#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
typedef unsigned long long ll;
const ll N=1e5+1,M=1.5*N,K=64;
struct E{
    ll next,to,w;
}e[M*2];
struct P{
    ll a,b,w,incir;
    P(){};
    P(ll _a,ll _b,ll _w,ll _incir){a=_a,b=_b,w=_w,incir=_incir;};
    friend bool operator<(P a,P b){
        return a.w<b.w;
    }
}p[M];
ll n,m,a,b,c,ans,esz,psz,head[N],ssz,fa[N],vis[N],cnt[N][K][2],insta[N];
P sta[M];
void cir(){
    ll st=sta[ssz].b,minw=__UINT64_MAX__,minidx=0;
    for (ll i=ssz;i==ssz||sta[i].b!=st;i--){
        sta[i].incir=1;
        if (sta[i].w<minw){
            minw=sta[i].w;
            minidx=i;
        }
    }
    for (ll i=ssz;i==ssz||sta[i].b!=st;i--){
        if (i!=minidx){
            psz++;
            p[psz]=sta[i];
            p[psz].w+=minw;
        }
    }
}
void dfs(ll x){
    vis[x]=1;
    insta[x]=1;
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to;
        ssz++;
        sta[ssz]=P(x,v,e[i].w,0);
        if (!vis[v]){
            vis[v]=1;
            fa[v]=x;
            dfs(v);
            if (!sta[ssz].incir){
                psz++;
                p[psz]=sta[ssz];
            }
        }else if (v!=fa[x]&&insta[v]){
            cir();
        }
        ssz--;
    }
    insta[x]=0;
}
void insert(ll a,ll b,ll w){
    esz++;
    e[esz].next=head[a];
    head[a]=esz;
    e[esz].to=b;
    e[esz].w=w;
}
ll find(ll x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
void mktree(){
    while(psz){
        ll a=p[psz].a,b=p[psz].b,w=p[psz].w;
        ll faa=find(a),fab=find(b);
        for (ll i=0;i<K;i++){
            for (ll j=0;j<=1;j++){
                ll t=(w>>i)&1;
                ans+=cnt[faa][i][j]*cnt[fab][i][j^t^1]*(1<<i);
            }
        }
        fa[fab]=faa;
        for (ll i=0;i<K;i++)
            for (ll j=0;j<=1;j++)
                cnt[faa][i][j]+=cnt[fab][i][j];
        psz--;
    }
}
int main(){
    scanf("%llu%llu",&n,&m);
    for (ll i=1;i<=m;i++){
        scanf("%llu%llu%llu",&a,&b,&c);
        insert(a,b,c);insert(b,a,c);
    }
    sta[0]=P(0,1,0,0);
    dfs(1);
    sort(p+1,p+1+psz);
    for (ll i=1;i<=n;i++){
        fa[i]=i;
        for (ll j=0;j<K;j++)
            cnt[i][j][(i>>j)&1]++;
    }
    mktree();
    printf("%llu",ans);
}

I - LOL

Tarjan求割点

#include <cstdio>
#include <iostream>
using namespace std;
const int N=1e5+1;
int idx,n,m,a,b,sz,ans,head[N],dfn[N],low[N],cut[N],ch[N];
struct E{
    int next,to;
}e[2*N];
void insert(int a,int b){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
}
void tarjan(int x){
    dfn[x]=low[x]=++idx;
    for (int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if (!dfn[v]){
            tarjan(v);
            if (low[v]>=dfn[x]) cut[x]=1;
            low[x]=min(low[x],low[v]);
            ch[x]++;
        }
        low[x]=min(low[x],dfn[v]);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        insert(a,b);insert(b,a);
    }
    for (int i=1;i<=n;i++){
        if (!dfn[i]){
            tarjan(i);
            cut[i]=(ch[i]>=2);
        }
    }
    for (int i=1;i<=n;i++) ans+=cut[i];
    printf("%d",ans);
}

J - 荣耀永远属于星辰十字军

素数=奇数+偶数

这样所有的边都连接的都是一个奇数和一个偶数,恰好构成了一个二分图

两部分分别对应超级源点和超级汇点,这样就可以用最大流解决了

跑一个流量就代表两点间连一条边,看能不能把所有点容量为2的边跑满即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <climits>
using namespace std;
const int N=301,M=20001;
int n,a[N],sz=1,head[M],s,t,w,pre[M],dis[M],incf[M],vis[M],pri[M],cnt[2];
queue<int> que;
struct E{
    int next,to,w,c;
}e[(N*N+N)*2];
void insert(int a,int b,int w,int c){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].w=w;
    e[sz].c=c;
}
void flow(int a,int b,int w,int c){
    insert(a,b,w,c);
    insert(b,a,0,-c);
}
bool spfa(){
    memset(dis,0x7f,sizeof(dis));
    que.push(s);dis[s]=0;incf[s]=INT_MAX,incf[t]=0;
    while(!que.empty()){
        int x=que.front();que.pop();
        vis[x]=0;
        for (int i=head[x];i;i=e[i].next){
            int v=e[i].to,w=e[i].w,c=e[i].c;
            if (!w||dis[v]<=dis[x]+c) continue;
            dis[v]=dis[x]+c;
            incf[v]=min(w,incf[x]);
            pre[v]=i;
            if (!vis[v]) que.push(v),vis[v]=1;
        }
    }
    return incf[t];
}
int maxflow,mincost;
void update(){
    maxflow+=incf[t];
    for (int i=t;i!=s;i=e[pre[i]^1].to){
        e[pre[i]].w-=incf[t];
        e[pre[i]^1].w+=incf[t];
        mincost+=incf[t]*e[pre[i]].c;
    }
}
int main(){
    scanf("%d",&n);
    s=0,t=1;
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        cnt[a[i]%2]++;
        if (a[i]%2){
            flow(a[i],1,2,0);
        }else{
            flow(0,a[i],2,0);
        }
    }
    for (int i=2;i<M;i++) pri[i]=1;
    for (int i=2;i<M;i++)
        if (pri[i])
            for (int j=2*i;j<M;j+=i) pri[j]=0;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            if (a[i]%2==0&&a[j]%2==1&&pri[a[i]+a[j]]){
                flow(a[i],a[j],1,0);
            }
    while(spfa()) update();
    if (cnt[0]==cnt[1]&&maxflow==2*cnt[0])
        printf("YES");
    else
        printf("NO");
    
}

K - Vingying 营救计划

L - PAFF 的演唱会

考虑到每个点的门票费,可以转化为该点到超级源点的边长

求超级源点到每个点的最短路即可

#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
priority_queue<P,vector<P>,greater<P> > que;
const ll N=2*1e5+1;
ll n,m,a,b,c,sz,f[N],head[N];
struct E{
    ll next,to,w;
}e[4*N];
void insert(ll a,ll b,ll w){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].w=w;
}
void dijkstra(){
    memset(f,0x7f,sizeof(f));
    f[0]=0;
    que.push(P(0,0));
    while(!que.empty()){
        ll x=que.top().second;que.pop();
        for (ll i=head[x];i;i=e[i].next){
            ll v=e[i].to;
            if(f[v]>f[x]+e[i].w){
                f[v]=f[x]+e[i].w;
                que.push(P(f[v],v));
            }
        }
    }
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        insert(a,b,c*2);insert(b,a,c*2);
    }
    for (ll i=1;i<=n;i++){
        scanf("%lld",&a);
        insert(0,i,a);insert(i,0,a);
    }
    dijkstra();
    for (ll i=1;i<=n;i++) printf("%lld ",f[i]);
}

M - 魔法少女小糖

N - 法外狂徒

邻接矩阵快速幂,最短距离矩阵\(f\),方案数矩阵\(g\),转移时同时计算\(f\)\(g\)

\(f[i][j]=min\{f_1[i][k]+f_2[k][j]\}\)

\(g[i][j]=\sum_{f[i][j]=f[i][k]+f[k][j]}{g_1[i][k]*g_2[k][j]}\)

对于询问\(x\to y\),最短距离为\(f[x][y]\),方案数为\(g[x][y]\)

O - 间谍

P - 矩阵乘法

欧拉回路

首先用并查集判断整个图是否连通,如果不连通则无解

如果所有点的入度和出度相等,就取其中最大的数作为起点和终点

如果恰有一个点出度=入度+1,同时恰有一个点入度=出度+1,就分别把这两个点作为起点和终点

#include <cstdio>
#include <iostream>
using namespace std;
const int N=101;
int n,x,y,a[N],b[N],sig,mx,ga,gb,ans,wrong,fa[N];
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<N;i++) fa[i]=i;
    for (int i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        a[x]++;
        b[y]++;
        mx=max(mx,x);
        int faa=find(x),fab=find(y);
        if (faa!=fab){
            fa[fab]=faa;
        }
    }
    for (int i=1;i<N;i++)
        for (int j=1;j<N;j++)
            if (a[i]&&a[j]&&find(i)!=find(j))
                wrong=1;
    for (int i=1;i<N;i++){
        if (a[i]==b[i]+1) {
            if (ga) wrong=1;
            ga=i;
        }else if (a[i]==b[i]-1) {
            if (gb) wrong=1;
            gb=i;
        }else if (a[i]!=b[i]){
            wrong=1;
        }
    }
    if (ga&&gb){
        ans=ga*gb;
    }else{
        ans=mx*mx;
    }
    if (wrong) ans=-1;
    printf("%d",ans);
}

Q - 赛马 Ⅱ

把删点的操作倒着处理,就转化成了加点的操作

魔改一下Floyd最小环即可

#include <cstdio>
#include <climits>
#include <iostream>
using namespace std;
const int N=501,INF=INT_MAX/3;
int n,m,q,t[N],w[N][N],a,b,c,f[N][N],del[N],ans=INF,lans[N];
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            w[i][j]=f[i][j]=INF;
    q--;
    for (int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        f[a][b]=f[b][a]=w[a][b]=w[b][a]=c;
    }
    for (int i=1;i<=q;i++){
        scanf("%d",&a);
        t[i]=a,del[a]=1;
    }
    for (int k=1;k<=n;k++){
        if (!del[k]){
            for (int i=1;i<=n;i++)
                for (int j=1;j<=n;j++){
                    if (i!=j&&!del[i]&&!del[j]){
                        ans=min(ans,f[i][j]+w[i][k]+w[k][j]);
                    }
                    f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
                }
        }
    }
    lans[q+1]=ans==INF?-1:ans;
    for (int z=q,k=t[z];z>=1;z--,k=t[z]){
        del[k]=0;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++){
                    if (i!=j&&!del[i]&&!del[j]){
                        ans=min(ans,f[i][j]+w[i][k]+w[k][j]);
                    }
                    f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
                }
        lans[z]=ans==INF?-1:ans;
    }
    for (int i=1;i<=q+1;i++) printf("%d\n",lans[i]);
}

R - 传送门

posted @ 2020-06-10 16:03  Byaidu  阅读(213)  评论(0编辑  收藏  举报