洛谷mNOIP模拟赛Day2-星空
题目背景
pdf题面和大样例链接:http://pan.baidu.com/s/1cawM7c 密码:xgxv
命运偷走如果只留下结果, 时间偷走初衷只留下了苦衷。
你来过,然后你走后,只留下星空。
题目描述
逃不掉的那一天还是来了,小 F 看着夜空发呆。
天上空荡荡的,没有一颗星星——大概是因为天上吹不散的乌云吧。
心里吹不散的乌云,就让它在那里吧,反正也没有机会去改变什么了。
小 C 拿来了一长串星型小灯泡,假装是星星,递给小 F,想让小 F 开心一点。不过,有 着强迫症的小 F 发现,这串一共 n 个灯泡的灯泡串上有 k 个灯泡没有被点亮。小 F 决定 和小 C 一起把这个灯泡串全部点亮。
不过,也许是因为过于笨拙,小 F 只能将其中连续一段的灯泡状态给翻转——点亮暗灯 泡,熄灭亮灯泡。经过摸索,小 F 发现他一共能够翻转 m 种长度的灯泡段中灯泡的状态。
小 C 和小 F 最终花了很长很长很长很长很长很长的时间把所有灯泡给全部点亮了。他 们想知道他们是不是蠢了,因此他们找到了你,让你帮忙算算:在最优的情况下,至少需要 几次操作才能把整个灯泡串给点亮?
输入输出格式
输入格式:
从标准输入中读入数据。
输入第 1 行三个正整数 n,k,m。
输入第 2 行 kk 个正整数,第 i 个数表示第 i 个被没点亮的灯泡的位置 a_iai。
输入第 3 行 mm 个正整数,第 i 个数表示第 i 种操作的长度 b_ibi。
保证所有 b_ibi 互不相同;保证对于 1 \le i < k1≤i<k,有 a_i< a_i+1ai<ai+1;保证输入数据有解。
输出格式:
输出标准输入中。
输出一行一个非负整数,表示最少操作次数。
输入输出样例
说明
【样例 1 解释】
【数据范围与约定】
子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解 决一部分测试数据。
每个测试点的数据规模及特点如下表
特殊性质:保证答案小于 4
不妨就暴力一点吧。。。
1 #include<cstdio> 2 #include<cstdlib> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 #include<map> 7 #include<set> 8 #include<queue> 9 #include<vector> 10 #define INF 0x7f7f7f7f 11 #define pii pair<int,int> 12 #define ll long long 13 #define MOD 19260817 14 using namespace std; 15 int read(){ 16 int x=0,f=1;char ch=getchar(); 17 while(ch<'0'||ch>'9'){if('-'==ch)f=-1;ch=getchar();} 18 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 19 return x*f; 20 } 21 struct Node{ 22 int cost; 23 int sum; 24 int a[205]; 25 Node(int p1=0,int p2=0,int b[205]={0}){ 26 cost=p1; 27 sum=p2; 28 memcpy(a,b,sizeof(a)); 29 } 30 }; 31 ll hash(Node t){ 32 ll ret=1; 33 for(int i=1;i<=200;i++){ 34 ret=((ret<<1)+t.a[i])%MOD; 35 } 36 return ret; 37 } 38 set<ll> s; 39 int n,k,m; 40 int L[65]; 41 queue<Node> q; 42 void init(){ 43 n=read(),k=read(),m=read(); 44 int t[205]={0}; 45 for(int i=1;i<=k;i++){ 46 t[read()]=1; 47 } 48 for(int i=1;i<=m;i++){ 49 L[i]=read(); 50 } 51 q.push(Node(0,k,t)); 52 s.insert(hash(Node(0,k,t))); 53 } 54 void solve(){ 55 while(!q.empty()){ 56 int a[205]={0}; 57 int cost=q.front().cost; 58 int sum=q.front().sum; 59 memcpy(a,q.front().a,sizeof(a)); 60 q.pop(); 61 for(int i=1;i<=m;i++){ 62 for(int j=1;j<=n-L[i]+1;j++){ 63 int b[205]={0},dsum=sum; 64 memcpy(b,a,sizeof(b)); 65 for(int l=j;l<=j+L[i]-1;l++){ 66 if(!b[l]){ 67 dsum++; 68 } 69 else{ 70 dsum--; 71 } 72 b[l]=(!b[l]); 73 } 74 if(!dsum){ 75 printf("%d\n",cost+1); 76 return; 77 } 78 Node t=Node(cost+1,dsum,b); 79 ll h=hash(t); 80 if(s.count(h)){ 81 continue; 82 } 83 q.push(t); 84 s.insert(h); 85 } 86 } 87 } 88 } 89 int main() 90 { 91 // freopen("starlit2.in","r",stdin); 92 init(); 93 solve(); 94 return 0; 95 }
考虑正解:
首先我们知道异或满足差分的性质:可逆性
从本质上来说,异或就是取反的过程,所以正过来可以,反过去也是可以了
这样就把一段区间取反转化为端点取反了。
问题转化为:
给定一个长度为n的0-1序列,最多包含2k个1,每次可以把间隔一定长度的两个位置取反,求最少多少次把序列全部变成0
然后我们发现,肯定是要对1操作的,不可能平白无故地把两个0取反(尽管最后的结果与操作顺序无关,但这里考虑下操作顺序)
如果是1和0的话,就可以看成1转移到了0的位置并花费了一次操作的代价
如果是1和1的话,就可以看成1转移到了另一个1的位置,然后两个1都消去了并花费了一次操作的代价
这样问题转化为:
给定一个长度为n的0-1序列,最多包含2k个1,每次可以把1转移到相距一定长度的位置,如果两个1在同一位置就会消去,求最少多少次把序列全部变成0
进一步转化为:
给定一个n个节点的图,其中最多2k的节点有物品,每次可以把一件物品转移到相距一定长度的位置,如果一个节点出现两个物品就会消去,求最少多少次把物品全部消除
我们可以用2k次bfs处理出每个物品与另外所有物品消去的代价,
这样问题转化为:
给定2k个物品,其中每2个物品消去都会消耗一定的代价,求把所有物品消去的最小代价
这样就是状压dp了,可以用SPFA来解决
1 #include<cstdio> 2 #include<cstdlib> 3 #include<algorithm> 4 #include<cstring> 5 #include<vector> 6 #include<queue> 7 #define MAXK 9 8 #define MAXN 40005 9 #define pii pair<int,int> 10 using namespace std; 11 int read(){ 12 int x=0;char ch=getchar(); 13 while(ch<'0'||ch>'9'){ch=getchar();} 14 while(ch>='0'&&ch<='9'){x=x*10+(ch^48);ch=getchar();} 15 return x; 16 } 17 int n,k,m; 18 int a[MAXN],b[MAXN],ed[MAXN]; 19 int Cost[MAXK<<1][MAXK<<1]; 20 int cnt; 21 int len[65]; 22 vector<int> v; 23 void init(){ 24 n=read();k=read();m=read(); 25 for(int i=1;i<=k;i++){ 26 a[read()]=1; 27 } 28 n++; 29 for(int i=1;i<=n;i++){ 30 b[i]=a[i]^a[i-1]; 31 } 32 for(int i=1;i<=n;i++){ 33 if(b[i]){ 34 b[i]=(++cnt); 35 v.push_back(i); 36 } 37 } 38 for(int i=1;i<=m;i++){ 39 len[i]=read(); 40 } 41 } 42 queue<pii> q; 43 void bfs(){ 44 memset(Cost,-1,sizeof(Cost)); 45 for(int z=0;z<v.size();z++){ 46 int x=v[z]; 47 memset(ed,-1,sizeof(ed)); 48 while(!q.empty()) q.pop(); 49 q.push(make_pair(x,0)); 50 ed[x]=0; 51 while(!q.empty()){ 52 int t=q.front().first; 53 int c=q.front().second; 54 q.pop(); 55 for(int i=1;i<=m;i++){ 56 for(int j=-1;j<=1;j+=2){ 57 int dt=t+j*len[i]; 58 if(1<=dt&&dt<=n&&-1==ed[dt]){ 59 ed[dt]=c+1; 60 if(b[dt]){ 61 Cost[b[x]][b[dt]]=ed[dt]; 62 } 63 q.push(make_pair(dt,ed[dt])); 64 } 65 } 66 } 67 } 68 } 69 } 70 queue<int> Q; 71 int dp[1<<(MAXK*2)]; 72 int used[1<<(MAXK*2)]; 73 void solve(){ 74 memset(dp,0x7f,sizeof(dp)); 75 dp[(1<<cnt)-1]=0; 76 Q.push((1<<cnt)-1); 77 used[(1<<cnt)-1]=1; 78 while(!Q.empty()){ 79 int t=Q.front(); 80 Q.pop(); 81 used[t]=0; 82 for(int i=1;i<cnt;i++){ 83 if((t>>(i-1))&1){ 84 for(int j=i+1;j<=cnt;j++){ 85 if((t>>(j-1))&1){ 86 int dt=t; 87 dt^=(1<<(i-1)); 88 dt^=(1<<(j-1)); 89 if(Cost[i][j]!=-1&&dp[dt]>dp[t]+Cost[i][j]){ 90 dp[dt]=dp[t]+Cost[i][j]; 91 if(!used[dt]){ 92 Q.push(dt); 93 used[dt]=1; 94 } 95 } 96 } 97 } 98 } 99 } 100 } 101 printf("%d\n",dp[0]); 102 } 103 int main() 104 { 105 // freopen("a.in","r",stdin); 106 // freopen("a.out","w",stdout); 107 init(); 108 bfs(); 109 solve(); 110 return 0; 111 }
以上为正解思路,这也揭示了题目的本质
总结:很好的一道题,考察较高的思维能力