【NOIP2015】IOIOI卡片占卜

18 篇文章 0 订阅
5 篇文章 0 订阅

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

Solution

哇,十分像数据结构,但是肯定不能用数据结构。
怎么想都想不到,拿了个20分,看了题解瞬间爆炸……

差分

因为是01序列,每次都把一段序列取反,中间的异或值是不变的,只有端点与两边的异或值会改变,那么我们把01序列转化成两两的异或值,每次把l到r区间取反,就相当于把差分后的序列l-1和r取反。
不知道是怎么想来的,做题经验。

问题转化

现在问题就转化成了,有一段01序列,中间只有4个1,每次把给定的两个点取反(可以取反多次),要求把序列变成全是0的序列。
既然涉及了两个,那么就有依赖关系了,我们考虑连边。每走完一条边就是把两个点的值取反,因为是差分,所以从i走到j,再从j走到k,这两段差分区间相交,但是实际的区间只是相邻,所以连边就是区间的取反可以没有后效性了。
那么我们对于每个1的位置做一次spfa,然后枚举两对点,两两的最短路和就是当前的答案,最终答案去一个最小值就好了。
注意答案会爆int

Code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=500007;
int i,j,k,l,t,n,m;
long long ans,ans1;
int a,b,c,dd,e;
int first[maxn*2],next[maxn*2],last[maxn*2],chang[maxn*2],num;
int g[4],data[maxn*3];
long long d[4][maxn],da;
bool bz[maxn];
void add(int x,int y,int z){
    last[++num]=y;
    next[num]=first[x];
    first[x]=num;
    chang[num]=z;
}
void spfa(int x,int y){
    int i,j,k,head=0,tail=1,now;
    memset(bz,0,sizeof(bz));
    memset(data,0,sizeof(data));
    fo(i,0,maxn)d[y][i]=99999999999;
    da=d[y][0];
    d[y][x]=0;
    bz[x]=1;data[tail]=x;
     while(head<tail){
        now=data[++head];
        for(i=first[now];i;i=next[i]){
            if(d[y][now]+chang[i]<d[y][last[i]]){
                d[y][last[i]]=d[y][now]+chang[i];
                if(!bz[last[i]]){
                    bz[last[i]]=1;
                    data[++tail]=last[i];
                }
            }
        }
        bz[now]=0;
    }
}
int main(){
    freopen("card.in","r",stdin);
    freopen("card.out","w",stdout);
    scanf("%d%d%d%d%d",&a,&b,&c,&dd,&e);
    g[0]=a;g[1]=a+b;g[2]=a+b+c;g[3]=a+b+c+dd;
    scanf("%d",&n);
    fo(i,1,n){
        scanf("%d%d",&k,&t);
        add(k-1,t,t-k+1);
        add(t,k-1,t-k+1);
    }
    bool az[4]={0,0,0,0};
    spfa(g[0],0);
    spfa(g[1],1);
    spfa(g[2],2);
    spfa(g[3],3);
    ans=99999999999;
    fo(i,0,3){
        az[i]=1;
        fo(j,i+1,3){
            az[j]=1;
            fo(k,0,3){
                if(!az[k]){
                    az[k]=1;
                    fo(l,0,3){
                        if(!az[l]){ 
                            ans1=d[i][g[j]]+d[k][g[l]];            
                            ans=min(ans,ans1);
                        }
                    }
                    az[k]=0;
                }
            }
            az[j]=0;
        }
        az[i]=0;
    }
    if(ans>=da)printf("-1\n");
    else printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值