[HBCPC 2024]The 2024 International Collegiate Programming Contest in Hubei Province, China VP&做题笔记

gym 105139。

一个人 VP,4 小时做了 \(7\) 题,然后就去吃饭了。下面的题按 AC 顺序排序。

A.Long Live

简单化简式子即可得到 \(a=1\)\(b=\frac{xy}{\gcd(x,y)^2}\)

void solve(){
    int n,m;
    read(n),read(m);
    int x=__gcd(n,m);
    printf("1 %lld\n",n*m/x/x);
}

E.Spicy or Grilled?

简单加加减减。

void solve(){
    int n,m,a,b;
    read(n),read(m),n-=m;
    read(a),read(b);
    printf("%lld\n",n*a+m*b);
}

J.Points on the Number Axis A

一个很符合直觉的观察是,答案是所有点坐标的平均数。

int quick_power(int base,int x){
    int res=1;
    while(x){
        if(x&1)res*=base,res%=mo;
        base*=base,base%=mo;
        x>>=1;
    }
    return res;
}
void solve(){
    int n;
    read(n);
    int ans=0;
    rep(i,1,n){
        int x;
        read(x);
        ans+=x,ans%=mo;
    }
    printf("%lld\n",ans*quick_power(n,mo-2)%mo);
}

B.Nana Likes Polygons

任意一个 \(n\) 边形(\(n\geq 4\))都有三角剖分,因此面积最小的一定是三角形。\(O(Tn^3)\) 枚举顶点,然后用叉积算面积即可。

int n;
pii p[N];
double gets(pii x,pii y,pii z){
    pii a=mp(x.fir-y.fir,x.sec-y.sec),b=mp(x.fir-z.fir,x.sec-z.sec);
    double res=(a.fir*b.sec-a.sec*b.fir)/1.0/2;
    return res;
}
void solve(){
    read(n);
    rep(i,1,n)
        read(p[i].fir),read(p[i].sec);
    double ans=1e18;
    rep(i,1,n){
        rep(j,1,n){
            if(p[i]==p[j])continue;
            rep(k,1,n){
                if(p[k]==p[i]||p[k]==p[j])continue;
                double res=gets(p[i],p[j],p[k]);
                if(res>0)ans=min(ans,res);
            }
        }
    }
    if(ans==1e18)puts("-1");
    else printf("%.8Lf\n",ans);
}

L.LCMs

不妨设 \(a\leq b\)

  • \(a=b\) 时,答案为 \(0\)
  • \(b\bmod a=0\) 时,答案为 \(b\)
  • 否则,至少要有两次转化,答案下界是 \(a+b\)
    • \(\gcd(a,b)\neq 1\),可以取得下界 \(a+b\),跳到 \(\gcd\) 的位置即可。
    • 否则,一定会有一次贡献为两个互质的数的转化的操作,这个的代价是 \(v_{xy}=\min(xy,2(x+y))\)(要么直接转,要么选最小的数作为桥梁来转)。我们希望最小化这两个东西的乘积和加和,即在 \(a\)\(b\) 的最小非 \(1\) 因数(下简称“最小因数”)处取得。设 \(a,b\) 的最小因数分别为 \(x,y\),答案为 \(a\times[a\neq x]+b\times [b\neq y]+v_{xy}\)

最后一部分的正确性证明:考虑分类讨论。首先我们选的一定是 \(a\)\(b\) 的质因数,否则会带来无意义的因子。其次,对于不同的质数 \(x,y\)\(xy<2(x+y)\) 成立当且仅当 \(x=3\)\(y=5\)

  • \(a,b\) 均为质数时,答案显然最小。
  • 下面设 \(a,b\) 选取的因数(不要求最小)为 \(x,y\)。当 \(a,b\) 中恰有一个质数时,不妨设为 \(a\)。若选 \(y\),显然是选最小的因数,答案为 \(2(a+y)+b\);若不选,则答案为 \(2(a+b)\)。显然有 \(2y\leq b\),答案取得最小。对于 \(v\) 取乘积值的情况,仅有选最小因数时,可能使答案进一步减小。因此答案为 \(a\times[a\neq x]+b\times [b\neq y]+v_{xy}\) 时最小。
  • 两者都不为质数时,若两者有至少一个不选因数,则根据上面的讨论可知答案最小为 \(2(a+y)+b\)(此时 \(a\) 不选因数,\(b\) 选因数)。若两边都选因数,则答案为 \(a+b+2(x+y)\)。根据上面的讨论可知 \(2(x+y)+a\leq 2(a+y)\),特殊取值的情况同样只会使两边都取因数时的答案进一步减小。因此答案为 \(a\times[a\neq x]+b\times [b\neq y]+v_{xy}\) 时最小。
