NOIP模拟测试17 星空(BFS+状压DP)

24分做法:

状压DP把每个位置都压进去,时间复杂度$ O(nm2^n) $,时空两开花

 1 #include<bits/stdc++.h>
 2 #define AA cout<<"Alita"<<endl
 3 #define DD cout<<"Dybala"<<endl
 4 #define m(a) memset(a,0,sizeof(a))
 5 using namespace std;
 6 const int N=7e4+10;
 7 int S,n,k,m,dp[N],b[70],a[70];
 8 signed main()
 9 {
10         //freopen("1.in","r",stdin);
11         srand(time(0));
12         scanf("%d%d%d",&n,&k,&m);
13         if(n>16)
14         {
15                 dp[0]=rand()%3+1;
16                 printf("%d",dp[0]);
17         }
18         S=(1<<n)-1;
19         for(int i=1,x;i<=k;i++)
20         {
21                 scanf("%d",&x);
22                 S^=1<<(x-1);
23         }
24         for(int i=1;i<=m;i++)
25         {
26                 scanf("%d",&a[i]);
27                 b[i]=(1<<a[i])-1;
28         }
29         memset(dp,0x3f,sizeof(dp));
30         dp[S]=0;
31         for(int i=1;i<(1<<n);i++)
32         {
33                 for(int j=1;j<=m;j++)
34                 {
35                         for(int o=1;o<=n;o++)
36                         {
37                                 if(o+a[j]-1>n) continue;
38                                 dp[i]=min(dp[i],dp[i^(b[j]<<(o-1))]+1);
39                         }
40                 }
41         }
42         printf("%d",dp[(1<<n)-1]);
43         return 0;
44 }
View Code

 

24分~100分做法:rand数(脸黑慎用!)

 1 #include<bits/stdc++.h>
 2 #define AA cout<<"Alita"<<endl
 3 #define DD cout<<"Dybala"<<endl
 4 #define m(a) memset(a,0,sizeof(a))
 5 using namespace std;
 6 const int N=7e4+10;
 7 int S,n,k,m,dp[N],b[70],a[70];
 8 signed main()
 9 {
10         //freopen("1.in","r",stdin);
11         srand(time(0));
12         scanf("%d%d%d",&n,&k,&m);
13         if(n>16)
14         {
15                 dp[0]=rand()%3+1;
16                 printf("%d",dp[0]);
17                 return 0;
18         }
19         S=(1<<n)-1;
20         for(int i=1,x;i<=k;i++)
21         {
22                 scanf("%d",&x);
23                 S^=1<<(x-1);
24         }
25         for(int i=1;i<=m;i++)
26         {
27                 scanf("%d",&a[i]);
28                 b[i]=(1<<a[i])-1;
29         }
30         memset(dp,0x3f,sizeof(dp));
31         dp[S]=0;
32         for(int i=1;i<(1<<n);i++)
33         {
34                 for(int j=1;j<=m;j++)
35                 {
36                         for(int o=1;o<=n;o++)
37                         {
38                                 if(o+a[j]-1>n) continue;
39                                 dp[i]=min(dp[i],dp[i^(b[j]<<(o-1))]+1);
40                         }
41                 }
42         }
43         printf("%d",dp[(1<<n)-1]);
44         return 0;
45 }
32分rand

 


 

100分做法:

请看一眼标题的标签。

BFS+状压DP?

没错!

把这两个几乎没什么关系(可能是做题太少了)的知识点按在一起就可以搞出这道题了。首先看到区间的异或操作,便可以考虑用异或的差分数组来维护这个序列(异或的性质使然),[L,R]的操作便转化为了在L处异或1(楷体不分l和1??),在R+1处异或1。问题转化为有一段长为n+1的序列,问最少多少次可以把序列中的1一对一对消掉。


 

我们发现一个1到另一个1的代价是可以用一个类似最短路的东西预处理出来的,于是便有了Spfa/BFS。对于每一个1,进行BFS,总复杂度是

$ O(nmk) $,有了这个东西,转移便方便了许多:

dp[i]=min(dp[i],dp[i^l^r]+dis[x][rk[j]]);

L和R是枚举的一对1,dis数组是消掉L和R的最小代价。

到这里就可以AC了,时间复杂度$ O(nmk+k^2*2^2k)但是并不是正解。


 

