LA2965字符串合并

LA2965

http://122.207.68.93:9090/csuacmtrain/problem/viewProblem.action?id=3148§

【题目描述】:给定n个大写字母组成的连续字符串,(n<=24),选取其中的m个组合,使相同的大写字母的出现次数之和为偶数。目标是让这个m最大。

【算法分析】:

Ps:这道题看着比较简单,但是编码有难度。

【中途相遇法】:

首先,如果将每种状态用位表示,就是2^24种状态,1表示奇数个,0表示偶数个。利用异或运算的特殊性,模二加,奇数与奇数异或是偶数。所以我们的目的是找出m个数异或,使他们的结果为0。到这里为止,已经保证了编码的简便性。

但是,复杂度任然是难以支撑。朴素的算法为2^24(==16000000)种情况,超时。书中提供“中途相遇法”,将考虑的情况分为两半,一边是2^12(==4096)种,因为两个数异或,只有完全相等,才能为0。所以生成前半部分的“选取个数”(我们只关注这个)和“异或结果”的匹配的集合map。后半部分,每次生成一个新的匹配的时候,查找用nlog2(n)的复杂度即可。精髓在于“打表+查找”?

【综合复杂度】:2* 2^24+24*log2(24)约等于10000

【完整代码】:

  1 #include<iostream>
  2 
  3 #include<stdio.h>
  4 
  5 #include<string.h>
  6 
  7 #include<algorithm>
  8 
  9 #include<stdlib.h>
 10 
 11 #include<math.h>
 12 
 13 #include<queue>
 14 
 15 #include<vector>
 16 
 17 #include<set>
 18 
 19 #include<map>
 20 
 21 #define MAXN 100+5
 22 
 23 #define MAXM 100+5
 24 
 25 #define oo 1e9
 26 
 27 #define eps 0.001
 28 
 29 #define PI acos(-1.0)//这个精确度高一些
 30 
 31 #define REP1(i,n) for(int i=0;i<=(n);i++)
 32 
 33 #define REP2(i,n) for(int i=1;i<=(n);i++)
 34 
 35 #define DREP2(i,n) for(int i=(n);i>=1;i--)
 36 
 37 #define LL long long
 38 
 39 using namespace std;
 40 
 41  
 42 
 43 int nums[MAXN];//表示统计的异或的结果
 44 
 45 int n;
 46 
 47 int p1,p2;
 48 
 49 map<int,int> Map;
 50 
 51 //int getbits(int x)
 52 
 53 //{
 54 
 55 //    int ans=0;
 56 
 57 //    while(x>0)
 58 
 59 //    {
 60 
 61 //        ans+=(1&x);
 62 
 63 //        x>>1;
 64 
 65 //    }
 66 
 67 //    return ans;
 68 
 69 //}
 70 
 71  
 72 
 73 int getbits(int x)//这个统计位数的函数值得学习
 74 
 75 {
 76 
 77     return x==0?0:getbits(x/2)+(x&1);
 78 
 79 }
 80 
 81 void readin()
 82 
 83 {
 84 
 85     memset(nums,0,sizeof(nums));
 86 
 87     char s[10005];
 88 
 89     for(int i=0;i<n;i++)
 90 
 91     {
 92 
 93         cin>>s;
 94 
 95         int len=strlen(s);
 96 
 97         for(int j=0;j<len;j++)
 98 
 99         nums[i]=nums[i]^(1<<(s[j]-'A'));//存储的是z到a的结果
100 
101     }
102 
103     return ;
104 
105 }
106 
107  
108 
109 void makeMap()
110 
111 {
112 
113     Map.clear();
114 
115     int p=n/2;
116 
117     for(int i=0;i<(1<<p);i++)//状态压缩,枚举每种组合情况
118 
119     {
120 
121 //        cout<<"i="<<i<<endl;
122 
123         int x=0;//存储每种组合异或的结果,注意初始化为0,0和任何数异或等于原数
124 
125         for(int j=0;j<p;j++) {if (i&(1<<j)) x=x^nums[j];}//读取每位上的数字,注意nums从1开始存储
126 
127         if (!Map.count(x))Map[x]=i;//查找关键字//注意i中既包含位数又包含选择信息
128 
129         else if (getbits(Map[x])<getbits(i)) Map[x]=i;
130 
131     }
132 
133     return;
134 
135 }
136 
137 void getans()
138 
139 {
140 
141     int ans=0;
142 
143     int p=n-n/2;//枚举后p位,状态压缩比搜索好写啊
144 
145  
146 
147     for(int i=0;i<(1<<p);i++)
148 
149     {
150 
151         int x=0;
152 
153         for(int j=0;j<p;j++)
154 
155             if (i&(1<<j)) x=x^nums[j+n/2];//同上
156 
157         if (Map.count(x))//注意:我们的目标是异或结果为0,即两个x相等
158 
159         {
160 
161             int k1=getbits(i),k2=getbits(Map[x]),m=getbits(ans);
162 
163             if (m<k1+k2)
164 
165             {
166 
167 //                ans=(Map[x]<<(n/2))^i;//注意好移动步数
168 
169                   ans=(i<<(n/2))^Map[x];
170 
171             }
172 
173  
174 
175         }
176 
177     }
178 
179     cout<<getbits(ans)<<endl;
180 
181     if (ans>0)
182 
183     {
184 
185         int cnt=0,A[105];
186 
187         for(int i=0;i<n;i++) if (ans &(1<<i)) A[cnt++]=i+1;
188 
189         for(int i=0;i<cnt-1;i++) cout<<A[i]<<" ";
190 
191         cout<<A[cnt-1]<<endl;
192 
193     }
194 
195  
196 
197     cout<<endl;
198 
199     return;
200 
201 }
202 
203  int main(){
204 
205     while(cin>>n && n)
206 
207     {
208 
209     readin();
210 
211     makeMap();//构建前1--n/2组合异或的MAP,用状态压缩实现
212 
213     getans();
214 
215     }
216 
217     return 0;
218 
219 }

 

【关键词】:位运算,中途相遇法,建模

posted @ 2014-01-18 17:04  little_w  阅读(222)  评论(0编辑  收藏  举报