void solve(){
    read(a),read(b);
    if(a>b)swap(a,b);
    if(a==b)puts("0");
    else if(b%a==0)printf("%lld\n",b);
    else if(__gcd(a,b)!=1)printf("%lld\n",a+b);
    else{
        int res=0,x=a,y=b;
        rep(i,2,sqrt(a))
            if(a%i==0)x=min(x,i);
        rep(i,2,sqrt(b))
            if(b%i==0)y=min(y,i);
        if(x!=a)res+=a;
        if(y!=b)res+=b;
        res+=min(x*y,2*(x+y));
        printf("%lld\n",res);
    }
}

H.Genshin Impact Startup Forbidden III

把有鱼位置的鱼数量压成四进制,对有鱼的位置及鱼周围的 \(4\) 个位置做离散化,跑状压即可。

int n,m,k,cntp,tot;
pii tp[N],pk[N];
int dp[55][S],id[N][N],num[N][N],idk[N][N],val[N];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int modify(int s,int p,int v){
    p--;
    if(v&1)s|=(1<<(2*p));
    else s&=(tot^(1<<(2*p)));
    if((v>>1)&1)s|=(1<<(2*p+1));
    else s&=(tot^(1<<(2*p+1)));
    return s;
}
int nnum(int s,int p){
    p--;
    s=(s>>(2*p))&3;
    return s;
}
void solve(){
    read(n),read(m),read(k);
    rep(i,1,k){
        int x,y;
        read(x),read(y),read(num[x][y]),val[i]=num[x][y];
        pk[i]=mp(x,y),idk[x][y]=i;
        id[x][y]=++cntp,tp[cntp]=mp(x,y);
        rep(l,0,3){
            int tx=x+dx[l],ty=y+dy[l];
            if(tx<1||tx>n||ty<1||ty>m)continue;
            if(id[tx][ty])continue;
            id[tx][ty]=++cntp,tp[cntp]=mp(tx,ty);
        }
    }
    tot=(1<<(2*k))-1;
    rep(i,0,cntp){
        rep(j,0,tot)
            dp[i][j]=inf;
    }
    int inits=0;
    rep(i,1,k)
        inits=modify(inits,i,val[i]);
    dp[0][inits]=0;
    rep(i,1,cntp){
        int x=tp[i].fir,y=tp[i].sec;
        rep(st,0,tot){
            if(dp[i-1][st]==inf)continue;
            rep(j,0,3){
                int nw=st;
                if(idk[x][y])nw=modify(nw,idk[x][y],max(0,nnum(nw,idk[x][y])-j));
                rep(l,0,3){
                    int tx=dx[l]+x,ty=y+dy[l];
                    if(tx<1||tx>n||ty<1||ty>m)continue;
                    if(idk[tx][ty])nw=modify(nw,idk[tx][ty],max(0,nnum(nw,idk[tx][ty])-j));
                }
                dp[i][nw]=min(dp[i][nw],dp[i-1][st]+j);
            }
        }
    }
    printf("%d\n",dp[cntp][0]);
}

G.Genshin Impact Startup Forbidden II