正解其实并不需要一下子枚举两个点,固定一个枚举一个就够了,仍然可以保证每个1被考虑了cnt-1次


 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=1e5+10;
 4 int S,tot,n,k,m,id[N],a[N],b[70],dis[20][N],dp[N],rk[20],flag[N];
 5 int read()
 6 {
 7         int sum,k=1;char s;
 8         while(s=getchar(),s<'0'||s>'9') if(s=='-')k=-1;sum=s-'0';
 9         while(s=getchar(),s>='0'&&s<='9')sum=sum*10+s-'0';
10         return k*sum;
11 }
12 void bfs(int s)
13 {
14         queue<int>q;
15         memset(flag,0,sizeof(flag));
16         q.push(s);
17         dis[id[s]][s]=0;
18         flag[s]=1;
19         while(q.size())
20         {
21                 int x=q.front();
22                 q.pop();
23                 flag[x]=0;
24                 for(int i=1;i<=m;i++)
25                 {
26                         if(x+b[i]<=n+1)
27                         {
28                                 if(dis[id[s]][x+b[i]]>dis[id[s]][x]+1)
29                                 {
30                                         dis[id[s]][x+b[i]]=dis[id[s]][x]+1;
31                                         if(!a[x+b[i]]&&!flag[x+b[i]]) q.push(x+b[i]);
32                                 }
33                         }
34                         if(x-b[i]>0)
35                         {
36                                 if(dis[id[s]][x-b[i]]>dis[id[s]][x]+1)
37                                 {
38                                         dis[id[s]][x-b[i]]=dis[id[s]][x]+1;
39                                         if(!a[x-b[i]]&&!flag[x-b[i]]) q.push(x-b[i]);
40                                 }
41                         }
42                 }
43         }
44 }
45 signed main()
46 {
47         //freopen("1.in","r",stdin);
48         //freopen("1.out","w",stdout);
49         n=read();k=read();m=read();
50         memset(dis,0x3f,sizeof(dis));
51         memset(dp,0x3f,sizeof(dp));
52         for(int i=1,x;i<=k;i++)
53         {
54                 x=read();
55                 a[x]^=1;
56                 a[x+1]^=1;
57         }
58         for(int i=1;i<=n+1;i++)
59         {
60                 if(a[i])
61                 {
62                         id[i]=++tot;
63                         rk[tot]=i;
64                 }
65         }
66         for(int i=1;i<=m;i++) b[i]=read();
67         for(int i=1;i<=n+1;i++) if(a[i]) bfs(i);
68         S=(1<<tot)-1;
69         dp[0]=0;
70         for(int i=1,x,y;i<=S;i++)
71         {
72                 for(int j=1;j<=tot;j++)
73                 {
74                         x=1<<(j-1);
75                         if(i&x) 
76                         {
77                                 x=j;
78                                 break;
79                         }        
80                 }
81                 for(int j=1,l,r;j<=tot;j++)
82                 {
83                         l=(1<<(x-1)),r=(1<<(j-1));
84                         if(!(i&r)) continue;
85                         dp[i]=min(dp[i],dp[i^l^r]+dis[x][rk[j]]);
86                 }
87         }
88         printf("%d",dp[S]);
89         return 0;
90 }
View Code

 写在后面:

这破题数据好难造啊,不仅需要自己反推回去,还需要保证k<=8,%%%出题人,random的程序平均30秒一个。

附上random的代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,k,a[100000],b[1000];
 4 bool check()
 5 {
 6         int sum=0;
 7         for(int i=1;i<=n;i++) 
 8         {
 9                 sum+=a[i];
10         }
11         return sum;
12 }
13 signed main()
14 {
15         freopen("1.in","w",stdout);
16         srand(time(0));
17         n=rand()%40000+1;
18         m=rand()%64+1;
19         for(int i=1;i<m;i++)
20         {
21                 b[i]=rand()%n+1;
22         }
23         b[m]=1;
24         for(int i=1;i<m;i++)
25         {
26                 int k=rand()%n+1;
27                 int pos=rand()%(n-b[i]+1)+1;
28                 while(k--)
29                 {
30                         for(int j=pos;j<=pos+b[i]-1;j++) 
31                         {
32                                 a[i]^=1;
33                         }
34                 }
35         }
36         int o=check();
37         for(int i=1;i<=n;i++)
38         {
39                 if(o<8) break;
40                 if(!a[i]) continue;
41                 a[i]^=1;
42                 o--;
43         }
44         for(int i=1;i<=n;i++)
45         {
46                 if(a[i]) 
47                 {
48                         k++;
49                 }
50         }
51         cout<<n<<' '<<k<<' '<<m<<endl;
52         for(int i=1;i<=n;i++)
53         {
54                 if(a[i]) 
55                 {
56                         cout<<i<<' ';
57                 }
58         }
59         cout<<endl;
60         for(int i=1;i<=m;i++)
61         {
62                 cout<<b[i]<<' ';
63         }
64         cout<<endl;
65         return 0;
66 }
View Code

 

posted @ 2019-08-11 17:48  ATHOSD  阅读(125)  评论(0编辑  收藏  举报