总结:我真sb,Chdy大佬拿走我饭卡没吃早饭,队列没清空导致T2炸锅,模型没掌握导致解题不畅,太菜导致被三位大佬踩;
第一题:求无向图中1号点到n号点的必经点;
其实拿到第一题,想写圆方树,后来证明这样的做法是正确的,也有大佬用支配树搞出来,但是支配树针对有向图;
但是无向图让我去写了割点,让求x到y的必经割点,不就是嗅探器吗,题解点这里;
考虑如何求x,y两点之间的必经的割点。
很明显需要满足4个条件
考虑我们有一个u点,它连了一个v点,那么u点需要满足4个条件。
- u不是起点终点。因为题目要求在中间服务器上建嗅探器。
- u是割点。
- 终点(y)的dfn应该大于等于v点的dfn,因为要确保终点在v点或之后被访问到,即u点为必经的点。
- 终点(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; }
第二题:植物大战僵尸
好像是很多大佬做过的原题,作为一个网络流只学几天的蒟蒻还是有些东西没有想到的;
如果你要攻击植物 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; }
第三题:
题目描述
给定一个长度n的整数序列hi,
需要通过一些操作修改序列成为单调不降序列,即hi≤hi+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; }