UVALive-2965题目链接:https://vjudge.net/problem/UVALive-2965
题意:给n个大写字母构成的字符串,要求选取若干个字符串,满足每个字母出现偶数次(即选取的字符串集合中A-Z总数出现偶数次)
思路:n有24,直接暴力选(每个串选或者不选暴力,见下题有类似实现)是TLE。
所以采用将n个字符串一分为2,再逐个暴力,复杂度就变得可接受了。
由于本题只考虑A-Z出现的奇偶,所以先将每个字符串映射为数字(26位,A在最低位,Z在最高为),方便我们异或操作。
那么问题转化成 在我们划分的2个字符串集合中,选出两个子集(两个数),这两个数异或结果为0(每个字母出现偶数次,偶数次异或即为0)
因此,我们枚举并存储在第一个集合选取的结果,枚举第二个集合时,只需判断结果是否出现过(第一次是否枚举到),若出现,两数异或结果为0。同时,我们取选取字符串多的即可。
Code:
#include <bits/stdc++.h>
using namespace std;
const int AX = 1e3 + 66 ;
char s[AX];
int a[30] ;
int cal(int x){ //计算x二进制形式中1的个数
return !x ? 0 : cal( x >> 1 ) + ( x & 1 ) ;
}
int main(){
int n ;
while( ~scanf("%d",&n) && n ){
map<int,int> mp ;
memset( a , 0 , sizeof(a) );
for( int i = 0 ; i < n ; i++ ){
scanf("%s",&s);
int len = strlen(s) ;
for( int j = 0 ; j < len ; j++ ){
a[i] |= ( 1 << (int)( s[j] - 'A' ) ) ;
}
}
int len1 = n / 2 ;
int len2 = n - len1 ;
for( int i = 0 ; i < ( 1 << len1 ) ; i++ ){ //枚举第一个集合中选取字符串的所有情况
int tmp = 0 ;
for( int j = 0 ; j < len1 ; j++ ){
if( ( 1 << j ) & i ){
tmp ^= a[j] ;
}
}
if( !mp.count(tmp) || cal(i) > cal(mp[tmp]) ){ //1的个数即为选取的字符串数
mp[tmp] = i ;
}
}
int res = 0 ;
for( int i = 0 ; i < ( 1 << len2 ) ; i++ ){
int tmp = 0 ;
for( int j = 0 ; j < len2 ; j ++ ){
if( ( 1 << j ) & i ){
tmp ^= a[j+len1] ;
}
}
if( mp.count(tmp) && cal(i) + cal(mp[tmp]) > cal(res) ){ //tmp在第一次出现过,相同数异或得到0
res = ( i << len1 ) ^ mp[tmp] ; //将两个集合合并到res中
}
}
printf("%d\n",cal(res));
for(int i = 0; i < n; i++){
if( res & ( 1 << i ) ) printf("%d ",i+1);
}
printf("\n");
}
return 0 ;
}
网易19校招–牛牛的背包问题
题目描述
牛牛准备参加学校组织的春游, 出发前牛牛准备往背包里装入一些零食, 牛牛的背包容量为w。
牛牛家里一共有n袋零食, 第i袋零食体积为v[i]。
牛牛想知道在总体积不超过背包容量的情况下,他一共有多少种零食放法(总体积为0也算一种放法)。
输入描述:
输入包括两行
第一行为两个正整数n和w(1 <= n <= 30, 1 <= w <= 2 * 10^9),表示零食的数量和背包的容量。
第二行n个正整数v[i](0 <= v[i] <= 10^9),表示每袋零食的体积。
输出描述:
输出一个正整数, 表示牛牛一共有多少种零食放法。
示例1
输入
复制
3 10
1 2 4
输出
复制
8
说明
三种零食总体积小于10,于是每种零食有放入和不放入两种情况,一共有222 = 8种情况。
这题我开始想是背包,看到w是2e9,肯定就不行了,n是30,就想到了dfs爆搜,但是一直不敢动手,n是30感觉也是T,最后实在没辙,就试了下没想到过了,而且是6ms,看了评论区讲是中途相遇法,就又写了一种解法。(见下
dfs Code:
#include <bits/stdc++.h>
using namespace std;
const int AX = 30 + 6;
long long v[AX] ;
int n ;
long long w ;
int res = 0 ;
map<int,int>mp ;
void dfs( int x , long long sum , int sta ){
if( x == n ){
if( !mp[sta] ) { res ++ ; mp[sta] = 1 ; }
return ;
}
dfs( x + 1 , sum , sta ) ;
if( sum + v[x] <= w ){
dfs( x + 1 , sum + v[x] , sta |= ( 1 << x ) ) ;
}
}
int main(){
long long ans = 0 ;
cin >> n >> w ;
for( int i = 0 ; i < n ; i++ ){
cin >> v[i] ;
ans += v[i] ;
}
if( ans <= w ) res = 1 << n ;
else dfs( 0 , 0 , 0 ) ;
cout << res << endl ;
return 0 ;
}
中途相遇法:
思路:将n袋食物分成两半,然后枚举第一份所有集合,存储和<=w的,存储的和升序排序
再枚举第二份所有子集(假设子集和为tmp),二分第一次存储的满足的集合,找出第一个大于等于w-tmp的位置pos,res += pos + 1 即可。
这样这边的复杂度就从2^30 到 2^15 + 2 ^15了
#include <bits/stdc++.h>
using namespace std;
const int AX = 30 + 6;
long long v[AX] ;
int n , w ;
struct Node{
long long sum ;
int rec ;
Node(){}
Node( int sum , int rec ):sum(sum),rec(rec){}
bool operator < ( const Node &b )const{
return sum < b.sum ;
}
};
int cal(int x){
return !x ? 0 : cal( x >> 1 ) + ( x & 1 ) ;
}
vector<Node>vec ;
int Binary_search( int x ){
int l = 0 ;
int r = vec.size() - 1 ;
while( l <= r ){
int mid = ( l + r ) >> 1 ;
if( vec[mid].sum >= x ) r = mid - 1;
else l = mid + 1 ;
}
return l ;
}
int main(){
cin >> n >> w ;
for( int i = 0 ; i < n ; i++ ){
cin >> v[i] ;
}
int len1 = n / 2 ;
int len2 = n - len1 ;
long long res = 0 ;
for( int i = 0 ; i < ( 1 << len1 ) ; i++ ){
long long tmp = 0 ;
for( int j = 0 ; j < len1 ; j++ ){
if( i & ( 1 << j ) ){
tmp += v[j] ;
}
}
if( tmp <= w ) { vec.push_back(Node(tmp,i)) ; }
}
sort( vec.begin() , vec.end() );
for( int i = 0 ; i < ( 1 << len2 ) ; i++ ){
long long tmp = 0 ;
for( int j = 0 ; j < len2 ; j++ ){
if( i & ( 1 << j ) ){
tmp += v[j+len1] ;
}
}
if( tmp <= w ){
int pos = Binary_search( w - tmp ) ;
if( pos >= 0 && pos < vec.size() && vec[pos].sum + tmp <= w ){
res += pos + 1 ;
}else if( pos - 1 >= 0 && pos - 1 < vec.size() && vec[pos-1].sum + tmp <= w ){
pos -- ;
res += pos + 1 ;
}
}
}
cout << res << endl ;
return 0 ;
}