CodeChef-Little Party-爆搜+剪枝

题意:

戳这里

Solution:

我们可以把大小写分别看成0和1,这样我们就可以转化一下问题:构造一个最短的布尔函数,使得将m个01串作为变量代入这个布尔函数后,布尔函数的值都为1。这样就变成了一个加权覆盖子集的最小覆盖问题。

因为布尔函数是由一堆“或”连起来的“和”,所以我们的一个浅显的想法就是枚举子集,然后再枚举选那些子集,子集数量是 35=243 3 5 = 243 ,总复杂度 2243 2 243 ,显然我们已经爆炸了,所以说我们需要优化:

假设a,b,c都是布尔函数中的”和”,那么:

1.如果满足a条件的01串是满足b条件的01串的子集,那么除去a

2.如果a=xc,b=Xc,那么a,b都可以被c替换

这样的话我们的情况数就会减少很多,只剩下最后统计答案的复杂度

但是2^32还是会T,怎么办呢?

我们可以尝试剪枝:

我们用cover[i]表示当一个“和”的覆盖情况为i时,它可以满足哪些01串

我们用tot[i]表示[i,S-1]这段区间cover的或

然后我们进行dfs,每次先把当前状态和tot[i]取或,判断是否全部覆盖即可

具体细节请看代码。

代码:

#include<cstdio>
#include<iostream>
#include<cstring> 
using namespace std;
const int N=(1<<10);
bool can[N+10],vis[N+10];
char s[10];
int n,T,m,len[N+10],cover[N+10],tot[N+10],pos[N+10],lsh[N+10],cnt,ans,S;
void dfs(int x,int cov,int le)
{
    //cout<<x<<" "<<cov<<" "<<le<<" "<<tot[x]<<" "<<cnt<<" "<<S-1<<endl;
    if (le>=ans) return;
    if ((cov|tot[x])<(S-1)) return;
    if (cov==S-1) {ans=le;return;}
    if (x==cnt) return;
    dfs(x+1,cov,le);
    //cout<<x<<" "<<cover[lsh[x]]<<endl;
    if ((cov|cover[lsh[x]])>cov) dfs(x+1,cov|cover[lsh[x]],le+len[lsh[x]]);

}
int main()
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
    for (int i=0;i<N;i++)
    {
        can[i]=1;
        int x=i;
        for (int j=0;j<5;j++) 
        {
            if (x%4==3) can[i]=0;
            if (x%4) len[i]++;
            x>>=2;
        }
    }
    scanf("%d",&T);
    while (T--)
    {
        cnt=0;
        memset(cover,0,sizeof(cover));
        memset(tot,0,sizeof(tot));
        memset(pos,0,sizeof(pos));
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;i++)
        {
            scanf("%s",s+1);
            int x=0; 
            for (int j=1;j<=n;j++)
            {
                x<<=2;
                x+=1;
                if (s[j]>='a') x++;
            }
        //  cout<<x<<endl;
            vis[x]=1;           
        }
        S=1<<n+n;
        for (int i=0;i<S;i++) if (vis[i]) pos[i]=cnt++;
        if (cnt==0||cnt==(1<<n)) {printf("%d\n",0);continue;}
        for (int i=0;i<S;i++)
        {
            if (!can[i]) continue;
            bool gt=1;
            for (int j=0;j<S;j++)
            {
                if (!can[j]) continue;
                if ((i&j)==i&&len[j]==n)
                {if (vis[j]) cover[i]|=(1<<pos[j]);else gt=0;}
                if ((i&j)==j&&i!=j) {if (cover[j]) gt=0;}
            }
            if (!gt) cover[i]=0;
        }
        int SS=1<<cnt;
        cnt=0;
        for (int i=0;i<S;i++) if (cover[i]) lsh[cnt++]=i;
        for (int i=cnt-1;i>=0;i--) tot[i]=(tot[i+1]|cover[lsh[i]]); 
        S=SS;
        ans=1e9;
        dfs(0,0,0);
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值