开两个并查集分别维护黑子和白子的连通块及其气数,放子时先计算当前位置的气数,然后和四个方向可合并的连通块作合并,然后判断四个方向的敌方连通块是否死掉,最后判断自己和四个方向的我方连通块是否死掉。对于死掉的连通块,直接 dfs 遍历每个死掉的棋子,取走的时候记得加上四周敌方的气数。由于每个棋子只会死一次,所以 dfs 是均摊的。复杂度为 \(O(\alpha(m))\)

int T,n=19*19;
int getid(int x,int y){
    return (x-1)*19+y;
}
struct bcj{
    int fa[N],val[N];
    void init(){
        rep(i,1,n)
            fa[i]=i,val[i]=0;
    }
    int find(int x){
        if(fa[x]==x)return x;
        return fa[x]=find(fa[x]);
    }
    void merge(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return;
        fa[x]=y,val[y]+=val[x];
    }
    void clr(int x){
        fa[x]=x,val[x]=0;
    }
    void modify(int x,int v){
        x=find(x),val[x]+=v;
    }
    int query(int x){
        x=find(x);
        return val[x];
    }
}B[2];
bool vis[2][20][20];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0},num[2],ynum[2];
void delp(int x,int y,int op){
    B[op].clr(getid(x,y)),num[op]--;
    vis[op][x][y]=0;
    rep(i,0,3){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1||tx>19||ty<1||ty>19)continue;
        if(!vis[op^1][tx][ty])continue;
        B[op^1].modify(getid(tx,ty),1);
    }
}
void dfsc(int p,int op){
    int x=(p-1)/19+1,y=p-19*(x-1);
    delp(x,y,op);
    rep(i,0,3){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1||tx>19||ty<1||ty>19)continue;
        if(!vis[op][tx][ty])continue;
        dfsc(getid(tx,ty),op);
    }
}
void addp(int x,int y,int op){
    int nst=getid(x,y);
    vis[op][x][y]=1;
    rep(i,0,3){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1||tx>19||ty<1||ty>19)continue;
        if(vis[op][tx][ty]||vis[op^1][tx][ty])continue;
        B[op].modify(nst,1);
    }
    rep(i,0,3){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1||tx>19||ty<1||ty>19)continue;
        if(!vis[op][tx][ty])continue;
        int nid=getid(tx,ty);
        B[op].merge(nst,nid);
    }
    rep(i,0,3){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1||tx>19||ty<1||ty>19)continue;
        if(!vis[op^1][tx][ty])continue;
        int nid=getid(tx,ty);
        B[op^1].modify(nid,-1);
        if(!B[op^1].query(nid))dfsc(nid,op^1);
    }
    if(!B[op].query(nst)){
        dfsc(nst,op);
        return;
    }
    rep(i,0,3){
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1||tx>19||ty<1||ty>19)continue;
        if(!vis[op][tx][ty])continue;
        int nid=getid(tx,ty);
        B[op].modify(nid,-1);
        if(!B[op].query(nid))dfsc(nid,op);
    }
}
int m;
void solve(){
    read(m);
    B[0].init(),B[1].init();
    int op=0;
    while(m--){
        int x,y;
        read(x),read(y);
        num[op]++;
        rep(i,0,1)
            ynum[i]=num[i];
        addp(x,y,op);
        printf("%d %d\n",ynum[0]-num[0],ynum[1]-num[1]);
        op^=1;
    }
}

F.Enchanted

首先合并书本的过程等价于一个二进制加法,最终合并的书的等级就是加和值为 \(1\) 位置的位数,不难发现结果是唯一确定的。

因此操作 \(1\) 就是区间求和后找最高位;操作 \(2\) 就是区间求和后找 \(k\) 往上的连续位数,操作 \(3\) 用树状数组即可解决。操作 \(4\) 考虑离线之后建操作树,让每一次操作对应一个新的节点即可,仅需在操作 \(3\) 的节点做修改和回退(记录改之前的值)。

