【NOIP2015模拟11.3】IOIOI卡片占卜

Description

就像你看到的题目一样,现在有A个I,接着B个O,再接着C个I,再接着D个O,再接着E个I,排成一排。你现在有N种操作,第i种操作吧从第li个字符到第ri个字符这个区间内的字符,I变成O,O变成I,时间为ri-li+1。求把所有字符都变成I的最小时间。若无解输出-1。
A,B,C,D,E,N<=10^5

Solution

神奇的一道题。
先%%出题人,这种思路谁想得到嘛~
Japanese…
首先,我们建立一个新的序列b,bi=ai^ai+1
这样子,b中有了一堆0,中间有4个1。
那么一次操作相当于什么呢?
相当于把bl-1和br取反!(自己想想为什么)
那么,我们可以从l-1向r连一条长度为r-l+1的边。
而我们的任务就变成了把b序列变成0.(虽然原序列全为0,b序列也全为0,但这是不可能的,自己想想为什么)
那么对于两个1,把它们同时变为0的最小花费为,这两个1中间的最短路!(自己想想为什么)
那么,我们只需要打一个spfa/迪杰斯特拉就好了。
不过经过栋爷的测试,迪杰斯特拉好像过不了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 500005
#define ll long long
using namespace std;
int n,tot,g[3][4]={0,1,2,3,0,2,1,3,0,3,1,2};
int t[N*2],next[N*2],last[N],a[5],d[N*2];
ll dis[N],v[N*2],ans,l,r;
bool bz[N];
void add(int x,int y,ll z){
    t[++tot]=y;v[tot]=z;next[tot]=last[x];last[x]=tot;
}
ll spfa(int S,int T) {
    memset(dis,127,sizeof(dis));
    ll mx=dis[S];dis[S]=0;
    memset(bz,0,sizeof(bz));bz[S]=1;
    int i=0,j=1;d[1]=S;
    while (i<j) {
        rep(k,d[++i]) if (dis[t[k]]>dis[d[i]]+v[k]) {
            dis[t[k]]=dis[d[i]]+v[k];
            if (!bz[t[k]]) bz[t[k]]=1,d[++j]=t[k];
        }
        bz[d[i]]=0;
    }
    if (dis[T]==mx) return -1;else return dis[T];
} 
int main() {
    freopen("card.in","r",stdin);
    freopen("card.out","w",stdout);
    fo(i,0,4) scanf("%d",&a[i]),a[i]+=a[i-1];
    scanf("%d",&n);ans=100000000000;
    fo(i,1,n) scanf("%lld%lld",&l,&r),add(l-1,r,r-l+1),add(r,l-1,r-l+1);
    fo(i,0,2) {
        ll x=spfa(a[g[i][0]],a[g[i][1]]),y=spfa(a[g[i][2]],a[g[i][3]]);
        if (x==-1||y==-1) continue;
        ans=min(ans,x+y);
    }
    if (ans==100000000000) printf("-1");else printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值