题目描述:
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 le i le N$)为【将从左数第Li张卡片到第Ri张卡片全部翻转】。翻转一张卡片需要1秒的时间,因此第i种操作耗时$R_i-L_i+1$秒。
操作结束后,如果所有卡片都是正面朝上则占卜成功。$K$理事长不想翻多余的牌,因此在实际使用卡片占卜之前会先计算出是否存在占卜成功的可能性。进一步,如果占卜可能成功,他会计算出能使占卜成功所消耗的时间的最小值。
现在给出卡片的排列信息和预先决定的操作信息,请你写一个程序,计算出占卜能否成功,如果能成功,输出消耗时间的最小值。
题解:
第一眼:数据结构? 区间操作? 线段树? 想多了
此题思路简直毒瘤。
首先,按照官方题解的话来说,就是把所有字母$I$看成$0$, $O$看成$1$。然后对原序列进行一下魔改:
每一项都变成相邻两项的异或和。 可以发现,这样子处理,序列就被魔改成了: 一坨$0$中间放了四个$1$。
而这$4$个$1$在哪呢?刚好就在每次$I , O$交界的地方。
而如今对于新序列,我们的目标就是将其变成全为$0$的序列。
那么,不妨大胆一点。 我们把每个数字看作一个点,建图!
一次修改,就相当于一条从 $l - 1$ 连向 $r$ 的无向边, 边权即为 $(r - l + 1)$。
那么,如果我们要修改两个点 $1$, 最小的代价当然就是其间的最短路了。
为什么?
注意,这里连的是从一个点到一个点,不再是区间操作了!!!
按照样例建立的图。可以得知,一个点的度数为x,那么它就有被修改x次的机会。
而对于两个1(即我们要修改的关键位置), 我们所要的答案即为两个点之间的最短路。注意,由于原图已经改为点与点的连接,原序列中该区间的点只可能被修改$0$次或者是$2$次(新图中最短路之间的节点,如上图中区间$[1, 10]$的$3$,终点和起点各算修改一次。)
所以,当且仅当我们选择到最短路时,只会对两个关键位置修改,对其他点无影响。那么,我们的答案即为4个关键点两两配对,求最短路并相加的最小值。
注意,我们可能会出现最短路重合。如 $ 1 -> 3, 2 -> 4$这样的组合,两点之间的最短路可能出现重合部分,直接相加是否会重复计算,让答案变大?
当然不会。 如果路径出现重合,那我们在取min的时候,肯定会取 $1 -> 2, 3 -> 4$这个组合的答案。所以不会影响。
#include <bits/stdc++.h> using namespace std; #define N 1000100 #define ll long long const ll inf = (ll)1e16; template <class T> inline void read(T& a){ T x = 0, s = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); } a = x * s; return ; } struct node{ int v; ll w; int next; node(int v = 0, int w = 0, int next = -1){ this -> v = v; this -> w = w; this -> next = next; return ; } } t[N << 1]; int f[N]; int bian = 0; inline void add(int u, int v, int w){ t[++bian] = node(v, w, f[u]), f[u] = bian; t[++bian] = node(u, w, f[v]), f[v] = bian; return ; } int A, B, C, D, E; int n; bool vis[N]; ll d[N]; ll dis[5][5]; struct point{ ll u, w; point(ll u = 0, ll w = 0){ this -> u = u; this -> w = w; return ; } bool operator < (const point& a) const{ return w > a.w; } }; #define v t[i].v void dyj(int s){ for(int i = 0; i <= n; i++) d[i] = inf; memset(vis, 0, sizeof(vis)); priority_queue <point> q; d[s] = 0; q.push(point(s, 0)); while(!q.empty()){ point temp = q.top(); q.pop(); int now = temp.u; if(!vis[now]){ vis[now] = 1; for(int i = f[now]; ~i; i = t[i].next){ if(d[v] > d[now] + t[i].w){ d[v] = d[now] + t[i].w; if(!vis[v])q.push(point(v, d[v])); } } } } return ; } #undef v template <class T> inline void insert(T i){ dis[i][1] = d[A], dis[i][2] = d[A + B], dis[i][3] = d[A + B + C]; dis[i][4] = d[A + B + C + D]; return ; } int main(){ // freopen("card.in", "r", stdin); // freopen("card.out", "w", stdout); read(A), read(B), read(C), read(D), read(E); n = A + B + C + D + E; memset(f, -1, sizeof(f)); int m; read(m); for(int i = 1;i <= m; i++){ int x, y, w; read(x), read(y); w = y - x + 1; add(x - 1, y, w); add(y, x - 1, w); } dyj(A); insert(1); dyj(A + B); insert(2); dyj(A + B + C); insert(3); dyj(A + B + C + D); insert(4); ll ans = min(dis[1][2] + dis[3][4], min(dis[1][3] + dis[2][4], dis[1][4] + dis[2][3])); if(ans >= inf) puts("-1"); else printf("%lld ", ans); return 0; }