题意

一个 \(n\times n\) 的棋盘上面有若干障碍物。

定义两个棋子可以互相攻击当且仅当这两个棋子的横坐标或纵坐标相等而且中间不能隔着障碍物。(可以隔棋子)

有 \(q\) 次询问,每次询问你要回答在棋盘上摆 \(x\) 枚棋子最少互相能攻击到的棋子对数。

\(\texttt{Data Range:}1\leq n\leq 50,1\leq q\leq 10^4\)

题解

我咋连套路都不会了啊……

考虑二分图,将每一行每一列被 # 隔开的小段缩成一个点,对于每个可以放棋子位置像所属横纵坐标的小段连边。

但是一小段内放了 \(x\) 个棋子会对答案贡献 \(\binom{x}{2}\),我们要考虑如何最小化这个东西。

按照蓝书上的套路,考虑将一条边拆成流量为 \(1\),费用为 \(1,2,\cdots,x\) 的多条边,然后跑单路增广并且记录答案就行了。

注意到流量的意义就是放棋子的枚数,所以不能按照平常的写法来。

代码

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=1e4+51,inf=0x7fffffff;
struct Edge{
ll to,prev,flow,cost;
};
Edge ed[MAXN<<1];
ll n,source,sink,tot=1,maxFlow,minCost,limit,qcnt,curx;
char ch[51][51];
ll last[MAXN],vis[MAXN],dist[MAXN],res[MAXN],back[MAXN];
ll idx[51][51],idy[51][51],sz[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void addEdge(ll from,ll to,ll flow,ll cost)
{
ed[++tot].prev=last[from];
ed[tot].to=to;
ed[tot].flow=flow;
ed[tot].cost=cost;
last[from]=tot;
}
inline ll Min(ll x,ll y)
{
return x<y?x:y;
}
inline bool spfa()
{
queue<ll>q;
ll top,to;
memset(vis,0,sizeof(vis));
memset(dist,0x3f,sizeof(dist));
memset(back,0,sizeof(back));
dist[source]=0,vis[source]=1,q.push(source);
while(!q.empty())
{
top=q.front();
q.pop(),vis[top]=0;
for(register int i=last[top];i;i=ed[i].prev)
{
to=ed[i].to;
if(dist[to]>dist[top]+ed[i].cost&&ed[i].flow)
{
dist[to]=dist[top]+ed[i].cost,back[ed[i].to]=i;
if(!vis[to])
{
q.push(to),vis[to]=1;
}
}
}
}
if(dist[sink]!=0x3f3f3f3f)
{
return 1;
}
return 0;
}
inline ll MCMF()
{
ll flow;
while(spfa()&&maxFlow<limit)
{
for(register int i=back[sink];i;i=back[ed[i^1].to])
{
ed[i].flow--,ed[i^1].flow++;
}
res[++maxFlow]=(minCost+=dist[sink]);
}
return maxFlow;
}
int main()
{
n=read();
for(register int i=1;i<=n;i++)
{
scanf("%s",ch[i]+1);
for(register int j=1;j<=n;j++)
{
if(ch[i][j]=='.')
{
limit++;
}
}
}
for(register int i=1;i<=n;i++)
{
for(register int j=1;j<=n;j++)
{
if(ch[i][j]!='#')
{
idx[i][j]=(ch[i][j]==ch[i][j-1]?idx[i][j-1]:++sink);
}
}
}
curx=sink;
for(register int i=1;i<=n;i++)
{
for(register int j=1;j<=n;j++)
{
if(ch[j][i]!='#')
{
idy[j][i]=(ch[j][i]==ch[j-1][i]?idy[j-1][i]:++sink);
}
}
}
for(register int i=1;i<=n;i++)
{
for(register int j=1;j<=n;j++)
{
if(ch[i][j]!='#')
{
sz[idx[i][j]]++,sz[idy[i][j]]++;
}
}
}
sink++;
for(register int i=1;i<=curx;i++)
{
for(register int j=0;j<sz[i];j++)
{
addEdge(source,i,1,j),addEdge(i,source,0,-j);
}
}
for(register int i=curx+1;i<sink;i++)
{
for(register int j=0;j<sz[i];j++)
{
addEdge(i,sink,1,j),addEdge(sink,i,0,-j);
}
}
for(register int i=1;i<=n;i++)
{
for(register int j=1;j<=n;j++)
{
if(ch[i][j]=='.')
{
addEdge(idx[i][j],idy[i][j],1,0);
addEdge(idy[i][j],idx[i][j],0,0);
}
}
}
MCMF(),qcnt=read();
for(register int i=0;i<qcnt;i++)
{
printf("%d\n",res[read()]);
}
}

LOJ 6068「2017 山东一轮集训 Day4」棋盘的更多相关文章

  1. Loj 6068. 「2017 山东一轮集训 Day4」棋盘

    Loj 6068. 「2017 山东一轮集训 Day4」棋盘 题目描述 给定一个 $ n \times n $ 的棋盘,棋盘上每个位置要么为空要么为障碍.定义棋盘上两个位置 $ (x, y),(u, ...

  2. [LOJ#6068]. 「2017 山东一轮集训 Day4」棋盘[费用流]

    题意 题目链接 分析 考虑每个棋子对对应的横向纵向的极大区间的影响:记之前这个区间中的点数为 \(x\) ,那么此次多配对的数量即 \(x\) . 考虑费用流,\(S\rightarrow 横向区间 ...

  3. Loj #6069. 「2017 山东一轮集训 Day4」塔

    Loj #6069. 「2017 山东一轮集训 Day4」塔 题目描述 现在有一条 $ [1, l] $ 的数轴,要在上面造 $ n $ 座塔,每座塔的坐标要两两不同,且为整点. 塔有编号,且每座塔都 ...

  4. loj6068. 「2017 山东一轮集训 Day4」棋盘 二分图,网络流

    loj6068. 「2017 山东一轮集训 Day4」棋盘 链接 https://loj.ac/problem/6068 思路 上来没头绪,后来套算法,套了个网络流 经典二分图 左边横,右边列 先重新 ...

  5. 「2017 山东一轮集训 Day4」棋盘(费用流)

    棋盘模型 + 动态加边 #include<cstdio> #include<algorithm> #include<iostream> #include<cs ...

  6. Loj #6073.「2017 山东一轮集训 Day5」距离

    Loj #6073.「2017 山东一轮集训 Day5」距离 Description 给定一棵 \(n\) 个点的边带权的树,以及一个排列$ p\(,有\)q $个询问,给定点 \(u, v, k\) ...

  7. LOJ #6074. 「2017 山东一轮集训 Day6」子序列

    #6074. 「2017 山东一轮集训 Day6」子序列 链接 分析: 首先设f[i][j]为到第i个点,结尾字符是j的方案数,这个j一定是从i往前走,第一个出现的j,因为这个j可以代替掉前面所有j. ...

  8. loj #6077. 「2017 山东一轮集训 Day7」逆序对

    #6077. 「2017 山东一轮集训 Day7」逆序对   题目描述 给定 n,k n, kn,k,请求出长度为 n nn 的逆序对数恰好为 k kk 的排列的个数.答案对 109+7 10 ^ 9 ...

  9. LOJ #6119. 「2017 山东二轮集训 Day7」国王

    Description 在某个神奇的大陆上,有一个国家,这片大陆的所有城市间的道路网可以看做是一棵树,每个城市要么是工业城市,要么是农业城市,这个国家的人认为一条路径是 exciting 的,当且仅当 ...

随机推荐

  1. Android 字符串的常用操作

    目录 Substring 基本语法 IndexOf 基本语法 Split 基本语法 Substring 基本语法 str.substring(","); //从第一个,号开始截取 ...

  2. Android控件Gridivew列数行间距设定

    常用属性 列数 android:numColumns="3" 行间距 android:verticalSpacing="8dp"

  3. SpringBoot整合SpringDataJPA,今天没啥事情就看了一下springboot整合springdataJPA,实在是香啊,SQL语句都不用写了

    SpringBoot整合SpringDataJPA 1.JPA概念 JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映 ...

  4. makefile实验三 理解make工作的基本原则

    代码简单,但测试花样多,若能回答对本博客的每个步骤的预期结果,可以说对makefile的基础掌握是扎实的. 一,当前的makefile代码 root@ubuntu:~/Makefile_Test# r ...

  5. 07 C语言常量

    常量的定义 常量是指固定的值,固定值在程序执行期间不会改变.这些固定值,又叫做字面量. 常量可以是任意的基本数据类型,比如整数常量.浮点常量.字符常量,或字符串字面值,也有枚举常量. 不要搞得太复杂, ...

  6. matlab中bitshift 将位移动指定位数

    来源:https://ww2.mathworks.cn/help/matlab/ref/bitshift.html?searchHighlight=bitshift&s_tid=doc_src ...

  7. org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, "enterpCd")-Mybatis报错

    一.问题由来 下午快要下班时,登录测试服务器查看日志信息,看看有没有新的异常信息,如果有的话好及时修改.结果一看果然有新的异常信息. 主要的异常信息如下: 2020-10-13 14:51:03,03 ...

  8. day39 Pyhton 并发编程02

    一.内容回顾 并发和并行的区别 并发 宏观上是在同时运行的 微观上是一个一个顺序执行 同一时刻只有一个cpu在工作 并行 微观上就是同时执行的 同一时刻不止有一个cpu在工作 什么是进程 一个运行中的 ...

  9. 使用python编写正逆序乘法表

    # 99乘法表 # 顺序 for i in range(1,10): n = 1 while n <= i: print('{}x{}={}'.format(n,i,n*i),end=' ') ...

  10. jmeter_02_目录文档说明

    jmeter目录文档说明 bin目录是可执行文件 jmeter.bat 是启动文件 可以启动jmeter. 使用notpad++ 等文本编辑器打开 bat文件 可以配置jvm的参数 比如堆内存[Hea ...