两道水题 异或+公交旅行(扫描线+异或线性基)(最短路)

题面

异或

给一个长度为 n n n 的序列 a a a q q q 次询问在 a [ l . . . r ] a[l...r] a[l...r] 中选择若干个数 (可以不选) 和 d d d 异或所能得到的最大值。

1 ≤ n , q ≤ 1 0 6    ,    0 ≤ a i < 2 30 1\leq n,q\leq 10^6\;,\;0\leq a_i< 2^{30} 1n,q106,0ai<230

不强制在线,出题人提供快读快输。

公交旅行

城市中有 n n n 个站台和 m m m 条公交线路,第 i i i 条公交线路由 t i t_i ti 个站台组成,记为 s i , 1 , s i , 2 , . . . , s i , t i s_{i,1}, s_{i,2}, ...,s_{i,t_i} si,1,si,2,...,si,ti。在 0 时刻,第 i i i 辆公交车会处在 s i , 1 s_{i,1} si,1 站台,之后每个时刻公交都会到达路线中的下一个站台。当公交到达终点站后,它下一个时刻将会回到出发的站台,注意一座站台可能在一条线路中出现多次,但不会相邻。

在 0 时刻,你处在站台 1 。如果在某一时刻你和公交在同一个站台,那么你就可以上这辆公交车,并在任意时刻下车,上下车的过程并不会花费时间。作为一位环保的优秀旅行家,你热爱乘坐公交出行。现在你想要知道,如果只乘坐公交出行,分别能最早在何时到达每个站台,或者判断这是不可能的。

注意,你一次只能乘坐一辆车,但在通往某个站台的过程中,可以乘坐许多辆不同的公交车。

n , m ≤ 1 0 5    ,    ∑ t i ≤ 2 × 1 0 5 n,m\leq10^5\;,\;\sum t_i\leq 2×10^5 n,m105,ti2×105

题解

异或

通过观察数据范围不难发现,这道题要求复杂度在 O ( n log ⁡ n ) O(n\log n) O(nlogn) 以内。

任选若干个数与 d 异或,我们不难想到用异或线性基,只是要运用得更灵活些。

具体地,线性基的每一位额外存一个 p o s pos pos ,表示这个可供异或的数出现的位置。把询问离线,用扫描线从左往右扫,把数加入线性基,扫描到右端点时我们来查询答案。因此在线性基里我们得尽量让 p o s pos pos 变大(靠右),这个贪心策略可以尽可能的满足询问对区间的要求。

在线性基里,插入一个数时,若该位原先有值,那么保留 p o s pos pos 更大的,用 p o s pos pos 更小的去更新下面的位。具体地,若线性基里该位已存的数为 y y y ,位置为 p o s y pos_y posy ,要加入的数为 x x x(此时 x x x 的这一位应该为 1,不然进不来) ,位置为 p o s x pos_x posx,若 p o s x > p o s y pos_x>pos_y posx>posy 那么 s w a p ( x , y ) , s w a p ( p o s x , p o s y ) swap(x,y),swap(pos_x,pos_y) swap(x,y),swap(posx,posy),表示把 x x x 放到这一位上,然后拿 y y y 继续跑。然后不管怎样,不要忘了 x = x    x o r    y x=x\;xor\;y x=xxory (若已经 s w a p swap swap ,则应该是新的 x x x 和新的 y y y)再继续下一位。

这样一来,最靠右的位置尽量放高位,其他位置也没浪费,当我们对 d d d 进行查询时,只考虑 p o s ≥ l i pos\geq l_i posli 的数,就能尽量保证高位被异或,进而保证答案最大化。

这应该是做过的一道原题。

CODE #1

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 1000005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#pragma GCC optimize(2)
inline LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
namespace io {
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int qr;
	// getchar
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	// print the remaining part
	inline void flush () {
		fwrite (obuf, 1, oS - obuf, stdout);
		oS = obuf;
	}
	// putchar
	inline void putc (char x) {
		*oS ++ = x;
		if (oS == oT) flush ();
	}
	// input a integer
	template <class I>
	inline void gi (I &x) {
		for (c = gc(); c < '0' || c > '9'; c = gc());
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15);
	}
	// print a integer
	template <class I>
	inline void print (I &x) {
		if (!x) putc ('0');
		while (x) qu[++ qr] = x % 10 + '0',  x /= 10;
		while (qr) putc (qu[qr --]);
	}
}
using io :: gi;
using io :: putc;
using io :: print;

