题目
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理事长不想翻多余的牌,因此在实际使用卡片占卜之前会先计算出是否存在占卜成功的可能性。进一步,如果占卜可能成功,他会计算出能使占卜成功所消耗的时间的最小值。
现在给出卡片的排列信息和预先决定的操作信息,请你写一个程序,计算出占卜能否成功,如果能成功,输出消耗时间的最小值。
分析
首先在输入的字符串的后面加一个'I',然后把字符串中的'I'变成1,'O'变成0,转换为01串。
接着将相邻的数两两异或,就可以得出一个新的序列:一堆0之中夹着4个1。
现在要将序列中的数全部变成0;
我们发现如果修改区间[L,R],实际上就是把序列中在位置L-1的和位置R的数改变一下。
那么将每个位置看作一个点,每次修改当做一条连接位置L-1的和位置R的权值为R-L+1的边。
答案就是把这4个1的位置分成两组,求出它们的最短距离之和。
#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const long long maxlongint=214748364700000;
using namespace std;
long long a[10],d[2000006],n,m,ans,dis[5][500006],to[1000006],v[1000006],next[1000006],last[1000006],tot;
bool bz[500006];
long long bj(long long x,long long y,long long z)
{
next[++tot]=last[x];
last[x]=tot;
to[tot]=y;
v[tot]=z;
}
long long spfa(long long x)
{
long long head=0,tail=1,i,j,k;
memset(dis[x],43,sizeof(dis[x]));
memset(bz,true,sizeof(bz));
d[1]=a[x];
dis[x][a[x]]=0;
while(head<tail)
{
k=d[++head];
bz[k]=true;
for(i=last[k];i;i=next[i])
{
j=to[i];
if(v[i]+dis[x][k]<dis[x][j])
{
dis[x][j]=dis[x][k]+v[i];
if(bz[j])
{
bz[j]=false;
d[++tail]=j;
}
}
}
}
}
int main()
{
for(long long i=1;i<=5;i++)
{
long long x;
scanf("%lld",&x);
a[i]=a[i-1]+x;
}
scanf("%lld",&n);
for(long long i=1;i<=n;i++)
{
long long x,y;
scanf("%lld%lld",&x,&y);
bj(x-1,y,y-x+1);
bj(y,x-1,y-x+1);
}
ans=maxlongint;
for(long long i=1;i<=4;i++)
spfa(i);
for(long long i=1;i<=3;i++)
for(long long j=i+1;j<=4;j++)
for(long long k=1;k<=3;k++)
if(i!=k && j!=k)
for(long long l=k+1;l<=4;l++)
if(l!=i && l!=j)
{
ans=min(ans,dis[i][a[j]]+dis[k][a[l]]);
}
if(ans<=2147483647000)
printf("%lld\n",ans);
else
printf("-1\n");
}