JZOJ4954. 【WC模拟】Equation

题目描述

这里写图片描述

这里写图片描述

分析

呐首先这个布尔函数的变量只能是0,1.反正比赛的时候我以为可以随便取,就不会做了。
我们定义(v1,1 or v,1,2)为一个项,x为变量。
先观察题目,发现要项里面最多只有两个东西。然后里面的东西相同的不会出现两次以上,这是什么意思呢?意味着一个变量至多出现在两个不同的项里面。
纯暴力是对变量枚举,由于上面的特殊性,又由一般的xor套路可得,我们应该可以转化成对异或的项dp。
考虑subtask2,我们直接顺序对每一项DP就行了,因为一个项里面的变量在别的项中不会出现,那么我们这时枚举情况是无后效性的嘛。
而subtask3,可以状压DP来解决。

剩下的分怎么拿?观察每个变量最多在两个项出现这一条件。可以得出:我们确定了一个变量,只会影响至多两个项。那么我们先枚举一个变量x1的情况,影响了两个项,若再枚举两个项的其中一个项的另一个变量xi,有一个项就确定了,这时候另一个包含xi的项就又被影响了。然而我们发现可以保持被影响而不确定的项保持在2个以内,而且确定的项只有xor起来的值是有用的,要维护的信息非常少,是不是可做了呢?
不断地划水后,终于学会了游泳,这个我们可以进行图的转化!把项看成点,定义两个点有一条边相连,为拥有一对相同相同的变量。这样的话转化后就变成一堆链和环了。我们只要对每个环和链顺着边DP就好了。这里讨论环。(链其实是一样的)
若一个环有n个点,设F[i][init][last][val]表示某条环做到第i个点,第一个点和第n个点的共同变量值为init,第i个点和i+1共同的变量值为last,前i个点xor值为val。就可以搞出一个环值是0和1的方案数了。
最后合并一下,输出1的方案就好了。
还要考虑自环和未出现的变量的影响。

总结:这道题最主要是发现各种特殊性,然后图的转化。当然理解题意也很重要····

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=100005;
const int mo=1000000007;
int n,i,j,k[N],a[N][3],m,l,x[N],tmp,tp,f[N][70][2],ref[N],b[N],met[N],cnt[N];
ll ans;
int orit(int val,int tar,int pos)
{
    if (tar) tar=1;
    if (pos>0) return val|tar;
    else return val|(!tar);
}
void dfs(int x,int y,int z)
{
    if (x>k[i+1])
    {
        f[i+1][z][l^y]+=f[i][j][l];
        if (f[i+1][z][l^y]>mo) f[i+1][z][l^y]-=mo;
        return;
    }
    int now=abs(a[i+1][x]);
    if (cnt[now]>1&&met[now])
        dfs(x+1,orit(y,(1<<(ref[now]-1))&j,a[i+1][x]),z);
    else
    {
        int add=0;
        if (cnt[now]>1) add=1<<(ref[now]-1);
        dfs(x+1,orit(y,1,a[i+1][x]),z+add);
        dfs(x+1,orit(y,0,a[i+1][x]),z);
    }
}
int main()
{
    freopen("equation.in","r",stdin);
//  freopen("equation.out","w",stdout);
    scanf("%d %d",&n,&m);
    fo(i,1,n)
    {
        scanf("%d",k+i);
        fo(j,1,k[i])
        {
            scanf("%d",a[i]+j);
            cnt[abs(a[i][j])]++;
        }
    }
    if (m<=21)
    {
        fo(i,0,(1<<m)-1)
        {
            fo(j,1,m) 
                if (i&(1<<(j-1))) x[j]=1;
                else x[j]=0;
            tmp=0;
            fo(j,1,n)
            {
                tp=0;
                fo(l,1,k[j])
                {
                    if (a[j][l]>0)
                        tp|=x[a[j][l]];
                    else tp|=!(x[-a[j][l]]);
                }
                tmp^=tp;
            }
            if (tmp)  ans++;
        }
    }
    else
    {
        fo(i,1,m) if (cnt[i]>1)
        {
            b[++b[0]]=i;
            ref[i]=b[0];
        }
        f[0][0][0]=1;
        fo(i,0,n-1)
        {
            fo(j,0,(1<<b[0])-1)
                fo(l,0,1)
                    if (f[i][j][l])
                    {
                        dfs(1,0,j);
                    }
            fo(j,1,k[i+1]) met[abs(a[i+1][j])]=1;
        }
        fo(j,0,(1<<b[0])-1) ans=(ans+f[n][j][1])%mo;
        fo(i,1,m) if (!met[i]) ans=ans*2%mo;
    }
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值