【XSY2741】网格 分治 LCT 并查集

题目描述

  有一个\(n\times m\)的网格,线框的交点可以扭动,边不可伸缩。网格中有一些格子里面放了'x'形的支架,这些格子不会变形,但可以整体转动。如果所有格子都不能变形,那么称这个网格稳固。

  有\(q\)个操作,每次改变一个格子的状态,即有支架给为无支架,无支架改为有支架。

  请你判断初始及每次操作后这个网格是否稳固。

  比如说下面这个网格就不稳固。

1097689-20180310185007146-39286934.png

  \(n,m\leq 3000,q\leq 100000\)

题解

  先看看怎么判断一个网格是否稳固。

  先给整个网格的左边和上边加上一行一列,然后把第一行的最左边两个格子设为有支架的格子,其他的设为没支架的格子。

  样例那个图就会变成这样

1097689-20180310185020433-1690459836.png

  可以发现这样操作是不会改变整个图形的稳定性的。

  设格子\((i,j)\)右下角的角度为\(a_{i,j}+90\)

  因为一个交点四个角的度数和为\(360\),所以可以列出以下方程:
\[ \begin{align} 90+a_{i-1,j-1}+180-90-a_{i-1,j}+180-90-a_{i,j-1}+90+a_{i,j}&=360\\ a_{i-1,j-1}+a_{i,j}-a_{i,j-1}-a_{i-1,j}&=0 \end{align} \]
  然后通过一些简单变换可以得到
\[ a_{0,0}+a_{i,j}-a_{0,j}-a_{i,0}=0 \]
  因为\(a_{0,0}=0\),所以方程简化为
\[ a_{i,j}=a_{i,0}+a_{0,j} \]
  当\((i,j)\)有支架时\(a_{i,0}+a_{0,j}=a_{i,j}=0\),即\(a_{i,0}=-a_{0,j}\),那么我们就在图\(G\)\(i\)\(j+n\)两个点之间连一条边。

  显然这个图是二分图。

  因为边界上只有\(a_{0,1}=0\),所以一个点只有和\(n+1\)号点\((0,1)\)属于同一个联通块,这个点对应的角的角度才是确定的。

  当\((i,j)\)无支架时\(a_{i.j}=a_{i,0}+a_{0,j}\)。如果\(a_{i,0}\)\(a_{0,j}\)之间有一个没有确定,那么\(a_{i,j}\)也没有确定。

  所以说,这个网格是稳定的\(\Longleftrightarrow\)\(G\)只有一个联通块。

  现在问题就变成了:有一个\(n+m\)个点的图,有\(nm\)条边,还有\(q\)个加边删边的操作。问操作前和每一次操作完后联通块个数是不是\(1\)

  用分治+并查集和LCT都可以做。

  可以把一定存在的边先用路径压缩的并查集处理完。

  分治+并查集:\(O(nm\alpha+q\log^2(n+m))\)

  LCT:\(O(nm\alpha+q\log (n+m))\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<utility>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
void open(const char *s)
{
#ifndef ONLINE_JUDGE
    char str[100];
    sprintf(str,"%s.in",s);
    freopen(str,"r",stdin);
    sprintf(str,"%s.out",s);
    freopen(str,"w",stdout);
#endif
}
vector<pii> a[400010];
int f[10010];
int r[10010];
int ans[100010];
int s1[10010];//x
int s2[10010];//f[x]
int s3[10010];//r[f[x]]
int top;
int n,m,q;
char s[10010];
int c[3010][3010];
int find(int x)
{
    return f[x]==x?x:find(f[x]);
}
int find2(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
int num=0;
int merge(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x==y)
        return 0;
    if(r[x]>r[y])
        swap(x,y);
    top++;
    s1[top]=x;
    s2[top]=y;
    s3[top]=r[y];
    if(r[x]==r[y])
        r[y]++;
    f[x]=y;
    return 1;
}
int merge2(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x==y)
        return 0;
    if(r[x]>r[y])
        swap(x,y);
    f[x]=y;
    if(r[x]==r[y])
        r[y]++;
    return 1;
}
void back()
{
    f[s1[top]]=s1[top];
    r[s2[top]]=s3[top];
    top--;
}
void add(int p,int l,int r,int x,int y,int L,int R)
{
    if(l<=L&&r>=R)
    {
        a[p].push_back(pii(x,y));
        return;
    }
    int mid=(L+R)>>1;
    if(l<=mid)
        add(p<<1,l,r,x,y,L,mid);
    if(r>mid)
        add((p<<1)|1,l,r,x,y,mid+1,R);
}
void solve(int l,int r,int p)
{
    int now=top;
    for(auto v:a[p])
        if(merge(v.first,v.second))
            num++;
    if(l==r)
        ans[l]=(num==n+m-1);
    else
    {
        int mid=(l+r)>>1;
        solve(l,mid,p<<1);
        solve(mid+1,r,(p<<1)|1);
    }
    while(top>now)
    {
        back();
        num--;
    }
}
int main()
{
    open("grid");
    scanf("%d%d%d",&n,&m,&q);
    memset(c,-1,sizeof c);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
            if(s[j]=='x')
                c[i][j]=0;
    }
    for(int i=1;i<=n+m;i++)
    {
        f[i]=i;
        r[i]=1;
    }
    int x,y;
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&x,&y);
        if(~c[x][y])
        {
            add(1,c[x][y],i-1,x,y+n,0,q);
            c[x][y]=-1;
        }
        else
            c[x][y]=i;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(~c[i][j])
            {
                if(c[i][j])
                    add(1,c[i][j],q,i,j+n,0,q);
                else
                    if(merge2(i,j+n))
                        num++;
            }
    for(int i=1;i<=n+m;i++)
        find2(i); 
    solve(0,q,1);
    for(int i=0;i<=q;i++)
        if(ans[i])
            printf("S\n");
        else
            printf("U\n");
    return 0;
}

转载于:https://www.cnblogs.com/ywwyww/p/8541405.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值