const int MOD = 1000000007;
int n,m,i,j,s,o,k;
int L[MAXN],R[MAXN],d[MAXN]; 
int a[MAXN];
vector<int> bu[MAXN];
int as[MAXN];
int bt[105],rr[105];
void ins(int x,int ad) {
	for(int i = 29;i >= 0;i --) {
		if(x & (1<<i)) {
			if(!bt[i]) bt[i] = x,rr[i] = ad;
			else if(ad > rr[i]) swap(bt[i],x),swap(rr[i],ad);
			x ^= bt[i];
		}
	}
	return ;
}
int main() {
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	gi(n);
	for(int i = 1;i <= n;i ++) {
		gi(a[i]);
	}
	gi(m);
	for(int i = 1;i <= m;i ++) {
		gi(L[i]); gi(R[i]); gi(d[i]);
		bu[R[i]].push_back(i);
	}
	for(int i = 1;i <= n;i ++) {
		ins(a[i],i);
		for(int j = 0;j < (int)bu[i].size();j ++) {
			int y = bu[i][j],D = d[y];
			for(int k = 29;k >= 0;k --) {
				if(!(D & (1<<k)) && rr[k] >= L[y]) {
					D ^= bt[k];
				}
			}
			as[y] = D;
		}
	}
	for(int i = 1;i <= m;i ++) {
		print(as[i]);
		putc('\n');
	}
	io::flush();
	return 0;
}

公交旅行

如果坐同一辆公交车连续经过几个站,我们可以等价地认为在中间的每一站都瞬间下车然后瞬间上车,那么就可以抽象成有 ∑ t i \sum t_i ti 条费时为 1 的道路,第 i i i 条道路连接两个站点 u i , v i u_i,v_i ui,vi,该道路开放的时间点 T i T_i Ti 满足 T i ≡ k i      (  ⁣ ⁣ ⁣ m o d    P i ) T_i\equiv k_i\;\;(\!\!\!\mod P_i) Tiki(modPi)

实际上这就是一个不太好建图的最短路问题,边权是不定的(跟到达这个点的时间有关),但是容易发现我们肯定是能早到就早到,所以只要不把图建出来,每次实时获得边权,然后跑最短路就可以获得满分了。

CODE #2

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#pragma GCC optimize(2)
inline LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
namespace io {
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int qr;
	// getchar
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	// print the remaining part
	inline void flush () {
		fwrite (obuf, 1, oS - obuf, stdout);
		oS = obuf;
	}
	// putchar
	inline void putc (char x) {
		*oS ++ = x;
		if (oS == oT) flush ();
	}
	// input a integer
	template <class I>
	inline void gi (I &x) {
		for (c = gc(); c < '0' || c > '9'; c = gc());
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15);
	}
	// print a integer
	template <class I>
	inline void print (I &x) {
		if (!x) putc ('0');
		while (x) qu[++ qr] = x % 10 + '0',  x /= 10;
		while (qr) putc (qu[qr --]);
	}
}
using io :: gi;
using io :: putc;
using io :: print;

const int MOD = 1000000007;
int n,m,i,j,s,o,k;
int ti[MAXN];
vector<int> ad[MAXN];
struct it{
	int v;LL w;it(){v=w=0;}
	it(int V,LL W){v=V;w=W;}
};
bool operator < (it a,it b) {return a.w < b.w;}
bool operator > (it a,it b) {return b < a;}
priority_queue<it,vector<it>,greater<it> > b;
vector<it> q[MAXN];
LL dp[MAXN];
bool f[MAXN];
int main() {
	freopen("bus.in","r",stdin);
	freopen("bus.out","w",stdout);
	gi(n); gi(m);
	for(int i = 1;i <= m;i ++) {
		gi(ti[i]);
		for(int j = 0;j < ti[i];j ++) {
			gi(s);
			ad[i].push_back(s);
			q[s].push_back(it(i,(LL)j));
		}
	}
	dp[0] = 1e18;
	for(int i = 2;i <= n;i ++) {
		dp[i] = 1e18;
	}
	dp[1] = 0;
	b.push(it(1,0));
	while(!b.empty()) {
		it t = b.top();b.pop();
		if(f[t.v]) continue;
		dp[t.v] = t.w;
		f[t.v] = 1;
		for(int i = 0;i < (int)q[t.v].size();i ++) {
			it tt = q[t.v][i];
			int y = tt.v;
			LL adr = tt.w;
			int y2 = ad[y][(adr+1) % ti[y]];
			if(f[y2]) continue;
			adr -= t.w;
			((adr %= ti[y]) += ti[y]) %= ti[y];
			adr += t.w;
			b.push(it(y2,adr+1ll));
		}
	}
	for(int i = 2;i <= n;i ++) {
		if(dp[i] < 1e18) print(dp[i]),putc(' ');
		else putc('-'),putc('1'),putc(' ');
	}putc('\n');
	io :: flush();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值