bzoj2064[和谐社会模拟赛]分裂

题意:给定一个初始集合和目标集合,有两种操作:1.合并集合中的两个元素,新元素为两个元素之和 2.分裂集合中的一个元素,得到的两个新元素之和等于原先的元素。要求用最小步数使初始集合变为目标集合,求最小步数。

记集合S的元素之和为sum(S)

首先,如果初始集合的子集S1和目标集合的子集S2满足sum(S1)==sum(S2),那么我们就可以把S1合并为一个元素再拆分为S2,然后再处理两个集合剩下的部分。

我们得到了一个很显然的状态转移方程。记f[S1][S2]为使用初始集合的S1子集得到目标集合的S2子集的最小花费(如果状态合法,必须满足sum(S1)==sum(S2),但并不一定是将S1全部合并为一个元素再拆成S2,因为S1的某个子集还可以与S2的某个子集的和相等,从而用S1的几个子集分别得到S2的几个子集)。转移的时候,如果直接暴力枚举S1和S2的每一个子集判断能否匹配,复杂度会达到(2^n)^4,30分。

这时有一个显然的优化,就是只考虑合法的状态,对于非法的状态(S1的和不等于S2的状态)不进行求解。我的做法是预处理出所有合法状态,然后仅在合法状态之间进行转移,复杂度为O(m^2),m为合法状态数。如果出题人愿意造数据的话,这个做法是可以卡掉的,比如初始状态和末状态都是10个1,那么总的合法状态数为C(10,1)^2+C(10,2)^2+C(10,3)^2+….+C(10,10)^2,达到了184755,肯定会T。但是我在bzoj上assert了一发,发现最多的合法状态数大于5000小于8000,那么平方的复杂度就过掉了。

不会被卡的做法是:我们转移的方式是将初始集合分成几个子集对应到目标集合的几个子集。假如我们将初始集合分成了x个子集,那么最后的花费就是初始集合元素个数+目标集合元素个数-2*x(举几个例子就能发现这个规律)。由于元素个数已经确定,我们的最优化目标转化为将初始集合分成尽量多的子集与目标集合的同样数目的子集一一对应(元素之和相等)。

记f[S1][S2]为从初始集合的S1子集和目标集合的S2子集中选取元素,最多能组成几组元素和相等的集合。(不一定使用S1和S2中的所有元素)。

转移时,我们分两种情况讨论,一种是sum(S1)!=sum(S2),这时一定无法用上所有的元素,那么只要枚举没有使用的一个元素S1中的i或S2中的j,f[S1][S2]=max(f[S1^i][S2],f[S1][S2^j])

一种是sum(S1)==sum(S2),这时一定可以用上所有的元素,那么我们是否需要枚举S1,S2的所有子集?看似只能通过这个方法使得状态从前面转移过来,那么复杂度上界又变成了(2^n)^4,30分。不过,再仔细想想。如果我们在最优解中拿掉一个元素,那么能够配对的集合对数一定会减1.反过来,f[S1][S2]的最优解一定可以通过一个在S1或S2中拿掉一个元素的状态增加一个配对的集合得到。(由于sum(S1)==sum(S2),而且任何一个状态中已经配对的元素都满足两边总和相等,所以拿掉一个元素后的状态在增添一个元素后一定可以增加一组配对的集合。)这个用枚举元素代替枚举子集的思路很巧妙。

仍然枚举S1,S2中的元素i,j,那么f[S1][S2]= max(f[S1^i][S2],f[S1][S2^j])+1。

#include<cstdio>

#include<cstring>

#include<cassert>

const int inf=0x3f3f3f3f;

inline int lowbit(int x){

    return x&(-x);

}

inline int min(int a,int b){

    return a<b?a:b;

}

int a[15],b[15];

int suma[1024],sumb[1024];

int f[1024][1024];

int g[1024];

const int maxn=10005;

int u[maxn],v[maxn];

 

int main(){

    int n,m;

    scanf("%d",&n);

    for(int i=0;i<n;++i){

        scanf("%d",a+i);

        suma[1<<i]=a[i];

    }

    scanf("%d",&m);

    for(int i=0;i<m;++i){

        scanf("%d",b+i);

        sumb[1<<i]=b[i];

    }

    int lim1=1<<n,lim2=1<<m;

    for(int i=1;i<lim1;++i){

        suma[i]=suma[i^lowbit(i)]+suma[lowbit(i)];

    }

    for(int i=1;i<lim2;++i){

        sumb[i]=sumb[i^lowbit(i)]+sumb[lowbit(i)];

    }

    for(int i=1;i<1024;++i)g[i]=g[i>>1]+(i&1);

    int cnt=0;

    memset(f,0x3f,sizeof(f));

    for(int i=1;i<lim1;++i){

        for(int j=1;j<lim2;++j){

            if(suma[i]==sumb[j]){

                u[++cnt]=i;

                v[cnt]=j;

                f[i][j]=g[i]+g[j]-2;

            }

        }

    }

    for(int i=1;i<=cnt;++i){

        for(int j=i+1;j<=cnt;++j){

            if((u[i]&u[j])||(v[i]&v[j])){

                continue;

            }

            f[u[i]|u[j]][v[i]|v[j]]=min(f[u[i]|u[j]][v[i]|v[j]],f[u[i]][v[i]]+f[u[j]][v[j]]);

        }

    }

    assert(cnt<=8000);

    printf("%d\n",f[lim1-1][lim2-1]);

    return 0;

}

 

#include<cstdio>

#include<cstring>

const int inf=0x3f3f3f3f;

inline int lowbit(int x){

    return x&(-x);

}

inline int max(int a,int b){

    return a>b?a:b;

}

int a[15],b[15];

int suma[1024],sumb[1024];

bool e[1024][1024];

int f[1024][1024];

int g[1024];

int main(){

    int n,m;

    scanf("%d",&n);

    for(int i=0;i<n;++i){

        scanf("%d",a+i);

        suma[1<<i]=a[i];

    }

    scanf("%d",&m);

    for(int i=0;i<m;++i){

        scanf("%d",b+i);

        sumb[1<<i]=b[i];

    }

    int lim1=1<<n,lim2=1<<m;

    for(int i=1;i<lim1;++i){

        suma[i]=suma[i^lowbit(i)]+suma[lowbit(i)];

    }

    for(int i=1;i<lim2;++i){

        sumb[i]=sumb[i^lowbit(i)]+sumb[lowbit(i)];

    }

    for(int i=1;i<lim1;++i){

        for(int j=1;j<lim2;++j){

            for(int k=0;k<n;++k){

                if(i&(1<<k))f[i][j]=max(f[i][j],f[i^(1<<k)][j]);   

            }

            for(int k=0;k<m;++k){

                if(j&(1<<k))f[i][j]=max(f[i][j],f[i][j^(1<<k)]);

            }

            if(suma[i]==sumb[j])f[i][j]++;

        }

    }

    printf("%d\n",n+m-2*f[lim1-1][lim2-1]);

    return 0;

}

 

 

转载于:https://www.cnblogs.com/liu-runda/p/6019426.html

BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值