int n,m,tmp,p,q;
int rnd(){
    tmp=(7*tmp+13)%19260817;
    return tmp;
}
int a[N];
struct BIT{
    int t[4*N];
    void add(int x,int v){
        for(int i=x;i<=n;i+=lowbit(i))
            t[i]+=v;
    }
    int query(int x){
        int res=0;
        for(int i=x;i;i-=lowbit(i))
            res+=t[i];
        return res;
    }
}T;
struct oper{
    int op,l,r,k;//pos/t:l
}t[N];
int fa[N];
vector<int>e[N];
int ans[N];
//离线操作树+树状数组
void dfs(int x){
    int prev;
    if(t[x].op==0){
        rep(i,1,n)
            T.add(i,1<<(a[i]-1));
    }
    if(t[x].op==1){
        int le=t[x].l,ri=t[x].r;
        int res=T.query(ri)-T.query(le-1);
        ans[x]=floor(log2(res))+1;
    }
    if(t[x].op==2){
        int le=t[x].l,ri=t[x].r,k=t[x].k;
        int res=T.query(ri)-T.query(le-1);
        res>>=k-1;
        ans[x]=0;
        int nwv=1ll<<(k+1);
        while((res&1))
            ans[x]+=nwv,nwv<<=1,res>>=1,ans[x]%=mo;
    }
    if(t[x].op==3){
        int npos=t[x].l,nwv=(1<<(t[x].k-1));
        prev=a[npos];
        T.add(npos,nwv-(1<<(a[npos]-1)));
        a[npos]=t[x].k;
    }
    for(auto j:e[x])
        dfs(j);
    if(t[x].op==3){
        int npos=t[x].l;
        T.add(npos,(1<<(prev-1))-(1<<(a[npos]-1)));
        a[npos]=prev;
    }
}
signed main(){
    read(n),read(m),read(tmp),read(p),read(q);
    rep(i,1,n)
        a[i]=rnd()%q+1;
    rep(i,1,m){
        ans[i]=-1;
        int op=rnd()%p+1;
        if(op==1){
            int l=rnd()%n+1,r=rnd()%n+1;
            if(l>r)swap(l,r);
            t[i]=(oper){op,l,r,0};
        }
        if(op==2){
            int l=rnd()%n+1,r=rnd()%n+1;
            if(l>r)swap(l,r);
            int k=rnd()%q+1;
            t[i]=(oper){op,l,r,k};
        }
        if(op==3){
            int pos=rnd()%n+1,k=rnd()%q+1;
            t[i]=(oper){op,pos,pos,k};
        }
        if(op==4){
            int ver=rnd()%i;
            t[i]=(oper){op,ver,ver,0};
        }
    }
    rep(i,1,m){
        if(t[i].op==4)fa[i]=t[i].l;
        else fa[i]=i-1;
    }
    rep(i,1,m)
        e[fa[i]].push_back(i);
    dfs(0);
    rep(i,1,m)
        if(ans[i]!=-1)printf("%lld\n",ans[i]);
    return 0;
}

I.Colorful Tree

首先树上路径染色可以并查集维护,然后暴力染色,复杂度均摊。

然后正着扫一遍,并查集维护黑色连通块及其直径可以求出每次操作后黑色连通块的直径最大值。不妨在此过程中求出每个点被染黑的时间。

最后时光倒流,利用每个点被染黑的时间,新开并查集维护白色连通块即可。

