「NOI2016」网格

容易注意到,答案最多为2,也就是说答案为-\(1,0,1,2\)四种,考虑逐个判断。


无解的情况比较简单

如果\(nm\le c+1\),显然无解

如果\(nm=c+2\),判断2个跳蚤(如无说明,以下白点指跳蚤)是否四联通(如无说明,以下联通均指四联通),如果是,无解。


先不考虑复杂度

\(0\)的情况,就是白点有两个以上联通块,可以直接bfs判断

\(1\)的情况,就是白点存在割点,可以通过tarjan判断

\(2\)的情况,就是其他情况

这样的复杂度是\(O(Tnm)\)的


考虑可能成为割点的点其实不多,定义它们为关键点,然后提取关键点再进行操作

我们发现,只有黑点(以下均指蛐蛐)周围8联通的8个格子可能成为割点

我们把这些点拎出来,编一个号然后对每个白点联通块跑tarjan判断割点,就可以判断原图是否有割点。

但是只提出可能成为割点的点建出的图可能会把不是割点的点判断成割点

比如这种情况

黑点是红色的,建出\(3\times 3\)会使打叉的点成为割点,但实际上它不是

所以我们需要提取以黑点为中心的\(5\times 5\)个格子

这样就可以判断答案是否为\(1\)了


然后如何判断白点的连通性呢?

还是只考虑关键点,我们用并查集处理出每个关键点白点所在的联通块编号,然后遍历每个黑点联通块,如果某个黑点联通块边界接触到了至少两个不同的白点联通块,那么这个白点就是不连通的了。


考虑特殊情况,我们上面提取关键点后\(n=1\)或者\(m=1\)的情况就无法判断了,进行特判。


考虑实现,因为点数实际上比较大,如果用\(map\)去定位关键点会很慢,考虑使用Hash定位关键点


总复杂度\(O(\sum c)\)


