IOIOI卡片占卜——令人窒息的异或妙用
IOIOI卡片占卜
K理事长很喜欢占卜,经常用各种各样的方式进行占卜。今天,他准备使用正面写着”I”,反面写着”O”的卡片为今年IOI的日本代表队占卜最终的成绩。
占卜的方法如下所示:
首先,选择5个正整数A,B,C,D,E。
将A+B+C+D+E张IOI卡片排成一行,最左侧的A张卡片正面朝上,接下来B张反面朝上,接下来C张卡片正面朝上,接下来D张反面朝上,最后E张正面朝上。如此排列的话,从左侧开始顺次为A张“I”,B张“O”,C张“I”,D张“O”,E张“I”。
在预先决定的N种操作中选出至少1种,然后按照任意顺序执行。(注:同种操作执行多次也是可以的。)这里,第i种操作(1<=i<=N)为【将从左数第Li张卡片到第Ri张卡片全部翻转】。翻转一张卡片需要1秒的时间,因此第i种操作耗时Ri-Li+1秒。
操作结束后,如果所有卡片都是正面朝上则占卜成功。K理事长不想翻多余的牌,因此在实际使用卡片占卜之前会先计算出是否存在占卜成功的可能性。进一步,如果占卜可能成功,他会计算出能使占卜成功所消耗的时间的最小值。
现在给出卡片的排列信息和预先决定的操作信息,请你写一个程序,计算出占卜能否成功,如果能成功,输出消耗时间的最小值。
第一行5个空格分隔的整数A,B,C,D,E,表示占卜初始时,从最左端开始依次是A枚正面朝上,接下来B枚背面朝上,接下来C枚正面朝上,接下来D枚背面朝上,最后E枚正面朝上。
接下来一行一个正整数N,表示预先决定的操作种类数。
接下来N行,第i行(1<=i<=N)两个空格分隔的正整数Li,Ri,表示第i种操作为【将从左数第Li张卡片到第Ri张卡片全部翻转】。
如果占卜能够成功,输出消耗时间的最小值,否则输出-1。
1 2 3 4 5
3
2 3
2 6
4 10
12
【HINT】
初始的卡片序列为IOOIIIOOOOIIIII。
执行第2种操作后得到IIIOOOOOOOIIIII,耗时5秒。
接下来执行第3中操作,得到IIIIIIIIIIIIIII,占卜成功,耗时7秒。
耗时12秒完成占卜,这是耗时的最小值,故输出12。
对于15%的数据,N<=10
对于另外50%的数据,1<=A,B,C,D,E<=50
对于100%的数据:
1<=A,B,C,D,E,N<=10^5
1<=Li<=Ri<=A+B+C+D+E (1<=i<=N)
分析:
这题已经是今天题目里最好理解的一道题了,就不简述题意了。我是想都没想到这题能用最短路写,这题目给了有限个连续1,0串,所以很显然题目中心不在这里,关键是如何处理区间翻转。题解给出的答案是将区间翻转转化为对应点的翻转,这和区间加法的处理有点像,将原串转化为相邻两位异或后的串,目的是将区间[l,r]的翻转转移为点l-1和r的翻转。这样以后翻转即为点与点之间的关系,再结合题目所给的代价,这就成了图中的最短路,之后就很简单了,因为原串是5个1,0序列,所以异或后只有4个1,问题就是将这4个1全转为0,很显然只要最短路3次,一一枚举2对点的最短路之和,取其最小值即为答案。不过可能你会有疑惑,假如1到2与3到4的路径有重复路段怎么办,这其实就相当于1,2,3,4全是联通的,这样的话他们之间总会有两对点的路径没有重复路段,所以对最后答案是没有影响的。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<vector> using namespace std; #define debug printf("zjyvegetable\n") #define int long long inline int read(){ int a=0,b=1;char c=getchar(); while(!isdigit(c)){if(c=='-')b=-1;c=getchar();} while(isdigit(c)){a=a*10+c-'0';c=getchar();} return a*b; } const int N=7e5+50,M=5e6+50,inf=12345678901234; int a,b,c,dd,e,n,tot,h[N],nx[M],ver[M],ed[M],d[M],vis[N],anss[6][6],ans=inf; priority_queue <pair<int,int> >q; void add(int u,int v,int z){ ver[++tot]=v;ed[tot]=z; nx[tot]=h[u];h[u]=tot; } void dij(int s){ memset(vis,0,sizeof(vis)); memset(d,0x3f,sizeof(d)); q.push(make_pair(0,s));d[s]=0; while(q.size()){ int x=q.top().second;q.pop(); if(vis[x])continue; vis[x]=1; for(int i=h[x];i;i=nx[i]){ int v=ver[i],z=ed[i]; if(d[v]>d[x]+z){ d[v]=d[x]+z; q.push(make_pair(-d[v],v)); } } } } signed main(){ freopen("card.in","r",stdin); freopen("card.out","w",stdout); a=read();b=a+read();c=b+read(); dd=c+read();e=dd+read(); n=read(); int u,v; for(int i=1;i<=n;i++){ u=read();v=read(); add(u-1,v,v-u+1); add(v,u-1,v-u+1); } dij(a); anss[1][2]=d[b];anss[1][3]=d[c]; anss[1][4]=d[dd]; dij(b); anss[2][3]=d[c];anss[2][4]=d[dd]; dij(c); anss[3][4]=d[dd]; ans=min(anss[1][2]+anss[3][4],min(anss[1][3]+anss[2][4],anss[1][4]+anss[2][3])); printf("%lld\n",ans>=d[e+1]?-1:ans); return 0; }
最后:
连续相同01串异或的作用好像还有好多,这是一个化简为繁的辅助操作,值得留个印象。