Codeforces 1368H - Breadboard Capacity(最小割+线段树维护矩阵乘法)
Easy version:Codeforces 题面传送门 & 洛谷题面传送门
Hard version:Codeforces 题面传送门 & 洛谷题面传送门
首先看到这种从某一种颜色的点连向另一种颜色的点,要求经过的边不重复的问题,可以很自然地想到网络流,具体来说咱们建立源 \(S\) 和汇 \(T\),从源点 \(S\) 向所有红色点连容量为 \(1\) 的边,从所有蓝色点向汇点 \(T\) 连容量为 \(1\) 的边,然后将网络内部所有边都改为容量为 \(1\) 的双向边,然后跑最大流就是答案。
但是就算 H1 数据范围也高达 \(10^5\),更别说 H2 还带修改呢,因此直接最大流显然不可行。因此考虑模拟最大流(瞎起名字*1),即考虑原图的最小割。但是问题又来了,且不谈时间复杂度的问题,如果按照以往模拟最大流的套路:使用以最小割的情况为状态设计 \(dp\),或者对于每个割掉的边集重新建图跑最大流,套用到此题上显然不可行。怎么办呢?这时候就要用到我们善于发现的眼睛了,对于这种看似无从下手的题目,我们不妨来分析一下该图的最小割有什么性质,我们考虑将与源点连通的点标为红色,与汇点连通的点标为蓝色,那么可以发现以下性质:
Observation \(1\). 如果割掉的边内部出现环,并且这些环上的边都连在内部的 \(n\times m\) 个点中(即不与边界上的 \(2(n+m)\) 个点连边),我们显然可以改变环内部点的颜色使答案不会变得更劣。
证明:显然如果我们改变环内部的颜色,使其与外界一致,原来连接环内部和环外部的割边都会消失并且不会添加新的割边,因此割的大小减少。
Observation \(2\). 如果出现一条割掉的边形成的路径连接了同一个或相邻的边界,那么我们可以改变点的颜色使这样的路径消失并且答案不会变得更劣。
也就是对于下图中左边的情形,我们可以将其改成右边的情形(注:这里借用了官方题解的图):
证明:如果我们将这个割掉的边形成的路径包围的部分中,属于内部的 \(nm\) 个点的部分(也就是上图中改变颜色的五个点)的颜色改为与路径另一侧的点一直,那么显然这条路径上的割边都会消失,此同时有可能会增加一些割边,这些增加的割边都来自于边界上的点与该路径包围的区域中的点连成的边(也就是上图中增加的三条割边),但是很显然的一件事是,增加的割边的个数不会超过这条路径与该矩形区域边界的公共部分的长度,又由小学奥数,这块区域与边界公共部分的长度不会超过这块区域在矩形内部的轮廓的长度,因此减少的割边条数终究还是 \(\ge\) 增加的割边条数,割边条数终究还是会减少。
Observation \(3\). 如果连接相对边界上的路径出现了折线,那么我们完全可以将它捋直并且答案不会变得更劣
也就是对于下图中左边的情形,我们可以将其改成右边的情形(注:这里再次借用了官方题解的图):
证明:我们不妨假设这条折线是由上边界连向下边界的,并且起点是上边界上第 \(i\) 个点与第 \(i+1\) 个点之间的边,终点是下边界上第 \(j\) 个点与第 \(j+1\) 个点之间的边,那么这条路径的长度,也就是这条路径上的割边数量显然会 \(\ge n+|i-j|\),而如果我们把它捋直,连接着内部 \(nm\) 个点的割边恰有 \(n\) 个,而最多还会多出 \(|i-j|\) 条割边,因此割边数量不会增加。
不难发现,经过这三次调整之后,我们的割边要么出现在边界上的点与内部点之间,要么是一列上完整的 \(n\) 条割边,或者一行上完整的 \(m\) 条割边,因此考虑以此为状态设计 \(dp\)。行和列显然是对称的,因此考虑一种情况即可镜像地求出另一种情况,这里以行为例。设 \(dp_{i,j}\) 表示当前考虑了前 \(i\) 行,第 \(i\) 行上中间 \(m\) 个点的颜色是 \(j\) 最少割边条数,其中 \(0\) 表示红色,\(1\) 表示蓝色,那么显然有转移:
- \(dp_{i,0}=\min(dp_{i-1,0},dp_{i-1,1}+m)+[L_i=0]+[R_i=0]\)
- \(dp_{i,1}=\min(dp_{i-1,0}+m,dp_{i-1,1})+[L_i=1]+[R_i=1]\)
然后对行和列的最优答案取个 \(\min\) 即可,时间复杂度线性,可以通过 H1
const int MAXN=1e5;
int n,m;char L[MAXN+5],R[MAXN+5],U[MAXN+5],D[MAXN+5];
int dp[MAXN+5][2],ans=0x3f3f3f3f;
int main(){
scanf("%d%d%*d%s%s%s%s",&n,&m,L+1,R+1,U+1,D+1);
memset(dp,63,sizeof(dp));dp[0][0]=dp[0][1]=0;
for(int i=1;i<=m;i++) dp[0][0]+=(U[i]=='B'),dp[0][1]+=(U[i]=='R');
for(int i=1;i<=n;i++){
dp[i][0]=min(dp[i-1][0],dp[i-1][1]+m)+(L[i]=='B')+(R[i]=='B');
dp[i][1]=min(dp[i-1][1],dp[i-1][0]+m)+(L[i]=='R')+(R[i]=='R');
}
for(int i=1;i<=m;i++) dp[n][0]+=(D[i]=='B'),dp[n][1]+=(D[i]=='R');
chkmin(ans,dp[n][0]);chkmin(ans,dp[n][1]);
memset(dp,63,sizeof(dp));dp[0][0]=dp[0][1]=0;
for(int i=1;i<=n;i++) dp[0][0]+=(L[i]=='B'),dp[0][1]+=(L[i]=='R');
for(int i=1;i<=m;i++){
dp[i][0]=min(dp[i-1][0],dp[i-1][1]+n)+(U[i]=='B')+(D[i]=='B');
dp[i][1]=min(dp[i-1][1],dp[i-1][0]+n)+(U[i]=='R')+(D[i]=='R');
}
for(int i=1;i<=n;i++) dp[m][0]+=(R[i]=='B'),dp[m][1]+=(R[i]=='R');
chkmin(ans,dp[m][0]);chkmin(ans,dp[m][1]);
printf("%d\n",ans);
return 0;
}
关于 H2,由于带上修改,并且 \(dp\) 状态的第二维较小,因此可以想到线段树维护矩阵乘法,具体来说我们对行和列分别建一棵线段树,线段树上每个节点 \([L,R]\) 维护四个 \(2\times 2\) 的矩阵,分别表示 \(L_i,R_i\) 颜色均不改变/\(L_i\) 颜色改变 \(R_i\) 不变/\(L_i\) 颜色不变 \(R_i\) 改变/\(L_i,R_i\) 颜色均改变情况下的转移矩阵,这样每次区间翻转只用交换两对矩阵即可,时间复杂度 \(n\log n\),常数略有点大,但还是莽过去了(
const int MAXN=1e5;
int n,m,qu;
struct mat{
int a[2][2];
mat(){memset(a,63,sizeof(a));}
mat operator *(const mat &rhs){
mat res;
for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++)
chkmin(res.a[i][j],a[i][k]+rhs.a[k][j]);
return res;
}
};
struct solver{
int n,m;char s1[MAXN+5],s2[MAXN+5];
mat get(int x,int y){
mat res;
res.a[0][0]=x+y;res.a[0][1]=m+2-x-y;
res.a[1][0]=m+x+y;res.a[1][1]=2-x-y;
return res;
}
struct node{int l,r,s1,s2,tg1,tg2;mat v[2][2];} s[MAXN*4+5];
void pushup(int k){
for(int a=0;a<2;a++) for(int b=0;b<2;b++)
s[k].v[a][b]=s[k<<1].v[a][b]*s[k<<1|1].v[a][b];
s[k].s1=s[k<<1].s1+s[k<<1|1].s1;
s[k].s2=s[k<<1].s2+s[k<<1|1].s2;
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;
if(l==r){
for(int a=0;a<2;a++) for(int b=0;b<2;b++)
s[k].v[a][b]=get((s1[l]=='B')^a,(s2[l]=='B')^b);
s[k].s1=(s1[l]=='B');s[k].s2=(s2[l]=='B');
return;
} int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
pushup(k);
}
void tag1(int k){
swap(s[k].v[0][0],s[k].v[1][0]);
swap(s[k].v[0][1],s[k].v[1][1]);
s[k].s1=s[k].r-s[k].l+1-s[k].s1;
s[k].tg1^=1;
}
void tag2(int k){
swap(s[k].v[0][0],s[k].v[0][1]);
swap(s[k].v[1][0],s[k].v[1][1]);
s[k].s2=s[k].r-s[k].l+1-s[k].s2;
s[k].tg2^=1;
}
void pushdown(int k){
if(s[k].tg1){tag1(k<<1);tag1(k<<1|1);s[k].tg1=0;}
if(s[k].tg2){tag2(k<<1);tag2(k<<1|1);s[k].tg2=0;}
}
void modify(int k,int l,int r,int t){
if(l<=s[k].l&&s[k].r<=r) return ((t==1)?tag1(k):tag2(k)),void();
int mid=(pushdown(k),s[k].l+s[k].r>>1);
if(r<=mid) modify(k<<1,l,r,t);
else if(l>mid) modify(k<<1|1,l,r,t);
else modify(k<<1,l,mid,t),modify(k<<1|1,mid+1,r,t);
pushup(k);
}
void init(){build(1,1,n);}
mat query(){return s[1].v[0][0];}
int ask1(){return s[1].s1;}
int ask2(){return s[1].s2;}
} H,V;
int calc(){
mat m1=H.query(),m2=V.query();
return min(min(
min(m1.a[0][0]+V.ask1()+V.ask2(),m1.a[0][1]+V.ask1()+m-V.ask2()),
min(m1.a[1][0]+m-V.ask1()+V.ask2(),m1.a[1][1]+m-V.ask1()+m-V.ask2())),min(
min(m2.a[0][0]+H.ask1()+H.ask2(),m2.a[0][1]+H.ask1()+n-H.ask2()),
min(m2.a[1][0]+n-H.ask1()+H.ask2(),m2.a[1][1]+n-H.ask1()+n-H.ask2())));
}
int main(){
scanf("%d%d%d",&n,&m,&qu);V.m=H.n=n;V.n=H.m=m;
scanf("%s%s%s%s",H.s1+1,H.s2+1,V.s1+1,V.s2+1);
H.init();V.init();printf("%d\n",calc());
while(qu--){
char c;int l,r;cin>>c>>l>>r;
switch(c){
case 'L':{H.modify(1,l,r,1);break;}
case 'R':{H.modify(1,l,r,2);break;}
case 'U':{V.modify(1,l,r,1);break;}
case 'D':{V.modify(1,l,r,2);break;}
} printf("%d\n",calc());
}
return 0;
}
Codeforces 1368H - Breadboard Capacity(最小割+线段树维护矩阵乘法)的更多相关文章
- Codeforces 750E - New Year and Old Subsequence(线段树维护矩阵乘法,板子题)
Codeforces 题目传送门 & 洛谷题目传送门 u1s1 我做这道 *2600 的动力是 wjz 出了道这个套路的题,而我连起码的思路都没有,wtcl/kk 首先考虑怎样对某个固定的串计 ...
- Subsequence Count 2017ccpc网络赛 1006 dp+线段树维护矩阵
Problem Description Given a binary string S[1,...,N] (i.e. a sequence of 0's and 1's), and Q queries ...
- hdu 5068 线段树维护矩阵乘积
http://acm.hdu.edu.cn/showproblem.php?pid=5068 题意给的略不清晰 m个询问:从i层去j层的方法数(求连段乘积)或者修改从x层y门和x+1层z门的状态反转( ...
- 线段树维护矩阵【CF718C】 Sasha and Array
Description 有一个长为\(n\)的数列\(a_{1},a_{2}...a_{n}\),你需要对这个数列维护如下两种操作: \(1\space l \space r\space x\) 表示 ...
- CF718C Sasha and Array(线段树维护矩阵)
题解 (不会矩阵加速的先去学矩阵加速) 反正我想不到线段树维护矩阵.我太菜了. 我们在线段树上维护一个区间的斐波那契的列矩阵的和. 然后询问时提取每个符合题意列矩阵的答案项(不是列矩阵存了两项吗,一个 ...
- Codeforces 834D The Bakery【dp+线段树维护+lazy】
D. The Bakery time limit per test:2.5 seconds memory limit per test:256 megabytes input:standard inp ...
- Codeforces 719E (线段树教做人系列) 线段树维护矩阵
题面简洁明了,一看就懂 做了这个题之后,才知道怎么用线段树维护递推式.递推式的递推过程可以看作两个矩阵相乘,假设矩阵A是初始值矩阵,矩阵B是变换矩阵,求第n项相当于把矩阵B乘了n - 1次. 那么我们 ...
- CF633H Fibonacci-ish II 莫队、线段树、矩阵乘法
传送门 这题除了暴力踩标程和正解卡常数以外是道很好的题目 首先看到我们要求的东西与\(Fibonacci\)有关,考虑矩阵乘法进行维护.又看到\(n \leq 30000\),这告诉我们正解算法其实比 ...
- HDU 6155 Subsequence Count 线段树维护矩阵
Subsequence Count Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 256000/256000 K (Java/Oth ...
随机推荐
- IEEE754浮点数的转换
将十进制数转换为单精度浮点数 如何将十进制数转换为单精度浮点数参考 首先要知道 IEEE浮点标准:V=(-1)^s * M * 2^E 1.符号(sign)s决定这个数是负数(s=1)还是正数,0(s ...
- 什么是Spring,SpringMVC,SpringBoot,SpringCloud?通俗易懂
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架.Spring使你能够编写更干净.更可管理.并且更易于测试的代码. Spring MVC是Spring的一个模块,一个web框 ...
- VS Code Remote SSH设置
本文翻译自:5 Steps: Setup VS Code for Remote Development via SSH from Windows to Linux system 5个步骤:设置VS代码 ...
- spring源码分析(二)- 容器基础
1.基本用法 用过Spring的都知道,bean是Spring中最基础也是最核心的.首先看一个简单的例子. 一个类和一个配置文件 package bean; public class MyBean { ...
- JVM:垃圾收集器与对象的"存活"问题
垃圾收集器垃圾收集(Garbage Collection,GC).当需要排查各种内存溢出.内存泄露问题时,当垃圾收集成为系统更高并发量的瓶颈时,我们需要去了解GC和内存分配. 检查对象的"存 ...
- VUE项目实现主题切换
需求是 做一个深色主题和浅色主题切换的效果 方法一 多套css 这个方法也是最简单,也是最无聊的. <!-- 中心 --> <template> 动态获取父级class名称,进 ...
- vue修改启动的端口和host
打开vue项目(dev) dev/config/ 路径修改index.js文件 然后对host和pord修改指定的即可 host: 'localhost', // can be overwritten ...
- Android Thermal HAL 降龙十八掌
基本概念 参阅下面两篇文章,就可以大概了解一些概念的内容了 https://source.android.com/devices/architecture/hidl/thermal-mitigatio ...
- Python 爬取妹子图(技术是无罪的)
... import requests from bs4 import BeautifulSoup import os import sys class mzitu(): def html(self, ...
- ErrorProvider与CheckedListBox
http://www.cnblogs.com/myshell/archive/2010/09/24/1834184.html 最近因为做WinForm的项目,遇到这个问题,当时以为CheckedLis ...