BJ模拟:Mythological V (2-SAT)

解决一个关于在圣诞树上放置礼物的问题,通过构建2-SAT模型,并利用树的特性简化问题,最终找到一种合法的放置方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

小S打算送给小M一棵nn个点的圣诞树,点从1nn标号,他打算给树上挂上m个礼物,每个礼物在树上的某个点上,礼物可以重叠。
小M给了小S qq个限制,其中第i个形如“第aiai个礼物和第bibi个礼物在树上的路径经过了点cici”。
构造出符合小M条件的挂礼物方案。

题解:
这题一开始想到2SAT2−SAT,记fi,jfi,j表示第ii个点放第j个礼物的boolbool变量,不过发现这种方法限制不能完全体现出来,自然不能找到一种合法的方案(比如如果所有的fj,pfj,p都不选,那么fi,pfi,p必须选)。
然后因为是树上,有一个tricktrick ,记fi,jfi,j 为第ii个点及其子树放第j个点的boolbool变量, 具体我们用ai,jai,j表示jji的子树中,bi,jbi,j表示jj不在i的子树中。 这样不仅能够体现出所有限制而且能够很方便的输出方案(这时候根节点为truetrue,且具体方案为深度最大的那个合法点)。
我们先连边:

  • b1,ia1,ib1,i→a1,i
  • ai,jafai,jai,j→afai,j
  • bi,jbsoni,jbi,j→bsoni,j
  • ai,jbk,jai,j→bk,j (如果ii的子树与j的子树交集为空)
    对于每一个限制,连边:
  • ai,a/bbj,b/aai,a/b→bj,b/aii,j树上父亲为cc
  • bc,a/bac,b/a

然后跑2SAT2−SAT即可。 时间复杂度为O(n+m)=O(n2m+qn2)O(n+m)=O(n2m+qn2)

其实复杂度可以优化,每个点维护儿子的前缀后缀即可。 不过太麻烦就不写了。

#include <bits/stdc++.h>
using namespace std;
inline int rd() {
    char ch=getchar(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=getchar();}
    return i*f;
}
const int N=2e2+55,M=N*N*2;
int n,m,q,A[N][N],B[N][N],d[N],fa[N],tot,ins[M],sta[M],top,dfn[M],low[M],bl[M],scc,ind,pos[M];
vector <int> son[N];
vector <int> e[N];
vector <int> e2[M];
inline int lca(int x,int y) {while(x!=y) (d[x]>d[y])?(x=fa[x]):(y=fa[y]);}
inline bool anc(int x,int f) {return (x==f)?true:(x?anc(fa[x],f):false);}
inline void dfs(int x,int f) {
    fa[x]=f; d[x]=d[f]+1;
    for(auto v:e[x]) if(v!=f) son[x].push_back(v), dfs(v,x);
}
inline void dfs(int x) {
    sta[++top]=x; ins[x]=1; dfn[x]=low[x]=++ind;
    for(auto v:e2[x]) {
        if(!dfn[v]) dfs(v), low[x]=min(low[x],low[v]);
        else if(ins[v]) low[x]=min(low[x],dfn[v]);
    }
    if(low[x]==dfn[x]) {
        ++scc; int u;
        do {
            u=sta[top--];
            ins[u]=0; bl[u]=scc;
        } while(u!=x);
    }
}
int main() {
    n=rd(), m=rd(), q=rd();
    for(int i=1;i<n;i++) {
        int x=rd(), y=rd();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1,0); 
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++)
            A[i][j]=++tot, B[i][j]=++tot;
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++){
            if(fa[i]) e2[A[i][j]].push_back(A[fa[i]][j]);
            for(auto v:son[i]) e2[B[i][j]].push_back(B[v][j]);
        }
    for(int j=1;j<=m;j++) e2[B[1][j]].push_back(A[1][j]);
    for(int i=1;i<=n;i++)
        for(int k=1;k<=n;k++)
            if(i!=k && !anc(i,k) && !anc(k,i))
                for(int j=1;j<=m;j++) e2[A[i][j]].push_back(B[k][j]);
    for(int i=1;i<=q;i++) {
        int a=rd(), b=rd(), c=rd();
        for(auto v:son[c]) {
            e2[A[v][a]].push_back(B[v][b]);
            e2[A[v][b]].push_back(B[v][a]);
        }
        if(fa[c]) e2[B[c][a]].push_back(A[c][b]), e2[B[c][b]].push_back(A[c][a]);
    }
    for(int i=1;i<=tot;i++) if(!dfn[i]) dfs(i);
    for(int j=1;j<=m;j++)
        for(int i=1;i<=n;i++) 
            if(bl[A[i][j]]<bl[B[i][j]]) 
                pos[j]=(d[pos[j]]<d[i])?i:pos[j];
    for(int i=1;i<=m;i++) printf("%d ",pos[i]);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值