【学习笔记】网络流最大流(最小割)番外篇:题目&题解
点击查看目录
注:我前几篇题解的当前弧优化写挂了,建议不理。
-
当容量模型作为点的性质出现的时候,可以考虑拆点法。
-
当出现时间问题,可以考虑通过二分将时间转换为容量属性。
-
有些问题可以预处理出,比如说距离汇点的距离,而不用连其无用的路径经过的点的边。
-
当遇到棋盘骨牌问题,可以考虑染色。
-
当遇到“非此即彼”的选择边的问题,可以考虑转换为最小割问题。
-
如果跑 TLE 了,检查最大值是否足够大以及当前弧是否正确。
[SCOI2007] 蜥蜴
前排提示:题目中的“距离”指欧几里德距离而非曼哈顿距离。
折叠题干
[SCOI2007] 蜥蜴
题目描述
在一个 \(r\) 行 \(c\) 列的网格地图中有一些高度不同的石柱,第 \(i\) 行 \(j\) 列的石柱高度为 \(h_{i,j}\)。
一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外。
每行每列中相邻石柱的距离为 \(1\),蜥蜴的跳跃距离是 \(d\),即蜥蜴可以跳到平面距离不超过 \(d\) 的任何一个石柱上。
石柱都不稳定,每次当蜥蜴跳跃时,所离开的石柱高度减 \(1\)(如果仍然落在地图内部,则到达的石柱高度不变)。
如果该石柱原来高度为 \(1\),则蜥蜴离开后消失,以后其他蜥蜴不能落脚。
任何时刻不能有两只蜥蜴在同一个石柱上。
输入格式
输入格式
输入第一行为三个整数 \(r,c,d\),即地图的规模与最大跳跃距离。
接下来 \(r\) 行每行 \(c\) 个数字为石柱的初始状态,\(0\) 表示没有石柱,否则表示石柱的初始高度 \(h_{i,j}\)。
接下来 \(r\) 行每行 \(c\) 个字符为蜥蜴位置,L
表示蜥蜴,.
表示没有蜥蜴。
输出格式
输出仅一行,包含一个整数,即无法逃离的蜥蜴总数的最小值。
样例 #1
样例输入 #1
5 8 2
00000000
02000000
00321100
02000000
00000000
........
........
..LLLL..
........
........
样例输出 #1
1
提示
对于 \(100\%\) 的数据满足:\(1\le r,c\le20\),\(1\le d\le 4\),\(1\le h\le 3\)。
求无法逃离的蜥蜴的最小值 \(\longrightarrow\) 求可以逃离的蜥蜴的最大值 \(\longrightarrow\) 可以走到汇点的最大流。
所以是最大流题目。
考虑建模,每次走一个蜥蜴就会降低一个高度的石柱显然是容量,而容量是边的属性,而题目中则描述成了点。
于是我们可以运用网络流题目中大概比较经典的 拆点法,即将一个点拆成一个入点,一个出点,入点与出点之间的边就是容量,这样就将点的属性转化到了边上。
那么这道题的建模就明了了:
-
超级源点到有蜥蜴的点,容量为 \(1\)。
-
各个点到其可以到达的点的入点(超出边界的即超级汇点),容量为 \(inf\)。
-
各个点的入点到出点,容量为其高度。
而高度为 \(0\) 的可以不计,所以跑得比较快。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
typedef long long ll;
typedef unsigned char byte;
typedef unsigned long long ull;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48){ if(c=='-')f=-1;c=getchar(); }
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const int maxc=50,maxn=1e4+5,inf=0x3f3f3f3f;
int r,c,n,d,S,T,ans,dep[maxn],cur[maxn];
char mat1[maxc][maxc],mat2[maxc][maxc];
int head[maxn<<1],t=1;
struct Edge{ int v,w;int next; };Edge e[maxn<<1];
il void add_edge(int u,int v,int w){
e[++t].v=v;e[t].w=w;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].w=0;e[t].next=head[v];head[v]=t;
}
il int getid(int x,int y){ return (x-1)*c+y; }
il int dis(int x1,int y1,int x2,int y2){ return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2); }
il bool bfs(){
for(rg i=0;i<=(n<<1)+1;++i) dep[i]=0;
std::queue<int>q;
while(!q.empty()) q.pop();
q.push(S);dep[S]=1;cur[S]=head[S];
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].w>0){
q.push(to);cur[to]=head[to];dep[to]=dep[now]+1;
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,ll flow){
if(now==T) return flow;
ll res=0,rest;
for(rg i=cur[now];i && flow;i=e[i].next){
cur[now]=i;int to=e[i].v;
if(e[i].w>0 && dep[to]==dep[now]+1){
rest=dfs(to,Min(flow,e[i].w));
if(rest==0) dep[to]=0;
e[i].w-=rest;e[i^1].w+=rest;
res+=rest;flow-=rest;
}
}
return res;
}
il void input(){
r=read(),c=read(),d=read();n=r*c;S=0,T=(n<<1)+1;
for(rg i=1;i<=r;++i){
scanf("%s",mat1[i]+1);
for(rg j=1;j<=c;++j)
if(mat1[i][j]!='0'){
add_edge(getid(i,j),getid(i,j)+n,mat1[i][j]-'0');
if(i-d<1 || i+d>r || j-d<1 || j+d>c) add_edge(getid(i,j)+n,T,inf);
}
}
for(rg i=1;i<=r;++i){
scanf("%s",mat2[i]+1);
for(rg j=1;j<=c;++j) if(mat2[i][j]=='L') add_edge(S,getid(i,j),1),++ans;
}
for(rg x1=1;x1<=r;++x1) for(rg y1=1;y1<=c;++y1)
for(rg x2=1;x2<=r;++x2) for(rg y2=1;y2<=c;++y2)
if(mat1[x1][y1]!='0' && mat1[x2][y2]!='0') if(dis(x1,y1,x2,y2)<=d*d) add_edge(getid(x1,y1)+n,getid(x2,y2),inf);
}
int main(){
freopen("lizard.in","r",stdin);
input();
while(bfs()) ans-=dfs(S,inf);
printf("%d\n",ans);
return 0;
}
[SDOI2009] 晨跑
折叠题干
[SDOI2009] 晨跑
题目描述
Elaxia 最近迷恋上了空手道,他为自己设定了一套健身计划,比如俯卧撑、仰卧起坐等等,不过到目前为止,他坚持下来的只有晨跑。
现在给出一张学校附近的地图,这张地图中包含 \(N\) 个十字路口和 \(M\) 条街道,Elaxia 只能从 一个十字路口跑向另外一个十字路口,街道之间只在十字路口处相交。
Elaxia 每天从寝室出发跑到学校,保证寝室编号为 \(1\),学校编号为 \(N\)。
Elaxia 的晨跑计划是按周期(包含若干天)进行的,由于他不喜欢走重复的路线,所以在一个周期内,每天的晨跑路线都不会相交(在十字路口处),寝室和学校不算十字路口。
Elaxia 耐力不太好,他希望在一个周期内跑的路程尽量短,但是又希望训练周期包含的天数尽量长。
除了练空手道,Elaxia 其他时间都花在了学习和找 MM 上面,所有他想请你帮忙为他设计 一套满足他要求的晨跑计划。
存在 \(1\rightarrow n\) 的边存在。这种情况下,这条边只能走一次。
输入格式
第一行两个整数 \(N,M\),表示十字路口数和街道数。
接下来 \(M\) 行,每行 \(3\) 个数 \(a,b,c\),表示路口 \(a\) 和路口 \(b\) 之间有条长度为 \(c\) 的街道(单向)。
输出格式
一行两个整数,最长周期的天数和满足最长天数的条件下最短的路程度。
样例 #1
样例输入 #1
7 10
1 2 1
1 3 1
2 4 1
3 4 1
4 5 1
4 6 1
2 5 5
3 6 6
5 7 1
6 7 1
样例输出 #1
2 11
提示
- 对于 \(30\%\) 的数据,\(N\le 20\),\(M \le 120\)。
- 对于 \(100\%\) 的数据,\(N\le 200\),\(M\le 2\times 10^4\)。
这道题显然是费用流,因为每条边只希望走一次,那么这就是容量,而希望路程最小就是费用最小,故是最小费用最大流板子题。
发现题干中说“每天的晨跑路线都不会相交(在十字路口处)”,所以所谓的“每条边只走一次”题目中表达的含义其实是“每个点只走一次”。
如果我们要建立模型,这本来应该是边的属性,所以我们拆点,拆成一个入点一个出点,入点和出点之间的边容量为 \(1\)。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cout std::cout
#define cerr std::cerr
#define push_back emplace_back
#define make_pair std::make_pair
#define endl '\n'
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
const ff eps=1e-8;
int Max(int x,int y){ return x<y?y:x; }
int Min(int x,int y){ return x<y?x:y; }
int Abs(int x){ return x>0?x:-x; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48) { if(c=='-')f=-1;c=getchar(); }
while(c>47) x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}const int maxn=405,maxm=4e4+5,inf=0x3f3f3f3f;
int n,m,S,T,dis[maxn],incf[maxn],pre[maxn],maxflow,ans;
int head[maxm<<1],t=1;
bool vis[maxn];
struct Edge{ int v,c,w;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c,int w){
e[++t].v=v;e[t].c=c;e[t].w=w;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].w=-w;e[t].next=head[v];head[v]=t;
}
il bool SPFA(){
std::queue<int> q;while(!q.empty()) q.pop();
for(rg i=0;i<=(n<<1);++i) dis[i]=inf,vis[i]=false;
dis[S]=0;vis[S]=true;q.push(S);incf[S]=inf;
while(!q.empty()){
int now=q.front();q.pop();vis[now]=false;
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(e[i].c && dis[to]>dis[now]+e[i].w){
incf[to]=Min(incf[now],e[i].c);
dis[to]=dis[now]+e[i].w;pre[to]=i;
if(!vis[to]) q.push(to),vis[to]=true;
}
}
}
if(dis[T]==inf) return false;
return true;
}
il void update(){
int now=T;
while(now!=S){
int i=pre[now];
e[i].c-=incf[T];
e[i^1].c+=incf[T];
now=e[i^1].v;
}
maxflow+=incf[T];ans+=dis[T]*incf[T];
}
il void input(){
n=read(),m=read();S=1+n,T=n;int a,b,c;
for(rg i=1;i<=n;++i) add_edge(i,i+n,1,0);
for(rg i=1;i<=m;++i){
a=read(),b=read(),c=read();
add_edge(a+n,b,1,c);
}
}
int main(){
freopen("morningrun.in","r",stdin);
input();
while(SPFA()) update();
printf("%d %d",maxflow,ans);
return 0;
}
[ABC317G] Rearranging
折叠题面
[ABC317G] Rearranging
题目描述
$ N $ 行 $ M $ 列のグリッドがあります。上から $ i $ 行目左から $ j $ 列目のマスには整数 $ A_{i,j} $ が書かれています。
ここで、グリッドのマスに書かれている計 $ NM $ 個の整数は $ 1,\ldots,N $ をちょうど $ M $ 個ずつ含みます。
あなたは次の手順でマスに書かれた数を入れ替える操作を行います。
- $ i=1,\ldots,N $ の順に次を行う。
- $ i $ 行目に書かれた数を自由に並び替える。すなわち、$ 1,\ldots,M $ の並び替えである長さ $ M $ の数列 $ P=(P_{1},\ldots,P_{M}) $ を自由に選び、$ A_{i,1},\ldots,A_{i,M} $ を 同時に $ A_{i,P_{1}},\ldots,A_{i,P_{M}} $ に置き換える。
あなたの目的は、操作後に全ての列が $ 1,\ldots,N $ を $ 1 $ つずつ含むようにすることです。そのようなことが可能であるか判定し、可能であれば操作後のグリッドの状態を出力してください。
翻译:(提供:我)
现有一个 \(N\) 行 \(M\) 列的矩阵,从上往下数第 \(i\) 行,从左往右数第 \(j\) 列的元素是 \(A_{i,j}\)。
对于 \(i=1,\ldots,n\),你可以自由排列第 \(i\) 行的数字。
要求每一列包含 \(1,\ldots,N\) 一次(不要求顺序,包含即可),若可能,输出 Yes
,并打印结果表格,否则,输出 No
。
输入格式
入力は以下の形式で標準入力から与えられる。
$ N $ $ M $ $ A_{1,1} $ $ \ldots $ $ A_{1,M} $ $ \vdots $ $ A_{N,1} $ $ \ldots $ $ A_{N,M} $
输出格式
操作により全ての列が $ 1,\ldots,N $ を $ 1 $ つずつ含むようにするのが不可能ならば No
と出力せよ。
可能であるとき、$ 1 $ 行目に Yes
と出力し、続く $ N $ 行に、全ての列が $ 1,\ldots,N $ を $ 1 $ つずつ含むように操作したあとのグリッドの状態を次の形式で出力せよ。
グリッドの上から $ i $ 行目左から $ j $ 列目のマスに書かれた数を $ B_{i,j} $ とする。各 $ 1\leq\ i\ \leq\ N $ について $ i+1 $ 行目に $ B_{i,1},\ldots,B_{i,M} $ をこの順に空白区切りで出力せよ。
答えが複数存在する場合、どれを出力しても正解とみなされる。
样例 #1
样例输入 #1
3 2
1 1
2 3
2 3
样例输出 #1
Yes
1 1
3 2
2 3
样例 #2
样例输入 #2
4 4
1 2 3 4
1 1 1 2
3 2 2 4
4 4 3 3
样例输出 #2
Yes
1 4 3 2
2 1 1 1
4 2 2 3
3 3 4 4
提示
制約
- $ 1\ \leq\ N,M\ \leq\ 100 $
- $ 1\ \leq\ A_{i,j}\ \leq\ N $
- 入力は全て整数である
- $ NM $ 個の数 $ A_{1,1},\ldots,A_{N,M} $ は $ 1,\ldots,N $ をそれぞれちょうど $ M $ 個ずつ含む
Sample Explanation 1
この他、以下の出力も正解とみなされる。 Yes 1 1 2 3 3 2
解题:
其实官方题解写的还是很清楚的。(但是思路好难想啊~)
我们建立二分图的子集,左边的子集代表 \(1,\ldots,n\) 行,右边的子集代表 \(1,\ldots,n\) 个数,共 \(2n\) 个点,显然子集内点没有连边,它是二分图。
而该行内有哪些数就从左子集的代表行的点连向右子集代表数的点连边,有几个连几次。
如样例 #1:
input:
3 2
1 1
2 3
2 3
因此可以找一个图的最大匹配,对于某一列,我们就得到了其数,如#9999ff 色边,其为一列:
1
2
3
然后再删去已经找到过的边,继续找最大匹配,即可。
output:
Yes
1 1
3 2
2 3
Miku's code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register int
#define MYMAX 20070831
typedef long double llf;
typedef long long ll;
typedef pair<int,int> PII;
const double eps=1e-8;
#if ONLINE_JUDGE
char in[1<<20],*p1=in,*p2=in;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#endif
inline int read(){
char c=getchar();
int x=0,f=1;
while(c<48)<%if(c=='-')f=-1;c=getchar();%>
while(c>47)x=(x*10)+(c^48),c=getchar();
return x*f;
}const int maxn=205,maxm=40050;
int now[maxm<<1],head[maxm<<1],tt=1;
#define next Miku
struct edge{
int v,w,next;
};edge e[maxm<<1];
il void add_edge(int u,int v,int w){
e[++tt].v=v;e[tt].w=w;e[tt].next=head[u];head[u]=tt;
e[++tt].v=u;e[tt].w=0;e[tt].next=head[v];head[v]=tt;
}
int s,t,savt;
int n,m,dep[maxn],ans[maxn][maxn];
il void clear(){
for(rg i=1;i<=(n<<1|1);++i) dep[i]=0;
}
il bool bfs(){
clear();
queue<int> q;
while(!q.empty()) q.pop();
q.push(s);
dep[s]=1;
now[s]=head[s];
while(!q.empty()){
int x=q.front();q.pop();
for(rg i=head[x];i;i=e[i].next){
int to=e[i].v;
if(e[i].w>0 && dep[to]==0){
q.push(to);
now[to]=head[to];
dep[to]=dep[x]+1;
if(to==t) return true;
}
}
}
return false;
}
int dfs(int x,int flow){
if(x==t) return flow;
int rest,res=0;
for(rg i=head[x];i;i=e[i].next){
int to=e[i].v;
if(e[i].w>0 && dep[to]==dep[x]+1){
rest=dfs(to,min(flow,e[i].w));
if(rest==0) dep[to]=0;
e[i].w-=rest;
e[i^1].w+=rest;
res+=rest;
flow-=rest;
}
}
return res;
}
il int get_maxflow(){
int ans=0;
while(bfs())<% ans+=dfs(s,MYMAX); %>
return ans;
}
il void input(){
n=read(),m=read();
t=(n<<1|1);
int num;
for(rg i=1;i<=n;++i){
for(rg j=1;j<=m;++j){
num=read();
add_edge(i,n+num,1);
}
}
savt=tt;
for(rg i=1;i<=n;++i)<% add_edge(s,i,1);add_edge(n+i,t,1); %>
}
int main(){
input();
for(rg j=1;j<=m;++j){
int flow=get_maxflow();
if(flow!=n) <% puts("No");return 0; %>
for(rg i=3;i<=savt;i+=2){
//网络流将匹配转为反边,枚举反边
if(e[i].w){
int u=e[i].v,to=e[i^1].v;
// cout<<"i="<<i<<"; u="<<u<<"; j="<<j<<"; to="<<to<<endl;
ans[u][j]=to-n;
e[i].w=0;
}
}
for(rg i=savt+2;i<=tt;i+=2){
if(e[i].w){
e[i^1].w=1;
e[i].w=0;
}
}
}
puts("Yes");
for(rg i=1;i<=n;++i){
for(rg j=1;j<=m;++j) printf("%d ",ans[i][j]);
putchar('\n');
}
return 0;
}
[SDOI2015] 星际战争
折叠题干
[SDOI2015] 星际战争
题目描述
\(3333\) 年,在银河系的某星球上,X 军团和 Y 军团正在激烈地作战。
在战斗的某一阶段,Y 军团一共派遣了 \(N\) 个巨型机器人进攻 X 军团的阵地,其中第 \(i\) 个巨型机器人的装甲值为 \(A_i\)。当一个巨型机器人的装甲值减少到 \(0\) 或者以下时,这个巨型机器人就被摧毁了。
X 军团有 \(M\) 个激光武器,其中第 \(i\) 个激光武器每秒可以削减一个巨型机器人 \(B_i\) 的装甲值。激光武器的攻击是连续的。
这种激光武器非常奇怪,一个激光武器只能攻击一些特定的敌人。Y 军团看到自己的巨型机器人被 X 军团一个一个消灭,他们急需下达更多的指令。
为了这个目标,Y 军团需要知道 X 军团最少需要用多长时间才能将 Y 军团的所有巨型机器人摧毁。但是他们不会计算这个问题,因此向你求助。
输入格式
题面修理器
第一行,两个整数,N,M。
第二行,N个整数,A1,A2...AN。
第三行,M个整数,B1,B2...BM。
接下来的M行,每行N个整数,这些整数均为0或者1。这部分中的第i行的第j个整数为0表示第i个激光武器不可以攻击第j个巨型机器人,为1表示第i个激光武器可以攻击第j个巨型机器人。
专有名词(用,隔开)
第一行,两个整数,\(N,M\)。
第二行,\(N\) 个整数,\(A_1,A_2 \cdots A_N\)。
第三行,\(M\) 个整数,\(B_1,B_2 \cdots B_M\)。
接下来的 \(M\) 行,每行 \(N\) 个整数,这些整数均为 \(0\) 或者 \(1\)。这部分中的第 \(i\) 行的第 \(j\) 个整数为 \(0\) 表示第 \(i\) 个激光武器不可以攻击第 \(j\) 个巨型机器人,为 \(1\) 表示第 \(i\) 个激光武器可以攻击第 \(j\) 个巨型机器人。
输出格式
一行,一个实数,表示 X 军团要摧毁 Y 军团的所有巨型机器人最少需要的时间。输出结果与标准答案的绝对误差不超过 \(10^{-3}\) 即视为正确。
样例 #1
样例输入 #1
2 2
3 10
4 6
0 1
1 1
样例输出 #1
1.300000
提示
【样例说明1】
战斗开始后的前 \(0.5\) 秒,激光武器 \(1\) 攻击 \(2\) 号巨型机器人,激光武器 \(2\) 攻击 \(1\) 号巨型机器人。\(1\) 号巨型机器人被完全摧毁,\(2\) 号巨型机器人还剩余 \(8\) 的装甲值;
接下来的 \(0.8\) 秒,激光武器 \(1\) 、 \(2\) 同时攻击 \(2\) 号巨型机器人。\(2\) 号巨型机器人被完全摧毁。
【数据范围】
对于全部的数据,\(1 \le N,M \le 50\),\(1 \le A_i \le 10^5\),\(1 \le B_i \le 1000\),输入数据保证 X 军团一定能摧毁 Y 军团的所有巨型机器人。
[spj]
显然激光武器和与其对应的机器人有连边,再建立超级源点超级汇点一跑就行。
但是问题在于求时间,时间为实数。
考虑将时间变成容量问题,我们的最大流求的是单位时间内的最大流量,那么只要给容量乘上时间 \(t\),就成了时间 \(t\) 内的最大流量。
因为 \(t\) 为实数,精度要求 \(10^{-3}\),所以我们使得容量和血量乘上 \(10^5\) 即可。
总结建模:
-
所有激光武器和超级源点建 \(t\cdot b_i\) 的边。
-
激光武器和对应机器人之间建 \(inf\) 的边。
-
机器人和超级汇点建 \(a_i\) 的边。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define cout std::cout
#define endl '\n'
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48){ if(c=='-')f==-1;c=getchar(); }
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const int maxn=210,maxm=4005,inf=0x3f3f3f3f3f3f3f;
int n,m,S,T,a[maxn],HP,b[maxn],cont[maxn][maxn],ans;
int dep[maxn],cur[maxn];
int t=1,head[maxm];
struct Edge{ int v,c;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
il bool bfs(){
for(rg i=0;i<=n+m+1;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
q.push(S);dep[S]=1;cur[S]=head[S];
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].c>0){
q.push(to);cur[to]=head[to];dep[to]=dep[now]+1;
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,int flow){
if(now==T) return flow;
ll res=0,rest;
for(rg i=cur[now];i && flow;i=e[i].next){
cur[now]=i;int to=e[i].v;
if(e[i].c>0 && dep[to]==dep[now]+1){
rest=dfs(to,Min(flow,e[i].c));
if(rest==0) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
}
}
return res;
}
il bool check(int tim){
for(rg i=0;i<=n+m+1;++i) head[i]=0;t=1;ll res=0;
for(rg i=1;i<=m;++i) add_edge(S,i,tim*b[i]);
for(rg i=1;i<=m;++i) for(rg j=1;j<=n;++j) if(cont[i][j]&1) add_edge(i,j+m,inf);
for(rg i=1;i<=n;++i) add_edge(i+m,T,a[i]);
while(bfs()) res+=dfs(S,inf);
return res>=HP;
}
il void input(){
n=read(),m=read();S=0,T=n+m+1;
for(rg i=1;i<=n;++i) a[i]=read()*100000,HP+=a[i];for(rg i=1;i<=m;++i) b[i]=read();
for(rg i=1;i<=m;++i) for(rg j=1;j<=n;++j) cont[i][j]=read();
}
signed main(){
freopen("war.in","r",stdin);
input();
int l=0,r=inf;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%.5lf\n",1.0*ans/100000);
return 0;
}
P4311 士兵占领
感觉这道题还挺典的。
折叠题干
士兵占领
题目描述
有一个 \(M \times N\) 的棋盘,有的格子是障碍。现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵。我们称这些士兵占领了整个棋盘当满足第 \(i\) 行至少放置了 \(L_i\) 个士兵,第 \(j\) 列至少放置了 \(C_j\) 个士兵。现在你的任务是要求使用最少个数的士兵来占领整个棋盘。
输入格式
第一行两个数 \(M, N, K\) 分别表示棋盘的行数,列数以及障碍的个数。
第二行有 \(M\) 个数表示 \(L_i\)。
第三行有 \(N\) 个数表示 \(C_i\)。
接下来有 \(K\) 行,每行两个数 \(X, Y\) 表示 \((X, Y)\) 这个格子是障碍。
输出格式
输出一个数表示最少需要使用的士兵个数。如果无论放置多少个士兵都没有办法占领整个棋盘,输出”JIONG!” (不含引号)
样例 #1
样例输入 #1
4 4 4
1 1 1 1
0 1 0 3
1 4
2 2
3 3
4 3
样例输出 #1
4
提示
对于 \(100 \%\) 的数据,\(1 \le M, N \le 100\),\(0 \le K \le M \cdot N\)。
这道题问题在于存在一个“上界”和“下界”:每一行(列)的上界最多放上为 \(n/(m)\) 个士兵,下界为至少放 \(l_i/(r_i)\) 个士兵。要求求总共至少放多少个士兵。
考虑转换这个问题,将“至少”转换为“至多”,这样就可以跑最大流。
于是我们设已经放上所有的士兵,问去掉最多多少个士兵可以满足“上界”和“下界”。
既然已经成了“去掉多少士兵”,那么我们的上界就成了最多去掉 \(n-l_i/(m-r_i)\),下界就成了最少去掉 \(0\) 个士兵。
然后对于那些不可放置的“障碍点”,我们当成“必须去掉的士兵”,对于某一列(行)为 \(num\),那么上界就是 \(n-num-l_i/(m-num-r_i)\),下界就是 \(num\)。
下界是不好处理的,我们考虑直接在开始就去掉,有 \(n\cdot m\) 个点,直接减去必须去掉的士兵,这样我们需要考虑的就只有上界了。
总结就是:
-
对于每一列,建立一个点,连接源点 \(S\) 和每个点,容量为 \(n-l_i-num\)。
-
对于每一行,建立一个点,连接汇点 \(T\) 和每个点,容量为 \(m-r_i-num\)。
-
对于每一列,其在某一行的点可以放置士兵,连接这一列和这一行,容量为 \(1\)。
跑最大流,我们得到最多去掉多少士兵,拿可以放置的格子减去即可。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cout std::cout
#define cerr std::cerr
#define push_back emplace_back
#define make_pair std::make_pair
#define endl '\n'
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
const ff eps=1e-8;
int Max(int x,int y){ return x<y?y:x; }
int Min(int x,int y){ return x<y?x:y; }
int Abs(int x){ return x>0?x:-x; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48) { if(c=='-')f=-1;c=getchar(); }
while(c>47) x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}const int maxn=205,maxm=4e4+5,inf=0x3f3f3f3f;
int n,m,sum,k,S,T,l[maxn],r[maxn],dep[maxn],cur[maxn],ans;
bool cant[maxn][maxn],mj;
int head[maxm],t=1;
struct Edge{ int v,c;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
il bool bfs(){
for(rg i=0;i<=n+m+1;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
q.push(S);cur[S]=head[S];dep[S]=1;
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].c>0){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,int flow){
if(now==T) return flow;
int res=0,rest;
for(rg i=cur[now];i;i=e[i].next){
cur[now]=i;int to=e[i].v;
if(e[i].c>0 && dep[to]==dep[now]+1){
rest=dfs(to,Min(flow,e[i].c));
if(rest==0) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
}
}
return res;
}
il void input(){
m=read(),n=read(),k=read();S=0,T=n+m+1,sum=n*m;int x,y;
for(rg i=1;i<=m;++i) l[i]=read();
for(rg i=1;i<=n;++i) r[i]=read();
for(rg i=1;i<=k;++i) x=read(),y=read(),--sum,cant[x][y]=1,++l[x],++r[y];
for(rg i=1;i<=m;++i){
if(n-l[i]<0) return mj=true,void();
add_edge(S,i,n-l[i]);
}
for(rg i=1;i<=n;++i){
if(m-r[i]<0) return mj=true,void();
add_edge(i+m,T,m-r[i]);
}
for(rg i=1;i<=m;++i) for(rg j=1;j<=n;++j) if(!cant[i][j] ) add_edge(i,j+m,1);
}
int main(){
freopen("soldiers.in","r",stdin);
input();
if(mj) return puts("JIONG!"),0;
while(bfs()) ans+=dfs(S,inf);
printf("%d\n",sum-ans);
return 0;
}
[HNOI2007] 紧急疏散EVACUATE
折叠题干
[HNOI2007] 紧急疏散EVACUATE
题目描述
发生了火警,所有人员需要紧急疏散!假设每个房间是一个N M的矩形区域。每个格子如果是'.',那么表示这是一块空地;如果是'X',那么表示这是一面墙,如果是'D',那么表示这是一扇门,人们可以从这儿撤出房间。已知门一定在房间的边界上,并且边界上不会有空地。最初,每块空地上都有一个人,在疏散的时候,每一秒钟每个人都可以向上下左右四个方向移动一格,当然他也可以站着不动。疏散开始后,每块空地上就没有人数限制了(也就是说每块空地可以同时站无数个人)。但是,由于门很窄,每一秒钟只能有一个人移动到门的位置,一旦移动到门的位置,就表示他已经安全撤离了。现在的问题是:如果希望所有的人安全撤离,最短需要多少时间?或者告知根本不可能。
输入格式
输入文件第一行是由空格隔开的一对正整数N与M,3<=N <=20,3<=M<=20,以下N行M列描述一个N M的矩阵。其中的元素可为字符'.'、'X'和'D',且字符间无空格。
输出格式
只有一个整数K,表示让所有人安全撤离的最短时间,如果不可能撤离,那么输出'impossible'(不包括引号)。
样例 #1
样例输入 #1
5 5
XXXXX
X...D
XX.XX
X..XX
XXDXX
样例输出 #1
3
提示
2015.1.12新加数据一组,鸣谢1756500824
C++语言请用scanf("%s",s)读入!
看到时间,考虑二分时间转化为边的属性。
看到容量是点的属性(每一秒钟只能有一个人移动到门的位置),考虑拆点。
由于每个“空地”拆点后入点和出点连接的边容量为无限,所以可以不拆。
而“门”拆点后入点和出点的连接的边容量应为时间 \(t\)。
但是这违背了“每一秒钟只能有一个人移动到门的位置”的要求,如果我们这么建,可能在某一秒钟就有多人移动到了门的位置,只是满足了这段时间内“平均”有一人移动到门的位置而已。
所以我们把门拆成 \(t\) 个点,表示每一秒的门,由前一秒连向下一秒,容量为 \(inf\),表示可以停留无限的人,每个门连向汇点,容量为 \(1\)。
最后考虑如何模拟人的行走过程,由于每块空地初始只有一个人,而可以站无限的人,所以人到达门的路径为最短路时最优,区别在于选择门,考虑 \(\mathtt{BFS}\) 预处理每块空地与每个门的距离 \(dis\),这代表到达门的时间,所以对 \(dis\) 时间上门对应的拆点连边,容量为 \(1\)。
由于我们预处理了距离,所以必须考虑有些门不可达的情况,对于 Hack:
input:
5 4
XDXX
DXXX
D..X
X..X
XXXX
output:
4
来说,\((1,2)\),\((2,1)\),均不会到达,判断周围是否有空地即可。
总结建模:
-
每个门拆 \(t\) 个点,每个点连向汇点 \(T\),容量为 \(1\),表示可以逃离,上一时间向下一时间连边,容量为 \(inf\),表示可以停留。
-
源点向每个人(每块空地)连边,容量为 \(1\)。
-
每个人(每块空地)向每个门可达的对应时间拆点连边,容量为 \(1\)。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cout std::cout
#define cerr std::cerr
#define push_back emplace_back
#define endl '\n'
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
const ff eps=1e-8;
int Max(int x,int y){ return x<y?y:x; }
int Min(int x,int y){ return x<y?x:y; }
int Abs(int x){ return x>0?x:-x; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48) { if(c=='-')f=-1;c=getchar(); }
while(c>47) x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}const int maxn=55,maxm=8025,maxe=1e6+5,inf=0x3f3f3f3f;
int n,m,sum,S,T,dep[maxm],cur[maxm],pcnt,dcnt,dis[maxm][maxm],go[maxm][maxm];
PII dist[5],pos[maxm],door[maxm];
char s[maxn][maxn];
int head[maxe],t=1;
struct Edge{ int v,c;int next; };Edge e[maxe<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
il bool bfs(){
for(rg i=0;i<=T;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
q.push(S);dep[S]=1;cur[S]=head[S];
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(e[i].c>0 && !dep[to]){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,int flow){
if(now==T) return flow;
int res=0,rest;
for(rg i=cur[now];i;i=e[i].next){
cur[now]=i;int to=e[i].v;
if(e[i].c>0 && dep[to]==dep[now]+1){
rest=dfs(to,Min(flow,e[i].c));
if(!rest) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
}
}
return res;
}
il bool check(int tim){
for(rg i=0;i<=T;++i) head[i]=0;
t=1;int res=0;
S=0,T=dcnt*tim+pcnt+1;
for(rg i=1;i<=dcnt;++i)
for(rg j=1;j<=tim;++j){
int id=(i-1)*tim+j;
add_edge(id,T,1);
if(j^tim) add_edge(id,id+1,inf);
}
for(rg i=1;i<=pcnt;++i) add_edge(S,dcnt*tim+i,1);
for(rg i=1;i<=pcnt;++i){
int id=dcnt*tim+i;
for(rg j=1;j<=dcnt;++j) if(go[i][j]<=tim) add_edge(id,(j-1)*tim+go[i][j],1);
}
while(bfs()) res+=dfs(S,inf);
return res==pcnt;
}
il void bfs0(PII x,int id){
std::queue<PII> q;while(!q.empty()) q.pop();
for(rg i=0;i<=n;++i) for(rg j=0;j<=m;++j) dis[i][j]=inf;
dis[x.first][x.second]=0;q.push(x);
while(!q.empty()){
PII now=q.front();q.pop();
for(rg i=1;i<=4;++i){
PII to=std::make_pair(now.first+dist[i].first,now.second+dist[i].second);
if(to.first<1 || to.first>n || to.second<1 || to.second>m || s[to.first][to.second]=='X') continue;
if(dis[to.first][to.second]>dis[now.first][now.second]+1){
dis[to.first][to.second]=dis[now.first][now.second]+1;
q.push(to);
}
}
}
for(rg i=1;i<=dcnt;++i) go[id][i]=dis[door[i].first][door[i].second];
}
il void input(){
dist[1]=std::make_pair(1,0),dist[2]=std::make_pair(-1,0),dist[3]=std::make_pair(0,1),dist[4]=std::make_pair(0,-1);
n=read(),m=read();
for(rg i=1;i<=n;++i) scanf("%s",s[i]+1);
for(rg i=1;i<=n;++i)
for(rg j=1;j<=m;++j){
if(s[i][j]=='D') if(s[i-1][j]=='.' || s[i+1][j]=='.' || s[i][j+1]=='.' || s[i][j-1]=='.') door[++dcnt]=std::make_pair(i,j);
if(s[i][j]=='.') pos[++pcnt]=std::make_pair(i,j);
}
}
int main(){
freopen("evacuate.in","r",stdin);
input();
for(rg i=1;i<=pcnt;++i) bfs0(pos[i],i);
int l=0,r=n*m,ans=-1;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
(ans==-1)?puts("impossible"):printf("%d\n",ans);
return 0;
}
[SCOI2012] 奇怪的游戏
折叠题干
[SCOI2012] 奇怪的游戏
题目描述
Blinker 最近喜欢上一个奇怪的游戏。
这个游戏在一个 \(N \times M\) 的棋盘上玩,每个格子有一个数。每次 Blinker 会选择两个相邻的格子,并使这两个数都加上 \(1\)。
现在 Blinker 想知道最少多少次能使棋盘上的数都变成同一个数,如果永远不能变成同一个数则输出 \(-1\)。
输入格式
输入的第一行是一个整数 \(T\),表示输入数据有 \(T\) 轮游戏组成。
每轮游戏的第一行有两个整数 \(N\) 和 \(M\),分别代表棋盘的行数和列数。
接下来有 \(N\) 行,每行 \(M\) 个数。
输出格式
对于每个游戏输出最少能使游戏结束的次数,如果永远不能变成同一个数则输出 \(-1\)。
样例 #1
样例输入 #1
2
2 2
1 2
2 3
3 3
1 2 3
2 3 4
4 3 2
样例输出 #1
2
-1
提示
对于 \(30\%\) 的数据,保证 $ T\le 10,1\le N,M \le 8$。
对于 \(100\%\) 的数据,保证 \(T \le 10,1 \le N,M\le 40\),所有数为正整数且小于 \(10^9\)。
(写这道题才意识到我写的当前弧一直都是假的\kk)
由于棋盘上下左右连边,所以考虑对着棋盘染色,设第一种颜色的点有 \(col_1\) 种,权值为 \(val_1\),另一种颜色的点有 \(col_2\) 种,权值为 \(val_2\),那么建立的图就是二分图。
考虑设最后统一权值为 \(x\),那么有:
所以当 \(col_1-col_2\ne 0\) 时,我们只要知道 \(x\) 是否合法即可。
如何检验合法?
考虑网络流,由于我们单次修改肯定是同时修改两个连边的不同颜色的点,所以钦定第一种颜色的点连接源点,容量为 \(x-a_{i,j}\),第一种颜色的点连接其相邻的上下左右第二种颜色的点,容量为 \(inf\),而第二种颜色的点连接汇点,容量为 \(x-a_{i,j}\)。
跑一下网络流,如果跑出来能跑够就赢了。
如果不等于考虑二分 \(x\),下界显然是 \(\max\{a_{i,j}\}\),而上界显然是 \(n+m+\max\{a_{i,j}\}\)。
@jijidawang 在题解区严谨证明的复杂度,但是我个人不分析网络流题复杂度(
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il ll read(){
char c=getchar();ll x=0,f=1;
while(c<48){ if(c=='-')f=-1;c=getchar();}
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const ll inf=0x3f3f3f3f3f3f3f;
int dataT,n,m,A,a[100][100],col1,val1,col2,val2;
PII Go[5];
namespace Dinic{
const int maxn=1e4+5,maxm=2e5+5;
int head[maxn],t=1;int S,T;
struct Edge{ int v,c;int next; };Edge e[maxm];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
int dep[maxm],cur[maxm];
il bool bfs(){
for(rg i=0;i<=T;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
dep[S]=1;q.push(S);cur[S]=head[S];
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].c){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,ll flow){
if(now==T || flow==0) return flow;
ll rest,res=0;
for(rg i=cur[now];i;i=e[i].next){
cur[now]=i;int to=e[i].v;
if(dep[to]==dep[now]+1 && e[i].c){
rest=dfs(to,std::min(flow,e[i].c));
if(!rest) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
flow-=rest;res+=rest;
if(flow==0) break;
}
}
return res;
}
}using namespace Dinic;
il int getid(int x,int y){ return (x-1)*m+y; }
il bool check(int x){
memset(head,0,sizeof(head));t=1;int need=0,res=0;
S=0,T=n*m+1;
for(rg i=1;i<=n;++i){
for(rg j=1;j<=m;++j){
int ID=getid(i,j);
if((i+j)&1){
add_edge(S,ID,x-a[i][j]);need+=x-a[i][j];
for(rg d=1;d<=4;++d){
int dx=i+Go[d].first,dy=j+Go[d].second;
if(dx<1 || dy<1 || dx>n || dy>m) continue;
add_edge(ID,getid(dx,dy),inf);
}
}
else add_edge(ID,T,x-a[i][j]);
}
}
while(bfs()) res+=dfs(S,inf);
return res==need;
}
il void clear(){ A=col1=col2=val1=val2=0; }
il void count_col(int x,int y){ ((x+y)&1)?(++col1,val1+=a[x][y]):(++col2,val2+=a[x][y]); }
il void input(){
n=read(),m=read();
for(rg i=1;i<=n;++i) for(rg j=1;j<=m;++j){
a[i][j]=read(),A=Max(A,a[i][j]);
count_col(i,j);
}
}
signed main(){
freopen("game.in","r",stdin);
dataT=read();Go[1]=std::make_pair(1,0);Go[2]=std::make_pair(-1,0);Go[3]=std::make_pair(0,-1);Go[4]=std::make_pair(0,1);
while(dataT--){
clear();input();
int l=A,r=n*m*A,res=-1;
if(col1!=col2){
int x=(val1-val2)/(col1-col2);
(l<=x && x<=r && check(x))?printf("%lld\n",x*col1-val1):puts("-1");
continue;
}
if(val1!=val2){ puts("-1");continue; }
while(l<=r){
int mid=l+r>>1;
if(check(mid)) res=mid,r=mid-1;
else l=mid+1;
}
if(res==-1){ puts("-1");continue; }
printf("%lld\n",res*col1-val1);
}
return 0;
}
[国家集训队] happiness
折叠题干
[国家集训队] happiness
题目描述
高一一班的座位表是个 \(n\times m\) 的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。
作为计算机竞赛教练的 scp 大老板,想知道如何分配可以使得全班的喜悦值总和最大。
输入格式
第一行两个正整数 \(n\),\(m\)。
接下来是六个矩阵。
- 第一个矩阵为 \(n\) 行 \(m\) 列。此矩阵的第 \(i\) 行第 \(j\) 列的数字表示座位在第 \(i\) 行第 \(j\) 列的同学选择文科获得的喜悦值。
- 第二个矩阵为 \(n\) 行 \(m\) 列。此矩阵的第 \(i\) 行第 \(j\) 列的数字表示座位在第 \(i\) 行第 \(j\) 列的同学选择理科获得的喜悦值。
- 第三个矩阵为 \(n-1\) 行 \(m\) 列。此矩阵的第 \(i\) 行第 \(j\) 列的数字表示座位在第 \(i\) 行第 \(j\) 列的同学与第 \(i+1\) 行第 \(j\) 列的同学同时选择文科获得的额外喜悦值。
- 第四个矩阵为 \(n-1\) 行 \(m\) 列。此矩阵的第 \(i\) 行第 \(j\) 列的数字表示座位在第 \(i\) 行第 \(j\) 列的同学与第 \(i+1\) 行第 \(j\) 列的同学同时选择理科获得的额外喜悦值。
- 第五个矩阵为 \(n\) 行 \(m-1\) 列。此矩阵的第 \(i\) 行第 \(j\) 列的数字表示座位在第 \(i\) 行第 \(j\) 列的同学与第 \(i\) 行第 \(j+1\) 列的同学同时选择文科获得的额外喜悦值。
- 第六个矩阵为 \(n\) 行 \(m-1\) 列。此矩阵的第 \(i\) 行第 \(j\) 列的数字表示座位在第 \(i\) 行第 \(j\) 列的同学与第 \(i\) 行第 \(j+1\) 列的同学同时选择理科获得的额外喜悦值。
输出格式
输出一个整数,表示喜悦值总和的最大值。
样例 #1
样例输入 #1
1 2
1 1
100 110
1
1000
样例输出 #1
1210
提示
样例解释
两人都选理,则获得 \(100+110+1000\) 的喜悦值。
对于 \(100\%\) 的数据,\(1\le n,m \le 100\),且所有喜悦值均为小于等于 \(5000\) 的非负整数。
发现这个题有“非此即彼“的选择性,考虑用“此”和“彼”连接源点和汇点,求最小割使得整个图不连通就代表着不能同时选择,用权值和将最小割减去就是答案。
这道题中此彼就是选文选理。
首先每个点连接起点,容量为选择文科的喜悦值,连接汇点,容量为选择理科的喜悦值。
接着源点 \(S\) 连接一个虚点 \(x\),容量为选择文科的喜悦值,虚点 \(x\) 连接相邻点,容量为 \(inf\),表示两点同时选文的喜悦值。
选理相同,只是连接到汇点 \(T\)。
考虑为什么是可行的,当源点 \(S\) 连接了两个相邻的点表示两个点的都选文,如果有一个点同时连接了汇点 \(T\) 表示选理,那么就连通了,不合法。
而建立虚点就是为了保证我们只去掉一次同时选文的喜悦值就保证了图不连通。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48){ if(c=='-')f=-1;c=getchar();}
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const int maxn=5e4+5,maxm=3e5+5,inf=0x3f3f3f3f;
namespace Dinic{
int head[maxm<<1],t=1;
struct Edge{ int v,c;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
int dep[maxm],cur[maxm],S,T;
il bool bfs(){
for(rg i=0;i<=T;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
q.push(S);dep[S]=1;cur[S]=head[S];
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;cur[now]=i;
if(!dep[to] && e[i].c){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,ll flow){
if(now==T || flow==0) return flow;
ll res=0,rest;
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(dep[to]==dep[now]+1 && e[i].c){
rest=dfs(to,Min(flow,e[i].c));
if(rest==0) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
if(flow==0) break;
}
}
return res;
}
il ll dinic(){
ll res=0;
while(bfs()) res+=dfs(S,inf);
return res;
}
}using namespace Dinic;
int n,m;
ll ans;
il int getid(int x,int y){ return (x-1)*m+y; }
il void link(){
int val;S=0,T=n*m+(n*(m-1)<<1)+((n-1)*m<<1)+1;int id=n*m;
for(rg i=1;i<=n;++i) for(rg j=1;j<=m;++j){
val=read();ans+=val;
add_edge(S,getid(i,j),val);
}
for(rg i=1;i<=n;++i) for(rg j=1;j<=m;++j){
val=read();ans+=val;
add_edge(getid(i,j),T,val);
}
for(rg i=1;i<n;++i) for(rg j=1;j<=m;++j){
val=read();ans+=val;
add_edge(S,++id,val);add_edge(id,getid(i,j),inf);add_edge(id,getid(i+1,j),inf);
}
for(rg i=1;i<n;++i) for(rg j=1;j<=m;++j){
val=read();ans+=val;
add_edge(++id,T,val);add_edge(getid(i,j),id,inf);add_edge(getid(i+1,j),id,inf);
}
for(rg i=1;i<=n;++i) for(rg j=1;j<m;++j){
val=read();ans+=val;
add_edge(S,++id,val);add_edge(id,getid(i,j),inf);add_edge(id,getid(i,j+1),inf);
}
for(rg i=1;i<=n;++i) for(rg j=1;j<m;++j){
val=read();ans+=val;
add_edge(++id,T,val);add_edge(getid(i,j),id,inf);add_edge(getid(i,j+1),id,inf);
}
}
signed main(){
freopen("happiness.in","r",stdin);
n=read(),m=read();
link();
ans-=dinic();
printf("%lld\n",ans);
getime();
return 0;
}
[TJOI2015] 线性代数
前排提示:矩阵转置指将矩阵的行和列交换。
折叠题干
[TJOI2015] 线性代数
题目描述
为了提高智商,ZJY 开始学习线性代数。
她的小伙伴菠萝给她出了这样一个问题:给定一个 \(n \times n\) 的矩阵 \(B\) 和一个 \(1 \times n\) 的矩阵 \(C\)。求出一个 \(1×n\) 的 01 矩阵 \(A\),使得 \(D=(A×B-C)×A^{\sf T}\) 最大,其中\(A^{\sf T}\)为\(A\)的转置,输出\(D\)。
输入格式
第一行输入一个整数 \(n\)。接下来 \(n\) 行输入 \(B\) 矩阵,第 \(i\) 行第 \(j\) 个数代表 \(B\) 接下来一行输入 \(n\) 个整数,代表矩阵 \(C\)。矩阵 \(B\) 和矩阵 \(C\) 中每个数字都是不过 \(1000\) 的非负整数。
输出格式
输出一个整数,表示最大的 \(D\)。
样例 #1
样例输入 #1
3
1 2 1
3 1 0
1 2 3
2 3 7
样例输出 #1
2
提示
- 对于 \(30\%\) 的数据,\(n \leq 15\);
- 对于 \(100\%\) 的数据,\(1 \leq n \leq 500\);
- 另外还有两组不计分的 hack 数据,放在 subtask 2 中,数据范围与上面一致。
(我觉得我做了这个题可能才刚懂最小割)
考虑将矩阵的式子拆一下:
所以你发现因为 \(A\) 矩阵它是一个 \(1\times n\) 的 \(01\) 矩阵,所以就可以化作“选或不选的问题”,于是就转化成了最小割的模型。
它和上一道题还是一样的,考虑用总的权值(整个 \(B\) 矩阵的权值和)减去最小割就是答案。
有一个模型大概长这个样子。
设点 \(x\) 连接 \(S\) 表示 \(A_x\) 选 \(1\),连接 \(T\) 是选择 \(0\)。
我们先假设只有 \(x\) 和 \(y\) 两个点。
如果 \(x\) 和 \(y\) 都选 \(1\),那我们就要割掉 \(a_3+a_4=C_x+C_y\)。
如果 \(x\) 和 \(y\) 都选 \(0\),那我们就要割掉 \(a_1+a_2=B_{x,x}+B_{x,y}+B_{y,x}+B_{y,y}\)。
而对于有且仅有一个选 \(1\) 的情况,我们通过 \(v_1\)、\(v_2\) 微调。(其实上一道题也是这个东西,但是不是双向边)
如果 \(x\) 选 \(1\)、\(y\) 选 \(0\),那么我们要割掉 \(a_2+v_2+a_3=B_{x,y}+B_{x,x}+C_y\)。
如果 \(x\) 选 \(0\)、\(y\) 选 \(1\),那么我们要割掉 \(a_1+v_1+a_4=B_{y,x}+B_{y,y}+C_x\)。
所以我们有四个方程,六个未知数,但是没有关系,只要我们有一个合法解就行了。
于给他推广一下,现在把 \(n\) 个点都加入,那么有:
每个点都是一样的的连边。
所以有建边:
-
源点 \(S\) 向所有点 \(x\) 连边,容量为 \(B_{x,x}+\dfrac{\sum_{i=1}^{n}(i\ne x)B_{i,x}+B_{x,i}}{2}\)。
-
所有点 \(x\) 向汇点 \(T\) 连边,容量为 \(C_x\)。
-
点 \(x\) 和点 \(y\) 之间连双向边,容量为 \(\dfrac{B_{x,y}+B_{y,x}}{2}\)。
最终最小割一定是整数解,但是各边的容量却可能出现小数,考虑乘上 \(2\)。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48){ if(c=='-')f=-1;c=getchar();}
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const int maxn=505,maxm=251010,inf=0x3f3f3f3f;
int n,B[maxn][maxn],sum[maxn],C[maxn],ans;
namespace Dinic{
int head[maxn],t=1;
struct Edge{ int v,c;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
int dep[maxn],cur[maxn],S,T;
il bool bfs(){
for(rg i=0;i<=T;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
q.push(S);dep[S]=1;cur[S]=head[S];
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].c){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,ll flow){
if(now==T || flow==0) return flow;
ll res=0,rest;
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;cur[now]=i;
if(dep[to]==dep[now]+1 && e[i].c){
rest=dfs(to,Min(flow,e[i].c));
if(rest==0) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
if(flow==0) break;
}
}
return res;
}
il ll dinic(){
ll res=0;
while(bfs()) res+=dfs(S,inf);
return res;
}
}using namespace Dinic;
il void link(){
S=0,T=n+1;
for(rg i=1;i<=n;++i){
add_edge(S,i,sum[i]),add_edge(i,T,(C[i]<<1));
for(rg j=i+1;j<=n;++j) add_edge(i,j,B[i][j]+B[j][i]),add_edge(j,i,B[i][j]+B[j][i]);
}
}
il void input(){
n=read();
for(rg i=1;i<=n;++i) for(rg j=1;j<=n;++j) B[i][j]=read(),sum[i]+=B[i][j],sum[j]+=B[i][j],ans+=B[i][j];
for(rg i=1;i<=n;++i) C[i]=read();
}
int main(){
freopen("math.in","r",stdin);
input();
link();
ans-=(dinic()>>1);
printf("%d\n",ans);
getime();
return 0;
}
[国家集训队] 人员雇佣
折叠题干
[国家集训队] 人员雇佣
题目背景
原《线段覆盖》请做P1803
题目描述
作为一个富有经营头脑的富翁,小 \(L\) 决定从本国最优秀的经理中雇佣一些来经营自己的公司。这些经理相互之间合作有一个贡献指数,(我们用 \(E_{i,j}\) 表示 \(i\) 经理对 \(j\) 经理的了解程度),即当经理 \(i\) 和经理 \(j\) 同时被雇佣时,经理 \(i\) 会对经理 \(j\) 做出贡献,使得所赚得的利润增加 \(E_{i,j}\)。
当然,雇佣每一个经理都需要花费一定的金钱 \(A_i\),对于一些经理可能他做出的贡献不值得他的花费,那么作为一个聪明的人,小 \(L\) 当然不会雇佣他。然而,那些没有被雇佣的人会被竞争对手所雇佣,这个时候那些人会对你雇佣的经理的工作造成影响,使得所赚得的利润减少 \(E_{i,j}\)(注意:这里的 \(E_{i,j}\) 与上面的 \(E_{i,j}\) 是同一个)。
作为一个效率优先的人,小 \(L\) 想雇佣一些人使得净利润最大。你可以帮助小 \(L\) 解决这个问题吗?
输入格式
第一行有一个整数,表示经理的个数。
第二行有 \(N\) 个整数 \(A_i\) 表示雇佣每个经理需要花费的金钱。
接下来的 \(N\) 行中一行包含 \(N\) 个数,表示 \(E_{i,j}\),即经理 \(i\) 对经理 \(j\) 的了解程度。满足 \(E_{i,j}=E_{j,i}\)。
输出格式
第一行包含一个整数,即所求出的最大值。
样例 #1
样例输入 #1
3
3 5 100
0 6 1
6 0 2
1 2 0
样例输出 #1
1
提示
- \(20\%\) 的数据中 \(N\le 10\);
- \(50\%\) 的数据中 \(N\le 100\);
- \(100\%\) 的数据中 \(N\le 1000\),\(E_{i,j}<2^{31}\),\(A_i<2^{31}\)。
From 林衍凯。
这道题模型各种意义上都挺典的。
最小割,点 \(i\) 连接源点 \(S\) 表示选择,权值为 \(\sum\{E_i\}\),连接汇点 \(T\) 表示不选,权值为 \(A_i\)。
如果选择 \(i\),要断掉连向汇点的 \(T\),是花费 \(A_i\)。
考虑如果不选择 \(j\),那么会损失 \(E_{i,j}\times 2\),所以 \(i\) 和 \(j\) 之间连边 \(E_{i,j}\)。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define int long long
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48){ if(c=='-')f=-1;c=getchar();}
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const int maxn=1e3+5,maxm=1002005,inf=0x3f3f3f3f;
namespace Dinic{
int head[maxn],t=1,S,T;
struct Edge{ int v,c;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
int dep[maxn],cur[maxn];
il bool bfs(){
for(rg i=0;i<=T;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
cur[S]=head[S];dep[S]=1;q.push(S);
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].c){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,ll flow){
if(now==T || flow==0) return flow;
ll res=0,rest;
for(rg i=cur[now];i;i=e[i].next){
int to=e[i].v;cur[now]=i;
if(dep[to]==dep[now]+1 && e[i].c){
rest=dfs(to,Min(flow,e[i].c));
if(rest==0) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
if(flow==0) break;
}
}
return res;
}
il int dinic(){
ll res=0;
while(bfs()) res+=dfs(S,inf);
return res;
}
}using namespace Dinic;
int n,ans;
il void link(){
n=read();S=0,T=n+1;int A,E;
for(rg i=1;i<=n;++i) A=read(),add_edge(i,T,A);
for(rg i=1;i<=n;++i){
int sum=0;
for(rg j=1;j<=n;++j){
E=read();
if(E) add_edge(i,j,(E<<1)),sum+=E,ans+=E;
}
add_edge(S,i,sum);
}
}
signed main(){
freopen("employ.in","r",stdin);
link();
ans-=dinic();
printf("%lld\n",ans);
getime();
return 0;
}
bzoj3158千钧一发
折叠题干
输入格式
第一行一个正整数 \(N\)。
第二行共包括 \(N\) 个正整数,第 \(i\) 个正整数表示 \(A_i\)。
第三行共包括 \(N\) 个正整数,第 \(i\) 个正整数表示 \(B_i\)。
输出格式
共一行,包括一个正整数,表示在合法的选择条件下,可以获得的能量值总和的最大值。
样例:
input#1:
4
3 4 5 12
9 8 30 9
output#2:
39
首先看到 \(a^2+b^2=c^2\),要么是勾股定理,要么是定理:两个奇数的平方和一定不为完全平方数。
证明:两个奇数的平方和不等于完全平方数
设奇数 \(2n+1\),其平方为 \(4n^2+4n+1\),对 \(4\) 取余为 \(1\)。
那么两个奇数的平方和对 \(4\) 取余为 \(2\)。
而偶数 \(2n\) 的平方为 \(4n^2\),对 \(4\) 取余为 \(0\)。
所以两个奇数的平方和既不是奇数的平方又不是偶数的平方,不存在合法完全平方数。
而 \(gcd(x,y)\) 当 \(x\) 和 \(y\) 均为偶数的时候一定不为 \(1\)。
所以从奇偶性考虑,奇数之间可以随便选,偶数之间可以随便选,只有奇数和偶数之间有一些数不能选,考虑将他们连边建最小割模型。
-
源点 \(S\) 连奇数点 \(i\),容量为 \(B_i\)。
-
偶数点 \(j\) 连汇点 \(T\),容量为 \(B_j\)。
-
当两个条件均不满足,奇数点 \(i\) 连偶数点 \(j\),容量为 \(inf\)。
Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define bits(x) std::bitset<x>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
char c=getchar();int x=0,f=1;
while(c<48){ if(c=='-')f=-1;c=getchar();}
while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
return x*f;
}const int maxn=1e3+5,maxm=1e6+5,inf=0x3f3f3f3f;
namespace Dinic{
int head[maxn],t=1,S,T;
struct Edge{ int v,c;int next; };Edge e[maxm<<1];
il void add_edge(int u,int v,int c){
e[++t].v=v;e[t].c=c;e[t].next=head[u];head[u]=t;
e[++t].v=u;e[t].c=0;e[t].next=head[v];head[v]=t;
}
int dep[maxn],cur[maxn];
il bool bfs(){
for(rg i=0;i<=T;++i) dep[i]=0;
std::queue<int> q;while(!q.empty()) q.pop();
cur[S]=head[S];dep[S]=1;q.push(S);
while(!q.empty()){
int now=q.front();q.pop();
for(rg i=head[now];i;i=e[i].next){
int to=e[i].v;
if(!dep[to] && e[i].c){
dep[to]=dep[now]+1;cur[to]=head[to];q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dfs(int now,ll flow){
if(now==T || flow==0) return flow;
ll res=0,rest;
for(rg i=cur[now];i;i=e[i].next){
int to=e[i].v;cur[now]=i;
if(dep[to]==dep[now]+1 && e[i].c){
rest=dfs(to,Min(flow,e[i].c));
if(rest==0) dep[to]=0;
e[i].c-=rest;e[i^1].c+=rest;
res+=rest;flow-=rest;
if(flow==0) break;
}
}
return res;
}
il int dinic(){
ll res=0;
while(bfs()) res+=dfs(S,inf);
return res;
}
}using namespace Dinic;
int n,a[maxn],b[maxn],ans;
int odd[maxn],cnt1,even[maxn],cnt2;
int gcd(int a,int b){ return (b==0)?a:gcd(b,a%b); }
il bool check(int x,int y){
int num=x*x+y*y,k=sqrt(num);
if(gcd(x,y)==1 && k*k==num) return true;
return false;
}
il void link(){
for(rg i=1;i<=cnt1;++i) add_edge(S,odd[i],b[odd[i]]);
for(rg i=1;i<=cnt2;++i) add_edge(even[i],T,b[even[i]]);
for(rg i=1;i<=cnt1;++i) for(rg j=1;j<=cnt2;++j) if(check(a[odd[i]],a[even[j]])) add_edge(odd[i],even[j],inf);
}
il void input(){
n=read();S=0,T=n+1;
for(rg i=1;i<=n;++i){
a[i]=read();
if(a[i]&1) odd[++cnt1]=i;
else even[++cnt2]=i;
}
for(rg i=1;i<=n;++i) b[i]=read(),ans+=b[i];
}
signed main(){
freopen("MIku.in","r",stdin);
input();
link();
ans-=dinic();
printf("%lld\n",ans);
getime();
return 0;
}
(因为不知道起什么文件名了所以直接写了MIku)