1. 程式人生 > 實用技巧 >IOIOI卡片占卜——令人窒息的異或妙用

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。

12345
3
23
26
410

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串異或的作用好像還有好多,這是一個化簡為繁的輔助操作,值得留個印象。