Code:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
using std::min;
using std::max;
const int SIZE=1<<21;
char ibuf[SIZE],*iS,*iT;
//#define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),iS==iT?EOF:*iS++):*iS++)
#define gc() getchar()
template <class T>
void read(T &x)
{
x=0;char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+c-'0',c=gc();
}
const int N=2e6+5e5+10;
const int mod=19260817;
int n,m,c,dx[N],dy[N];
void ckmin(int &x,int y){x=x<y?x:y;}
void ckmax(int &x,int y){x=x>y?x:y;}
struct Hash
{
int dx[N],dy[N],dis[N],is[N];
int head[mod],Next[N],cnt;
void ins(int x,int y,int isbla,int d)//ququ is 1
{
if(x<=0||y<=0||x>=n+1||y>=m+1) return;
int p=(1ll*(x-1)*m+y)%mod;
for(int i=head[p];i;i=Next[i])
if(dx[i]==x&&dy[i]==y)
{
ckmin(dis[i],d);
ckmax(is[i],isbla);
return;
}
Next[++cnt]=head[p],head[p]=cnt;
dx[cnt]=x,dy[cnt]=y,dis[cnt]=d,is[cnt]=isbla;
}
int qry(int x,int y)
{
if(x<=0||y<=0||x>=n+1||y>=m+1) return -1;
int p=(1ll*(x-1)*m+y)%mod;
for(int i=head[p];i;i=Next[i])
if(dx[i]==x&&dy[i]==y)
return i;
return -1;
}
void clear()
{
for(int i=1;i<=cnt;i++)
{
head[(1ll*(dx[i]-1)*m+dy[i])%mod]=0;
dx[i]=dy[i]=dis[i]=is[i]=Next[i]=0;
}
cnt=0;
}
}Ha;
//int ct=0;
bool ck()
{
if(1ll*n*m<=c+1) return true;
if(1ll*n*m==c+2)
{
//++ct;
/*printf("ct is:%d\n",ct);
if(ct==5)
{
puts("");
}*/
int kx[5]={},ky[5]={};
for(int i=1;i<=c;i++)
Ha.ins(dx[i],dy[i],1,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(Ha.qry(i,j)==-1)
{
kx[++kx[0]]=i;
ky[++ky[0]]=j;
}
int d=abs(kx[1]-kx[2])+abs(ky[1]-ky[2]);
Ha.clear();
if(d==1) return true;
}
return false;
}
const int px[5]={0,-1,0,1,0};
const int py[5]={0,0,-1,0,1};
int f[N],vis[N];
int Find(int x){return f[x]=f[x]==x?x:Find(f[x]);}
void Merge(int x,int y)
{
f[Find(y)]=Find(x);
}
void dfs(int x,int y,int anc)
{
int now=Ha.qry(x,y);
if(now==-1||Ha.is[now]||vis[now]) return;
vis[now]=1;
Merge(anc,now);
for(int i=1;i<=4;i++)
{
int tx=x+px[i],ty=y+py[i];
dfs(tx,ty,anc);
}
}
void dfs(int x,int y,int col,int &flag)
{
int now=Ha.qry(x,y);
if(now==-1||vis[now]||flag) return;
vis[now]=1;
for(int i=1;i<=4;i++)
{
int tx=x+px[i],ty=y+py[i],to=Ha.qry(tx,ty);
if(to!=-1&&!Ha.is[to])
{
if(!col) col=Find(to);
else if(col!=Find(to))
{
flag=1;
return;
}
}
}
for(int i=1;i<=4;i++)
{
int tx=x+px[i],ty=y+py[i];
dfs(tx,ty,col,flag);
}
}
bool ck0()
{
for(int i=1;i<=Ha.cnt;i++) f[i]=i,vis[i]=0;
for(int i=1;i<=Ha.cnt;i++)
if(!Ha.is[i]&&!vis[i])
dfs(Ha.dx[i],Ha.dy[i],i);
int flag=0;
for(int i=1;i<=Ha.cnt;i++)
if(Ha.is[i]&&!vis[i])
{
dfs(Ha.dx[i],Ha.dy[i],0,flag);
if(flag) return true;
}
return false;
}
int head[N],to[N<<2],Next[N<<2],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int dfn[N],low[N],dfsclock;
void tarjan(int now,int fa,int &cut)
{
if(cut) return;
dfn[now]=low[now]=++dfsclock;
int chi=0;
for(int v,i=head[now];i&&!cut;i=Next[i])
if((v=to[i])!=fa)
{
if(!dfn[v])
{
++chi;
tarjan(v,now,cut);
if(low[v]>=dfn[now]&&now!=fa&&Ha.dis[now]==1)
{
cut=1;
return;
}
low[now]=min(low[now],low[v]);
}
else
low[now]=min(low[now],dfn[v]);
}
if(now==fa&&chi>1&&Ha.dis[now]==1) cut=1;
}
void work()
{
read(n),read(m),read(c);
for(int i=1;i<=c;i++) read(dx[i]),read(dy[i]);
if(ck())
{
puts("-1");
return;
}
if(n==1)
{
std::sort(dy+1,dy+1+c);
int ans=1,p=1;
while(p<=c)
{
int loc=p;
while(loc<=c&&dy[loc+1]==dy[loc]+1) ++loc;
if(dy[p]>1&&dy[p]<m)
{
ans=0;
break;
}
p=loc+1;
}
printf("%d\n",ans);
return;
}
if(m==1)
{
std::sort(dx+1,dx+1+c);
int ans=1,p=1;
while(p<=c)
{
int loc=p;
while(loc<=c&&dx[loc+1]==dx[loc]+1) ++loc;
if(dx[p]>1&&dx[p]<n)
{
ans=0;
break;
}
p=loc+1;
}
printf("%d\n",ans);
return;
}
for(int x,y,i=1;i<=c;i++)
{
x=dx[i],y=dy[i];
for(int j=-2;j<=2;j++)
for(int k=-2;k<=2;k++)
{
int d=max(abs(j),abs(k));
if(!j&&!k) Ha.ins(x+j,y+k,1,d);
else Ha.ins(x+j,y+k,0,d);
}
}
for(int x,y,i=1;i<=Ha.cnt;i++)
{
if(Ha.is[i]) continue;
x=Ha.dx[i],y=Ha.dy[i];
for(int l=1;l<=4;l++)
{
int tx=x+px[l],ty=y+py[l];
int v=Ha.qry(tx,ty);
if(~v&&!Ha.is[v]) add(i,v);
}
}
if(ck0())
{
puts("0");
for(int i=1;i<=Ha.cnt;i++) head[i]=0;
cnt=0;
Ha.clear();
return;
}
int cut=0;
for(int i=1;i<=Ha.cnt;i++)
if(!dfn[i])
{
tarjan(i,i,cut);
if(cut) break;
}
if(cut) puts("1");
else puts("2");
for(int i=1;i<=Ha.cnt;i++)
head[i]=dfn[i]=low[i]=0;
cnt=dfsclock=0;
Ha.clear();
}
int main()
{
int T;read(T);
while(T--) work();
return 0;
}

2019.5.31

「NOI2016」网格 解题报告的更多相关文章

  1. 「NOI2016」区间 解题报告

    「NOI2016」区间 最近思维好僵硬啊... 一上来就觉得先把区间拆成两个端点进行差分,然后扫描位置序列,在每个位置维护答案,用数据结构维护当前位置的区间序列,但是不会维护. 于是想研究性质,想到为 ...

  2. 「ZJOI2016」旅行者 解题报告

    「ZJOI2016」旅行者 对网格图进行分治. 每次从中间选一列,然后枚举每个这一列的格子作为起点跑最短路,进入子矩形时把询问划分一下,有点类似整体二分 至于复杂度么,我不会阿 Code: #incl ...

  3. 「HNOI2016」树 解题报告

    「HNOI2016」树 事毒瘤题... 我一开始以为每次把大树的子树再接给大树,然后死活不知道咋做,心想怕不是个神仙题哦 然后看题解后才发现是把模板树的子树给大树,虽然思维上难度没啥了,但是还是很难写 ...

  4. 「HNOI2016」序列 解题报告

    「HNOI2016」序列 有一些高妙的做法,懒得看 考虑莫队,考虑莫队咋移动区间 然后你在区间内部找一个最小值的位置,假设现在从右边加 最小值左边区间显然可以\(O(1)\),最小值右边的区间是断掉的 ...

  5. 「HNOI2016」网络 解题报告

    「HNOI2016」网络 我有一个绝妙的可持久化树套树思路,可惜的是,它的空间是\(n\log^2 n\)的... 注意到对一个询问,我们可以二分答案 然后统计经过这个点大于当前答案的路径条数,如果这 ...

  6. 「HAOI2018」染色 解题报告

    「HAOI2018」染色 是个套路题.. 考虑容斥 则恰好为\(k\)个颜色恰好为\(c\)次的贡献为 \[ \binom{m}{k}\sum_{i\ge k}(-1)^{i-k}\binom{m-k ...

  7. 「HNOI2016」最小公倍数 解题报告

    「HNOI2016」最小公倍数 考虑暴力,对每个询问,处理出\(\le a,\le b\)的与询问点在一起的联通块,然后判断是否是一个联通块,且联通块\(a,b\)最大值是否满足要求. 然后很显然需要 ...

  8. 「SCOI2016」围棋 解题报告

    「SCOI2016」围棋 打CF后困不拉基的,搞了一上午... 考虑直接状压棋子,然后发现会t 考虑我们需要上一行的状态本质上是某个位置为末尾是否可以匹配第一行的串 于是状态可以\(2^m\)压住了, ...

  9. 「SCOI2016」妖怪 解题报告

    「SCOI2016」妖怪 玄妙...盲猜一个结论,然后过了,事后一证,然后假了,数据真水 首先要最小化 \[ \max_{i=1}^n (1+k)x_i+(1+\frac{1}{k})y_i \] \ ...

随机推荐

  1. 云平台(cloud platforms)

    云平台:允许开发者们或是将写好的程序放在“云”里运行,或是使用“云”里提供的服务,或二者皆是的服务 转向云计算(cloud computing),是业界将要面临的一个重大改变.各种云平台(cloud ...

  2. C# Self Injector into non managed process

    Hey all, I'm gonna explain you how make a self injecting program in C#.I hope you guys thinks its us ...

  3. 测开之路五十七:实现runner和测试报告

    准备测试用例 from fox.case import Casefrom src import Calculator class TestCalculator(Case): def setUp(sel ...

  4. 全面了解python中的类,对象,方法,属性

    全面了解python中的类,对象,方法,属性 python中一切皆为对象,所谓对象:我自己就是一个对象,我玩的电脑就是对象,坐着的椅子就是对象,家里养的小狗也是一个对象...... 我们通过描述属性( ...

  5. Makefile之自动化变量

    makefile自动化变量在大型项目的Makefile使用的太普遍了,如果你看不懂自动化变量,开源项目的makefile你是看不下去的. 以往总是看到一些项目的makefile,总是要翻gnu的Mak ...

  6. T1218:取石子游戏

    [题目描述] 有两堆石子,两个人轮流去取.每次取的时候,只能从较多的那堆石子里取,并且取的数目必须是较少的那堆石子数目的整数倍,最后谁能够把一堆石子取空谁就算赢. 比如初始的时候两堆石子的数目是25和 ...

  7. 【读书笔记】:MIT线性代数(4):Independence, Basis and Dimension

    Independence: The columns of A are independent when the nullspace N (A) contains only the zero vecto ...

  8. 利用print函数模拟打印进度条

    import time , , ): time.sleep(0.1) num = i // 2 # 地板除,即取不大于/后的最小整数(3//2 = 1, 9//4 = 2, -7//2 = -4) s ...

  9. spring data jpa 多对多查询

    package com.ytkj.dao; import com.ytkj.entity.Customer; import com.ytkj.entity.Role; import org.sprin ...

  10. 关于array_merge()的注意

    array_merge() 函数把两个或多个数组合并为一个数组. 1 如果键名有重复,该键的键值为最后一个键名对应的值(后面的覆盖前面的). 2 如果数组是数字索引的,则键名会以连续方式重新索引. 2 ...