int n,m;
vector<int>e[N];
int fa[N][20],dep[N];
int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    repp(i,18,0)
        if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return x;
    repp(i,18,0)
        if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int getdis(int x,int y){
    return dep[x]+dep[y]-2*dep[lca(x,y)]+1;
}
struct node{
    int val,p[2];
    void init(int x){
        val=1,p[0]=p[1]=x;
    }
    friend node operator+(node x,node y){
        node res;
        res.val=res.p[0]=res.p[1]=0;
        if(x.val>y.val)res=x;
        else res=y;
        rep(i,0,1){
            rep(j,0,1){
                int nwv=getdis(x.p[i],y.p[j]);
                if(nwv>res.val)res.val=nwv,res.p[0]=x.p[i],res.p[1]=y.p[j];
            }
        }
        return res;
    }
};
struct bcj{
    node dir[N];
    int fa[N];
    void init(){
        rep(i,1,n)
            fa[i]=i,dir[i].init(i);
    }
    int find(int x){
        if(fa[x]==x)return x;
        return fa[x]=find(fa[x]);
    }
    void merge(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return;
        if(dep[x]>dep[y])swap(x,y);
        fa[y]=x,dir[x]=dir[x]+dir[y];
    }
    int query(int x){
        x=find(x);
        return dir[x].val;
    }
}B;
int tms[N],ans1[N],ans2[N];
void dfs(int x,int f){
    fa[x][0]=f,dep[x]=dep[f]+1;
    rep(i,1,18)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(auto j:e[x]){
        if(j==f)continue;
        dfs(j,x);
    }
}
void pntp(int x,int t){
    if(tms[x]<=t)return;
    tms[x]=t;
    for(auto j:e[x])
        if(tms[j]<=t)B.merge(x,j);
    ans1[t]=max(ans1[t],B.query(x));
}
void pntl(int x,int y,int t){
    while(dep[x]>=dep[y])
        pntp(x,t),x=fa[B.find(x)][0];
}
vector<int>pt[N];
bool vis[N];
void solve(){
    read(n),read(m);
    rep(i,1,n)
        e[i].clear(),tms[i]=inf,vis[i]=0;
    rep(i,1,n-1){
        int x,y;
        read(x),read(y);
        e[x].push_back(y),e[y].push_back(x);
    }
    dfs(1,0);
    B.init();
    rep(i,0,m+1)
        pt[i].clear(),ans1[i]=ans2[i]=0;
    rep(i,1,m){
        ans1[i]=ans1[i-1];
        int x,y,lcap;
        read(x),read(y),lcap=lca(x,y);
        pntl(x,lcap,i),pntl(y,lcap,i);
    }
    rep(i,1,n)
        pt[min(m,tms[i]-1)].push_back(i);
    B.init();
    repp(i,m,1){
        ans2[i]=ans2[i+1];
        for(auto p:pt[i]){
            vis[p]=1;
            for(auto j:e[p])
                if(vis[j])B.merge(p,j);
            ans2[i]=max(ans2[i],B.query(p));
        }
    }
    rep(i,1,m)
        printf("%d\n",max(ans1[i],ans2[i]));
}

D.MACARON Likes Happy Endings

\(a\) 做前缀异或,就变成在区间中选两个点,使得异或值为 \(d\)

首先有一个 \(O(n^2k)\) 的 dp,贡献可以类似莫队的方式,对值域开桶计算。

不难发现这个贡献满足四边形不等式,于是可以上二分优化决策单调性。一个经典的 trick 是,二分过程中询问贡献的区间的左右端点的移动量是 \(O(n\log n)\) 的,于是可以用同样的方式计算。最后复杂度 \(O(nk\log n)\)

int n,k,d;
int dp[S][N],a[N];
int cnt[M],nwl,nwr,nwv;
int calc(int le,int ri){
    if(le>ri)return inf;
    while(nwl>le)
        nwl--,nwv+=cnt[d^a[nwl]],cnt[a[nwl]]++;
    while(nwr<ri)
        nwr++,nwv+=cnt[d^a[nwr]],cnt[a[nwr]]++;
    while(nwl<le)
        cnt[a[nwl]]--,nwv-=cnt[d^a[nwl]],nwl++;
    while(nwr>ri)
        cnt[a[nwr]]--,nwv-=cnt[d^a[nwr]],nwr--;
    return nwv;
}
void solve_dp(int op,int le,int ri,int ql,int qr){
    if(ql>qr)return;
    if(le==ri){
        rep(i,ql,qr)
            dp[op][i]=min(dp[op][i],dp[op-1][le]+calc(le,i));
        return;
    }
    int res=inf,minp=0,mid=(ql+qr)>>1;
    rep(i,le,min(ri,mid-1)){
        int nwv=dp[op-1][i]+calc(i,mid);
        if(nwv<res)res=nwv,minp=i;
    }
    dp[op][mid]=res;
    solve_dp(op,le,minp,ql,mid-1),solve_dp(op,minp,ri,mid+1,qr);
}
signed main(){
    read(n),read(k),read(d);
    rep(i,1,n)
        read(a[i]),a[i]^=a[i-1];
    nwl=1,nwr=0;
    rep(i,0,k){
        rep(j,0,n)
            dp[i][j]=inf;
    }
    dp[0][0]=0;
    rep(i,1,k)
        solve_dp(i,0,n,1,n);
    int ans=inf;
    rep(i,0,k)
        ans=min(ans,dp[i][n]);
    printf("%lld\n",ans);
    return 0;
}

