D3 HL 模拟赛订正

总结:我真sb,Chdy大佬拿走我饭卡没吃早饭,队列没清空导致T2炸锅,模型没掌握导致解题不畅,太菜导致被三位大佬踩;

第一题:求无向图中1号点到n号点的必经点;

其实拿到第一题,想写圆方树,后来证明这样的做法是正确的,也有大佬用支配树搞出来,但是支配树针对有向图;

但是无向图让我去写了割点,让求x到y的必经割点,不就是嗅探器吗,题解点这里

考虑如何求x,y两点之间的必经的割点。

很明显需要满足4个条件

考虑我们有一个u点,它连了一个v点,那么u点需要满足4个条件。

  1. u不是起点终点。因为题目要求在中间服务器上建嗅探器。
  2. u是割点。
  3. 终点(y)的dfn应该大于等于v点的dfn,因为要确保终点在v点或之后被访问到,即u点为必经的点。
  4. 终点(y)的low应该大于等于u点的dfn,因为要确保终点必须要经过u点。

剩下的没什么了,板子

注意我们从x点开始tarjan,就不用看x点是不是割点了。

这个输出不忽略行末空格,贼恶心;

#include<bits/stdc++.h>
using namespace std;
#define N 500001
template<typename T>inline void read(T &x) {
    x=0;
    register int f=1;
    register char ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int T,n,m,tot,x,y,lin[N],dfn[N],low[N],k,cnt,root,ans[N]; 
struct gg
{
    int y,next;
}a[N<<1];
void add(int x,int y)
{
    a[++tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    for(int i=lin[u];i;i=a[i].next)
    {
        int v=a[i].y;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(u!=x&&u!=y)
                if(dfn[u]<=low[v]&&dfn[v]<=dfn[y]&&low[y]>=dfn[x])
                       ans[++k]=u;
        }
        else low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
//    freopen("1.in","r",stdin);
    freopen("home.in","r",stdin);
    freopen("home.out","w",stdout);
    read(T);
    while(T--) {
        memset(lin,0,sizeof(lin));
        memset(dfn,0,sizeof(dfn));
        memset(lin,0,sizeof(lin));
        memset(ans,0,sizeof(ans));
        read(n); read(m);
        k=0,tot=0,cnt=0;
        for(int i=1;i<=m;i++) {
            read(x); read(y);
            if(x==y) continue;
            add(x,y); add(y,x);
        }
        x=1,y=n;
        tarjan(1);
        cout<<k<<endl;
        if(!k) {
            cout<<endl;
            continue;
        }
        sort(ans+1,ans+k+1);
        cout<<ans[1];
        for(int i=2;i<=k;i++) {
            cout<<' '<<ans[i];
        } 
        cout<<endl;
    }
    
    return 0;
} 
View Code

第二题:植物大战僵尸

好像是很多大佬做过的原题,作为一个网络流只学几天的蒟蒻还是有些东西没有想到的;

如果你要攻击植物 i,那么你就必须把 i 的右边一棵植物、以及保护着它的所有植物都一起攻击掉 。

这与最大权闭合子图中 对于每个点,从它出发,能够走到的所有点都属于闭合子图中 刚好契合。

我们可以这样建图:

植物 i 的点权设为 val[i],所有植物 i 向它的右边连边,如果一个植物 j 保护着 i , i 向 j 连边;

在这个图上求出最大权闭合子图,答案就是最大收益,也就是问题的答案。

但是,我们漏了一种情况,如果两棵植物互相保护(环),那么僵尸无论如何都无法攻击到它们。

所以我们需要拓扑找一下环;

这里感谢Chdy大佬改掉我‘TLE的代码,如果存在一个环后面连接一个环,那么我么直接将其忽略,而我原来的代码并没有这样;

同理,被环所保护的节点也无法被攻击到。

所有出现在环中以及被环保护的节点都应该被去除。我们可以先建一个反向图(这里是指相对于网络图的反向图),并进行拓扑排序,能够被拓扑排序遍历到的节点才能用来建图。

#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
#define maxn 1000100
template<typename T>inline void read(T &x) {
    x=0;
    register int f=1;
    register char ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

int n,m,s,t,tot=1;
int d[5000],lin[maxn],du[5000],vis[5000],sc[5000];
vector<int> g[5000];

struct gg {
    int y,next,flow;
}a[maxn<<1];

inline void add(int x,int y,int flow) {
    a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; a[tot].flow=flow;
    a[++tot].y=x; a[tot].next=lin[y]; lin[y]=tot; a[tot].flow=0;
}

inline bool bfs() {
    memset(d,0,sizeof(d));
    queue<int>q;
    q.push(s); d[s]=1;
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=lin[x];i;i=a[i].next) {
            int y=a[i].y;
            if (a[i].flow&&!d[y]) {
                d[y]=d[x]+1;
                if(y==t) return 1;
                q.push(y);
            }
        }
    }
    return 0;
}

inline int dinic(int x,int flow) {
    if (x==t) return flow;
    int rest=flow,k;
    for (int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if (a[i].flow&&d[y]==d[x]+1) {
            k=dinic(y,min(rest,a[i].flow));
            if (!k) d[y]=0;
            a[i].flow-=k;
            a[i^1].flow+=k;
            rest-=k;
        }
    }
    return flow-rest;
}

inline int num(int i,int j) {
    return (i-1)*m+j;
}

queue<int>Q;
inline void topsort() {
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(!du[num(i,j)]) Q.push(num(i,j)),vis[num(i,j)]=1;
    
    while(!Q.empty()) {
        int x=Q.front();
        Q.pop();
    
        for (int i=0;i<g[x].size();++i) {
            int y=g[x][i];
            --du[y];
            add(y,x,inf); 
            if (!vis[y]&&!du[y]) Q.push(y),vis[y]=1;
        }
    }
}

int main() {
    freopen("B.in","r",stdin);
    freopen("B.out","w",stdout);
    
    read(n);read(m);
    
    for(int i=1;i<=n;++i)
        for(int j=1,w;j<=m;++j) {
            read(sc[num(i,j)]),read(w);
            for(int k=1,x,y;k<=w;++k) {
                read(x);read(y);
                ++x,++y;
                g[num(i,j)].push_back(num(x,y));
                ++du[num(x,y)];
            }
            if(j<m) {
                g[num(i,j+1)].push_back(num(i,j));
                ++du[num(i,j)];
            }
        }
    s=0,t=1010;
    int sum=0;    
    topsort();
    
    
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j) {
            int x=num(i,j);
            if(!vis[x]) continue;
            if(sc[x]>=0) add(s,x,sc[x]),sum+=sc[x];
            else add(x,t,-sc[x]);
        }
    
    int max_flow=0,flow=0;
    while(bfs())    max_flow+=dinic(s,inf);
    printf("%d
",sum-max_flow);
    return 0;
}
View Code

第三题:

题目描述

给定一个长度n的整数序列hi

需要通过一些操作修改序列成为单调不降序列,即hihi+1

给定m种操作,

第i种操作为付出ci的代价将一段长为li的连续子序列+1或-1(输入会给出)

不限制每种操作的使用次数,

hi可以被修改为任意整数,求最小代价,无解输出-1

输入格式

第一行两个整数n,m

第二行n个整数hi

接下来m行,每行的格式为opti,li,ci,其中opti是一个字符表示+1还是-1

输出格式

输出一行一个整数代表最小代价,若无解输出-1;

题解:

知道考网络流才往这个模型上想,很显然是个费用流,感觉石神说的对,网络流就考个建图;

首先需要单调不下降,所以我们对原序列进行差分,一定都是非负数;

所以考虑这样建图;

将差分后的序列,进行判断,如果>0,我们将源点和它相连,容量为差分后序列对应的大小,花费为0;我们期望

如果小于零我们将其连向汇点,代价为对应大小的绝对值,花费为0,我们还要记录一下负数绝对值的累加和sum,因为我们期望的是满流;

对于一个操作,我们只需要暴力连上他所能连接的边(在这个区间)就可以了,具体方向的话,我们思考之后发现;

如果是-1的操作,我们将j连向j+L,容量为inf,费用为c,因为我们可以使用无数次;

如果是+1的操作,我们从j+L连向j,容量为inf,费用为c,都别忘了处理反向边;

跑费用流即可,判断无解就是看最大流是否等于sum即可;

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 1000010 
template<typename T>inline void read(T &x)
{
    x=0;
    register int f=1;
    register char ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

int tot=1,n,m,s,t,sum,l,c;
int pre[N],lin[N],vis[N],dis[N],incf[N],h[N],g[N];
long long max_flow,ans;
struct gg {
    int y,next,flow,c;
}a[N<<1];

inline void add(int x,int y,int flow,int c) {
    a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; a[tot].c=c; a[tot].flow=flow;
    a[++tot].y=x; a[tot].next=lin[y]; lin[y]=tot; a[tot].c=-c; a[tot].flow=0;
}

inline bool spfa()
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    queue<int>q;
    q.push(s);
    dis[s]=0,vis[s]=1,incf[s]=1<<30;
    while(!q.empty()) {
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=lin[x];i;i=a[i].next) {
            if(!a[i].flow) continue;
            int y=a[i].y;
            if(dis[y]>dis[x]+a[i].c) {
                dis[y]=dis[x]+a[i].c;
                incf[y]=min(incf[x],a[i].flow);
                pre[y]=i;
                if(!vis[y]) q.push(y),vis[y]=1;
            }
        }
    }
    if(dis[t]==inf) return false;
    else return true;
}

inline void update() {
    int x=t;
    while(x!=s) {
        int i=pre[x];
        a[i].flow-=incf[t];
        a[i^1].flow+=incf[t];
        x=a[i^1].y;
    }
    max_flow+=incf[t];
    ans+=dis[t]*incf[t];
}

int main(){
    //freopen("1.in","r",stdin);
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    read(n); read(m);
    s=n+2,t=n+3; 
    for(int i=1;i<=n;i++)
        read(h[i]);
    for(int i=1;i<=n;i++) 
        g[i]=h[i]-h[i-1];//搞一个差分序列; 
    g[1]=g[n+1]=inf;//特殊处理1和n的情况 
    for(int i=1;i<=n+1;i++)
        if(g[i]>0) add(s,i,g[i],0);
        else if(g[i]<0) add(i,t,-g[i],0),sum-=g[i];
    for(int i=1;i<=m;i++) {
        char opt[2];scanf("%s",opt);
        read(l);read(c);
        for(int j=1; j+l<=n+1; ++j)
            if(opt[0]=='-') add(j,j+l,inf,c);
            else add(j+l,j,inf,c);
    }
    while(spfa()) update();
    if(max_flow!=sum) ans=-1;
    cout<<ans<<endl;
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/Tyouchie/p/11153855.html