JZOJ 4302【NOIP2015模拟11.3】IOIOI卡片占卜

Description

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理事长不想翻多余的牌,因此在实际使用卡片占卜之前会先计算出是否存在占卜成功的可能性。进一步,如果占卜可能成功,他会计算出能使占卜成功所消耗的时间的最小值。
现在给出卡片的排列信息和预先决定的操作信息,请你写一个程序,计算出占卜能否成功,如果能成功,输出消耗时间的最小值。

Analysis

首先把I变成1,O变成0。(长得好像)
这道题的特点是“IOIOI”,是固定的格式。可以发现,对于01串,可以对于相邻的两位异或一下。因为只有在I和O的转折点xor值为一,所以异或后的串是由4个1和插在其中的一堆0组成的。
例如:

111011000011(原串)

异或之后变成:

00110100010 (A)

我们模拟一个操作,例如在原串中的 [3,6] 区间去个反,原串变成

110100000011

异或一下试试:

01110000010 (B)

找一找(A)和(B)的关系。
可以发现,将区间 [3,6] 取反等价于把A中的位置 2,6 取反。
一般地,把区间 [l,r] 取反等价于把A中的位置 l1,r 取反。
那么目标串(IIII…IIIII)中异或起来是没有一个1的。所以可以把每个位置抽象成点,一个操作自然成了边。边权即为 rl+1 。然后对于4个1的位置,两两配对最短路去最小值即可。

Code

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=500010,M=N*2;
const ll INF=4340410370284600380;
int tot,to[M],next[M],last[N];
ll wei[M],dis[3][N];
bool bz[N];
queue<int> q;
void link(int u,int v,ll w)
{
    to[++tot]=v;
    wei[tot]=w;
    next[tot]=last[u];
    last[u]=tot;
}
void SPFA(int u,int p)
{
    memset(bz,0,sizeof(bz));
    memset(dis[p],60,sizeof(dis[p]));
    dis[p][u]=0,bz[u]=1;
    q.push(u);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=last[u];i;i=next[i])
        {
            int v=to[i];
            if(dis[p][u]!=INF && dis[p][u]+wei[i]<dis[p][v])
            {
                dis[p][v]=dis[p][u]+wei[i];
                if(!bz[v])
                {
                    bz[v]=1;
                    q.push(v);
                }
            }
        }
        bz[u]=0;
    }
}
int main()
{
    freopen("card.in","r",stdin);
    freopen("card.out","w",stdout);
    int A,B,C,D,E,n,u,v;
    scanf("%d %d %d %d %d %d",&A,&B,&C,&D,&E,&n);
    int v0=A,v1=A+B,v2=A+B+C,v3=A+B+C+D;
    fo(i,1,n)
    {
        scanf("%d %d",&u,&v),u--;
        link(u,v,v-u),link(v,u,v-u);
    }
    SPFA(v0,0);SPFA(v1,1);SPFA(v2,2);
    ll ans=dis[0][v1]+dis[2][v3];
    ans=min(ans,dis[0][v2]+dis[1][v3]);
    ans=min(ans,dis[0][v3]+dis[1][v2]);
    printf("%lld",ans>=INF?-1:ans);
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值