K.Points on the Number Axis B

用线性性拆期望,每个点的坐标都会带一个系数 \(k_i\)。考虑 dp 求系数。设 \(dp_{i,j}\) 表示左边有 \(i\) 个点,右边有 \(j\) 个点的贡献,\(dp_{0,0}=1\)。转移时枚举下一步删掉的是哪两个点,有:

\[\begin{aligned} dp_{i,j}&=\frac{i-1}{i+j}dp_{i-1,j}+\frac 12 \frac 1{i+j}dp_{i-1,j}+\frac{j-1}{i+j}dp_{i,j-1}+\frac 12\frac 1{i+j}dp_{i,j-1}\\ &=\frac{i-\frac12}{i+j}dp_{i-1,j}+\frac{j-\frac12}{i+j}dp_{i,j-1}\\ (i+j)!dp_{i,j}&=(i-\frac12)(i+j-1)!dp_{i-1,j}+(j-\frac12)(i+j-1)!dp_{i,j-1}\\ \end{aligned} \]

不妨设 \(f_{i,j}=(i+j)!dp_{i,j}\),有 \(f_{i,j}=(i-\frac12)f_{i-1,j}+(j-\frac12)f_{i,j-1}\)

这是一个经典的网格图优化形式,等价于一个带权路径和问题。从 \((0,0)\) 到点 \((i,j)\) 的所有路径权值均为 \(\prod_{x=1}^i(x-\frac12)\prod_{y=1}^j(y-\frac12)\),合法路径数量为 \(\binom{i+j}{i}\)。于是预处理 \(\prod_{x=1}^i(x-\frac12)\) 之后,\(f\)\(dp\) 都可以 \(O(1)\) 求了。

int n,a[N];
int jc[N],qj[N],val[N];
int quick_power(int base,int x){
    int res=1;
    while(x){
        if(x&1)res*=base,res%=mo;
        base*=base,base%=mo;
        x>>=1;
    }
    return res;
}
int C(int x,int y){
    if(x<y)return 0;
    return jc[x]*qj[y]%mo*qj[x-y]%mo;
}
int inv2;
signed main(){
    read(n),inv2=quick_power(2,mo-2);
    rep(i,1,n)
        read(a[i]);
    jc[0]=qj[0]=val[0]=1;
    rep(i,1,n){
        val[i]=val[i-1]*(i-inv2+mo)%mo;
        jc[i]=jc[i-1]*i%mo;
        qj[i]=quick_power(jc[i],mo-2);
    }
    int ans=0;
    rep(i,1,n){
        int le=i-1,ri=n-i;
        int k=val[le]*val[ri]%mo*C(le+ri,le)%mo*qj[le+ri]%mo;
        ans+=k*a[i]%mo,ans%=mo;
    }
    printf("%lld\n",ans);
    return 0;
}

C.Lili Likes Polygons

最小矩形剖分的分割线必定过至少一个凹角,且不过凸角、不与已有边重合。我们考虑用分割后矩形的内角和计算矩形剖分的数目。先计算原图形的内角和,然后考虑分割对于内角和的影响。

  • 对于分割两个凹角的分割线,它会将内角和减少 \(360^{\circ}\)
  • 对于分割一个凹角和一个平角的分割线,它不影响内角和。

需要注意的是,一个凹角被分割一次后就会变成一个平角和一个凸角,不再产生新的减少量。因此我们找出所有的凹角、以及凹角之间可能的分割线,将凹角作为点、分割线作为边建图。不难发现一个凹角最多对应两个分割线,连出来的图仅包含偶环和链,是二分图。于是减少的次数就是这个二分图的最大匹配数,跑网络流即可。

