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

差分

神奇的差分!
把原序列相邻两项进行异或得到新序列。
那么新序列只有4个1,目标是经过一系列操作让1的个数变为0。
一次操作[l,r]在新序列上相当于把l-1与r取反。
把操作[l,r]视为l-1向r连无向边,权值为操作的耗时。
如果只有两个1,答案是最短路。
因为最短路不会出现环,所以除了起点和终点都被经过2次,自身不变。而起点与终点只被经过1次,会由0变成1。
四个1的话暴力两两匹配做最短路,取最小值即可。
DIJ我超时了,打了SPFA发现有奇效。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=500000+10;
int h[maxn],go[maxn*2],dis[maxn*2],next[maxn*2],id[maxn];
int dl[maxn];
bool bz[maxn];
int a[5];
int i,j,k,l,r,n,m,tot,top;
ll t,ans,inf,f[maxn];
void add(int x,int y,int z){
    if (!bz[x]){
        bz[x]=1;
        id[++top]=x;
    }
    if (!bz[y]){
        bz[y]=1;
        id[++top]=y;
    }
    go[++tot]=y;
    dis[tot]=z;
    next[tot]=h[x];
    h[x]=tot;
}
ll spfa(int x,int y){
    int i,t,now,l,r;
    fo(i,1,top) f[id[i]]=inf+1,bz[id[i]]=0;
    f[x]=0;
    bz[x]=1;
    dl[r=1]=x;
    l=0;
    while (l!=r){
        l=l%top+1;
        now=dl[l];
        t=h[now];
        while (t){
            if (f[now]+dis[t]<f[go[t]]){
                f[go[t]]=f[now]+dis[t];
                if (!bz[go[t]]){
                    r=r%top+1;
                    dl[r]=go[t];
                    bz[go[t]]=1;
                }
            }
            t=next[t];
        }
        bz[now]=0;
    }
    return f[y];
}
int main(){
    freopen("card.in","r",stdin);freopen("card.out","w",stdout);
    scanf("%d%d%d%d%d",&j,&k,&l,&r,&t);
    a[1]=j;
    a[2]=j+k;
    a[3]=j+k+l;
    a[4]=j+k+l+r;
    n=j+k+l+r+t;
    scanf("%d",&m);
    fo(i,1,m){
        scanf("%d%d",&j,&k);
        add(j-1,k,k-j+1);add(k,j-1,k-j+1);
        inf+=(ll)k-j+1;
    }
    fo(i,1,4){
        fo(j,1,top+1)
            if (j>top||id[j]==a[i]) break;
        if (j>top){
            printf("-1\n");
            return 0;
        }
    }
    ans=inf+1;
    fo(i,1,3)
        fo(j,i+1,4){
            t=spfa(a[i],a[j]);
            fo(k,1,4)
                if (k!=i&&k!=j) break;
            fo(l,1,4)
                if (l!=i&&l!=j&&l!=k) break;
            t+=spfa(a[k],a[l]);
            ans=min(ans,t);
        }
    if (ans==inf+1) printf("-1\n");else printf("%lld\n",ans);
}

你可能感兴趣的:(IOIOI卡片占卜)