注意到矩形和角的数目都很小,所以可以离散化坐标,然后暴力覆盖求内角和;也可以暴力找凹角和凹角之间的分割线。细节较多。

int n,m;
int lshx[N],cntx,cnty,lshy[N];
struct oper{
    int l,r,op;
};
vector<oper>op[N];
struct rect{
    int l,r,b,t;
}rec[N];
int vis[N][N];
struct inter{
    int x,y,tp;
}itr[N];
int cnta;
int dx[8]={-1,-1,0,1,1,1,0,-1},dy[8]={0,1,1,1,0,-1,-1,-1};
struct edge{
    int to,nxt,val;
}e[10*N];
int fir[N],np=1,st[N];//flow
void add(int x,int y,int w){
    e[++np]=(edge){y,fir[x],w};
    fir[x]=np;
}
int co[N];
bool exi[N][N];//邻接矩阵
int id[N][N];
void checkadd(int i,int dir){
    if(dir==1){
        repp(x,itr[i].x-1,1){
            int y=itr[i].y;
            if(id[x][y]){
                int nid=id[x][y];
                if(itr[nid].tp==2||itr[nid].tp==3)exi[i][nid]=1;
                break;
            }
            if(!vis[x][y])break;
        }
    }
    else if(dir==2){
        rep(y,itr[i].y+1,cnty){
            int x=itr[i].x;
            if(id[x][y]){
                int nid=id[x][y];
                if(itr[nid].tp==3||itr[nid].tp==4)exi[i][nid]=1;
                break;
            }
            if(!vis[x][y])break;
        }
    }
    else if(dir==3){
        rep(x,itr[i].x+1,cntx){
            int y=itr[i].y;
            if(id[x][y]){
                int nid=id[x][y];
                if(itr[nid].tp==1||itr[nid].tp==4)exi[i][nid]=1;
                break;
            }
            if(!vis[x][y])break;
        }
    }
    else{
        repp(y,itr[i].y-1,1){
            int x=itr[i].x;
            if(id[x][y]){
                int nid=id[x][y];
                if(itr[nid].tp==1||itr[nid].tp==2)exi[i][nid]=1;
                break;
            }
            if(!vis[x][y])break;
        }
    }
}
void dfsc(int x){
    rep(i,1,cnta){
        if(!exi[x][i])continue;
        if(co[i])continue;
        co[i]=co[x]==2?1:2;
        dfsc(i);
    }
}
int dep[N];
bool bfs(){
    rep(i,0,cnta+1)
        dep[i]=0;
    dep[0]=1;
    queue<int>q;
    q.push(0);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        st[x]=fir[x];
        for(int i=fir[x];i;i=e[i].nxt){
            int j=e[i].to;
            if(dep[j]||!e[i].val)continue;
            dep[j]=dep[x]+1,q.push(j);
        }
    }
    return dep[cnta+1]!=0;
}
int dinic(int x,int num){
    if(x==cnta+1)return num;
    if(!num)return 0;
    int lst=num;
    for(int i=st[x];i;i=e[i].nxt){
        int j=e[i].to;
        st[x]=i;
        if(!e[i].val||dep[j]!=dep[x]+1)continue;
        int gnum=dinic(j,min(e[i].val,lst));
        e[i].val-=gnum,e[i^1].val+=gnum,lst-=gnum;
        if(!lst)break;
    }
    return num-lst;
}
signed main(){
    read(n);
    rep(i,1,n){
        read(rec[i].l),read(rec[i].b),read(rec[i].r),read(rec[i].t);//修改了一下输入,长方形i轴范围(l,r),j轴范围(b,t)
        lshx[++cntx]=rec[i].l-1,lshx[++cntx]=rec[i].l,lshx[++cntx]=rec[i].l+1;
        lshx[++cntx]=rec[i].r-1,lshx[++cntx]=rec[i].r,lshx[++cntx]=rec[i].r+1;
        lshy[++cnty]=rec[i].b-1,lshy[++cnty]=rec[i].b,lshy[++cnty]=rec[i].b+1;
        lshy[++cnty]=rec[i].t-1,lshy[++cnty]=rec[i].t,lshy[++cnty]=rec[i].t+1;
    }
    sort(lshx+1,lshx+cntx+1),sort(lshy+1,lshy+cnty+1);
    cntx=unique(lshx+1,lshx+cntx+1)-lshx-1;
    cnty=unique(lshy+1,lshy+cnty+1)-lshy-1;
    rep(i,1,n){
        rec[i].l=lower_bound(lshx+1,lshx+cntx+1,rec[i].l)-lshx;
        rec[i].r=lower_bound(lshx+1,lshx+cntx+1,rec[i].r)-lshx;
        rec[i].b=lower_bound(lshy+1,lshy+cnty+1,rec[i].b)-lshy;
        rec[i].t=lower_bound(lshy+1,lshy+cnty+1,rec[i].t)-lshy;
    }
    rep(i,1,n){
        op[rec[i].l].push_back((oper){rec[i].b,rec[i].t,1});
        op[rec[i].r+1].push_back((oper){rec[i].b,rec[i].t,-1});
    }
    rep(i,1,cntx){
        rep(j,1,cnty)
            vis[i][j]=vis[i-1][j];
        for(auto j:op[i]){
            int l=j.l,r=j.r,v=j.op;
            rep(k,l,r)
                vis[i][k]+=v;
        }
    }
    rep(i,1,cntx){
        rep(j,1,cnty)
            vis[i][j]=min(vis[i][j],1);
    }
    int ans=0;
    rep(i,1,cntx){
        rep(j,1,cnty){
            for(int ls=0;ls<=6;ls+=2){
                bool ok=1,ok2=1;
                rep(l,ls,ls+2){
                    int x=i+dx[l%8],y=j+dy[l%8];
                    if(x<1||y<1||x>cntx||y>cnty)ok=0;
                    else if(!(vis[x][y]^vis[i][j]))ok=0;
                    if(l==ls+1)continue;
                    if(x<1||y<1||x>cntx||y>cnty)ok2=0;
                    else if(!(vis[x][y]^vis[i][j]))ok2=0;
                }
                if(ok){
                    if(vis[i][j])ans++;
                    else{
                        ans+=3;
                        if(ls==0)itr[++cnta]={i,j+1,1},id[i][j+1]=cnta;
                        if(ls==2)itr[++cnta]={i+1,j+1,2},id[i+1][j+1]=cnta;
                        if(ls==4)itr[++cnta]={i+1,j,3},id[i+1][j]=cnta;
                        if(ls==6)itr[++cnta]={i,j,4},id[i][j]=cnta;
                    }
                }
                else if(ok2&&vis[i][j])ans++;
            }
        }
    }
    rep(i,1,cnta){
        if(itr[i].tp==1)checkadd(i,1),checkadd(i,2);
        else if(itr[i].tp==2)checkadd(i,2),checkadd(i,3);
        else if(itr[i].tp==3)checkadd(i,3),checkadd(i,4);
        else checkadd(i,4),checkadd(i,1);
    }
    rep(i,1,cnta)
        if(!co[i])co[i]=1,dfsc(i);
    rep(i,1,cnta){
        if(co[i]==1)add(0,i,1),add(i,0,0);
        else add(i,cnta+1,1),add(cnta+1,i,0);
    }
    rep(i,1,cnta){
        if(co[i]==2)continue;
        rep(j,1,cnta){
            if(co[j]==1)continue;
            if(!exi[i][j])continue;
            add(i,j,1),add(j,i,0);
        }
    }
    int resm=0;
    while(bfs())
        resm+=dinic(0,inf);
    printf("%d\n",(ans-4*resm)/4);
    return 0;
}
posted @ 2024-08-16 11:12  烟山嘉鸿  阅读(195)  评论(0)    收藏  举报