CF-diary

(做题方式:瞟题解然后码)

1238E. Keyboard Purchase

\(\texttt{Difficulty:2200}\)

题意

给你一个长度为 \(n\) 的由前 \(m\) 个小写字母组成的字符串 \(s\) ,定义一个 \(m\) 个字母的排列 \(t\) 的代价为 \(\sum_{i=2}^n |pos_{s_i}-pos_{s_i-1}|\) ,其中 \(pos_c\) 表示 \(c\)\(t\) 中的位置。求最小的代价。

\(n\le 10^5, m\le 20\)

题解

考虑状压DP,但我们无法记录内部选择的顺序,所以我们可以相当于把所有已选的字符都放在最前面,加入一个字符就对没加入的字符减掉产生的多余的贡献。

复杂度 \(O(2^m\times m^2)\) .

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=21;
char s[N];
int a[M][M],f[1<<M],n,m;
int main()
{
	scanf("%d%d%s",&n,&m,s+1);
	for(int i=1;i<n;++i)
		++a[s[i]-'a'][s[i+1]-'a'],++a[s[i+1]-'a'][s[i]-'a'];
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int s=0;s<(1<<m);++s)
	{
		int d=__builtin_popcount(s);
		for(int i=0;i<m;++i) if(~s&(1<<i))
		{
			int t=0;
			for(int j=0;j<m;++j) if(i!=j) (s&(1<<j))?t+=d*a[i][j]:t-=d*a[i][j];
			f[s|(1<<i)]=min(f[s|(1<<i)],f[s]+t);
		}
	}
	printf("%d",f[(1<<m)-1]);
}

1234F. Yet Another Substring Reverse

\(\texttt{Difficulty:2400}\)

题意

给你一个由前 \(20\) 个小写字母组成的字符串 \(S\) ,你可以翻转一次 \(S\) 的任意一个子串。

问翻转后最长的各个字符都不相同的子串的长度。

题解

下设字符集大小为 \(m\)

注意到翻转一次相当于将两个子串拼起来。

预处理所有字符不相同的字符串( \(m^2\) 级别)包含的字母集合,并做一遍高维前缀和求出所有集合的最大子集。

枚举一个出现过的集合,其答案则为集合大小+其补集的子集大小最大值。

复杂度为高维前缀和的复杂度,\(O(2^m\times m)\) .

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,M=22;
char a[N];
int f[1<<M],n,ans;
bool vis[1<<M];
int main()
{
	scanf("%s",a+1);
	int n=strlen(a+1);
	for(int i=1;i<=n;++i)
	{
		int s=0,t=0;
		for(int j=i;j<=n;++j)
		{
			if(s&(1<<(a[j]-'a'))) break;
			else s|=(1<<(a[j]-'a')),f[s]=++t,vis[s]=true;
		}
	}
	const int S=(1<<20)-1;
	for(int i=0;i<20;++i)
		for(int s=0;s<=S;++s) if(s&(1<<i)) f[s]=max(f[s],f[s^(1<<i)]);
	for(int s=0;s<=S;++s)
		if(vis[s]) ans=max(ans,__builtin_popcount(s)+f[S^s]);
	printf("%d",ans);
}

1228E. Another Filling the Grid

\(\texttt{Difficulty:2300}\)

题意

\(n\times n\) 的网格中填 \(1\sim k\) 的数,要求每行每列至少有一个 \(1\) 。求方案数。

\(n\le 250, k\le 10^9\) .

题解

\(O(n^3)\) 的做法我很难会就扔了……

考虑经典容斥,枚举有 \(i\)\(j\) 列打破限制。

\[\sum_{i=0}^n\sum_{j=0}^n(-1)^{i+j}\binom{n}{i}\binom{n}{j}(k-1)^{(n-i)(n-j)}k^{n^2-(n-i)(n-j)} \]

可以做到 \(O(n^2\log n)\) 。可以推式子优化到 \(O(n\log n)\) ,可参考官方题解或 这个博客

代码

代码是 \(O(n^2\log n)\) 的。

#include<bits/stdc++.h>
using namespace std;
const int N=255,Mod=1e9+7;
int n,k,c[N][N],ans;
inline int po(int x, long long y)
{
	int r=1;
	while(y)
	{
		if(y&1) r=1ll*r*x%Mod;
		x=1ll*x*x%Mod, y>>=1;
	}
	return r;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=0;i<=n;++i) c[i][0]=c[i][i]=1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<i;++j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%Mod;
	for(int i=0;i<=n;++i)
		for(int j=0;j<=n;++j)
		{
			int ret=1ll*c[n][i]*c[n][j]%Mod*po(k-1,1ll*(n-i)*(n-j))%Mod*po(k,1ll*n*n-1ll*(n-i)*(n-j))%Mod;
			ans=(i+j&1?ans+Mod-ret:ans+ret)%Mod;
		}
	printf("%d",ans);
}

1223E. Paint the Tree

\(\texttt{Difficulty:2200}\)

题意

给你一棵 \(n\) 个节点的带权树,你需要给每个节点分配 \(k\) 种颜色。你的颜色种数是无限的,但每种颜色只能分配给至多 \(2\) 个节点。定义树的一条边是好的当且仅当边的两个顶点的颜色的交不为空。求最大的好的边的权值和。

\(n,k\le 5\times 10^5\),多组数据。

题解

对于当前点,我们可以考虑选择若干条边,但是被选择的点只剩下至多 \(k-1\) 条边可选。

\(f_{u,0/1}\) 表示以 \(u\) 为根的子树,至多能选 \(k\) / \(k-1\) 条边能获得的最大代价。

将所有子节点按照 \(f_{v,0}+w_v-f_{v,1}\) 从大到小排序,依次选择即可。

\(O(n\log n)\) .

代码

#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
	char c=getchar(); int x=0;
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
	return x;
}
const int N=5e5+5;
int n,k,head[N],nxt[N<<1],to[N<<1],wei[N<<1],wf[N];
long long f[N][2];
void addedge(int u, int v, int w, int now)
{
	nxt[now]=head[u], head[u]=now, to[now]=v, wei[now]=w;
}
bool cmp(int x, int y)
{
	return f[x][0]+wf[x]-f[x][1]>f[y][0]+wf[y]-f[y][1];
}
void dfs(int u, int fa)
{
	vector<int> ve;
	f[u][0]=f[u][1]=0;
	for(int e=head[u];e;e=nxt[e]) if(to[e]!=fa)
		dfs(to[e],u),wf[to[e]]=wei[e],ve.push_back(to[e]);
	sort(ve.begin(),ve.end(),cmp);
	int chos=0;
	for(auto v:ve)
	{
		if(f[v][0]+wf[v]>f[v][1])
		{
			if(chos<k-1) f[u][0]+=f[v][0]+wf[v];
			else f[u][0]+=f[v][1];
			if(chos<k) f[u][1]+=f[v][0]+wf[v];
			else f[u][1]+=f[v][1];
			++chos;
		}
		else f[u][0]+=f[v][1],f[u][1]+=f[v][1];
	}
}
int main()
{
	int q=gi();
	while(q--)
	{
		memset(head,0,sizeof(int)*(n+1));
		n=gi(),k=gi();
		for(int i=1;i<n;++i)
		{
			int u=gi(),v=gi(),w=gi();
			addedge(u,v,w,i*2-1),addedge(v,u,w,i*2);
		}
		dfs(1,0);
		printf("%lld\n",f[1][1]);
	}
}

1223F. Stack Exterminable Arrays

\(\texttt{Difficulty:2600}\)

题意

定义一个入栈出栈的操作序列:顺次扫序列,如果当前数与栈顶不等就将其加入栈中,否则弹出栈顶。

合法的入栈出栈的操作序列定义为操作完毕后栈为空。

给你一个长度为 \(n\) 的序列 \(\{a_n\}(1\le a_i\le n)\) ,求有多少个子段是合法的入栈出栈序列。

\(n\le 3\times 10^5\) .

题解

题目可以转化成有多少个子段是合法的括号序。

用 hash 记录每个右端点的栈,然后统计前面有多少个与其相等的即可。

理论复杂度 \(O(n\log n)\) ,根据实现可能可以线性。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,Mod=1e9+7;
int n,c[N],st[N];
unsigned long long a[N],b[N],hsh[N];
int main()
{
	int q; scanf("%d",&q);
	while(q--)
	{
		scanf("%d",&n);
		int tp=0;
		for(int i=1,x;i<=n;++i)
		{
			scanf("%d",&x);
			if(st[tp]==x) --tp;
			else st[++tp]=x,hsh[tp]=hsh[tp-1]*19260817+x;
			a[i]=b[i]=hsh[tp];
		}
		b[n+1]=0;
		sort(b+1,b+2+n);
		int m=unique(b+1,b+2+n)-b-1;
		for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+1+m,a[i])-b;
		long long ans=0;
		c[1]=1;
		for(int i=1;i<=n;++i) ans+=(c[a[i]]++);
		printf("%lld\n",ans);
		for(int i=1;i<=n;++i) c[a[i]]=0;
	}
}

1220E. Tourism

\(\texttt{Difficulty:2200}\)

题意

有一个 \(n\) 个点 \(m\) 条边的无向图,每个点有一个权值 \(w_i\),从 \(s\) 点出发,每次可以走到相邻的点,一条边不能连续走两次。求最大的走到的点的权值和。

\(n,m\le 2\times 10^5, w_i\le 10^9\) .

题解

边双可以从任意一个点进任意一个点出。

将边双缩点,转变成树上的问题。

注意到一个点如果所有子树节点(包括自己)边双大小都为 \(1\) 那么他无法向上返回,否则可以。

答案即为所有能返回点的权值和+不能返回的点为根的一条最长链权值和(最后走到这个不能返回的点)。

\(O(n\log n)\) .

代码

#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
	char c=getchar(); int x=0;
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
	return x;
}
const int N=2e5+5;
vector<int> e1[N],e[N];
typedef long long ll;
int n,m,u[N],v[N],w[N],dfn[N],bel[N],low[N],size[N],st[N],tp,tid,bid;
ll sum[N],f[N],ans,mx;
unordered_map<int,bool> mp[N];
void tarjan(int u, int fa)
{
	low[u]=dfn[u]=++tid;
	st[++tp]=u;
	for(auto v:e1[u]) if(v!=fa)
	{
		if(!dfn[v]) tarjan(v,u),low[u]=min(low[u],low[v]);
		else if(!bel[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		++bid; int v;
		do
		{
			v=st[tp--],bel[v]=bid;
			++size[bid],sum[bid]+=w[v];
		} while(v!=u);
	}
}
void dfs(int u, int fa)
{
	for(auto v:e[u]) if(v!=fa)
	{
		dfs(v,u);
		size[u]=max(size[u],size[v]);
		f[u]=max(f[u],f[v]);
	}
	f[u]+=sum[u];
	if(size[u]>1) ans+=sum[u];
	else mx=max(mx,f[u]);
}
int main()
{
	n=gi(),m=gi();
	for(int i=1;i<=n;++i) w[i]=gi();
	for(int i=1;i<=m;++i)
	{
		u[i]=gi(),v[i]=gi();
		e1[u[i]].push_back(v[i]);
		e1[v[i]].push_back(u[i]);
	}
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,0);
	for(int i=1;i<=m;++i)
		if(bel[u[i]]!=bel[v[i]]&&!mp[bel[u[i]]][bel[v[i]]]&&!mp[bel[v[i]]][bel[u[i]]])
		{
			e[bel[u[i]]].push_back(bel[v[i]]);
			e[bel[v[i]]].push_back(bel[u[i]]);
			mp[bel[u[i]]][bel[v[i]]]=true;
		}
	int s=gi();
	dfs(bel[s],0);
	printf("%lld",ans+mx);
}

1220F. Gardener Alex

\(\texttt{Difficulty:2600}\)

题意

用一个排列来递归构造一棵二叉树:每次选一个最小的,将左边的放左子树,右边的放右子树。

给你一个长度为 \(n\) 的排列,求所有循环移位的排列中构造出的二叉树深度最小的,输出深度和左移的位数。

\(n\le 2\times 10^5\)

题解

考虑 \(a_x\)\(a_y\) 的祖先,当且仅当 \(a_x=\min_{i=\min(x,y)}^{\max(x,y)}{a_i}\)

预处理出每个点的祖先数量,考虑维护将最左边的点放到最右边造成的影响,会造成左边一段区间祖先数量-1,右边一段区间+1。移到右边后自己的祖先数量也容易计算。用线段树维护区间祖先数量,ST表+二分找区间端点即可。

\(O(n\log n)\) .

代码

#include<bits/stdc++.h>
using namespace std;
const int N=400005;
int n,lg[N],rid[N],a[N],b[N],st[N<<2],tg[N<<2],f[N][25];
#define lx (x<<1)
#define rx (x<<1|1)
void build(int x, int l, int r)
{
	if(l==r)
	{
		st[x]=b[l];
		return ;
	}
	int mid=l+r>>1;
	build(lx,l,mid),build(rx,mid+1,r);
	st[x]=max(st[lx],st[rx]);
}
void pushdown(int x)
{
	if(!tg[x]) return ;
	tg[lx]+=tg[x],tg[rx]+=tg[x];
	st[lx]+=tg[x],st[rx]+=tg[x];
	tg[x]=0;
}
void update(int x, int l, int r, int sl, int sr, int w)
{
	if(sl>sr) return ;
	if(sl<=l&&r<=sr)
	{
		st[x]+=w,tg[x]+=w;
		return ;
	}
	pushdown(x);
	int mid=l+r>>1;
	if(sl<=mid) update(lx,l,mid,sl,sr,w);
	if(sr>mid) update(rx,mid+1,r,sl,sr,w);
	st[x]=max(st[lx],st[rx]);
}
int qq(int x, int l, int r, int s)
{
	if(!s) return 0;
	if(l==r) return st[x];
	pushdown(x);
	int mid=l+r>>1;
	return (s<=mid?qq(lx,l,mid,s):qq(rx,mid+1,r,s));
}
int query(int x, int l, int r, int sl, int sr)
{
	if(sl<=l&&r<=sr) return st[x];
	pushdown(x);
	int mid=l+r>>1,ans=0;
	if(sl<=mid) ans=query(lx,l,mid,sl,sr);
	if(sr>mid) ans=max(ans,query(rx,mid+1,r,sl,sr));
	return ans;
}
int qmin(int l, int r)
{
	int t=lg[r-l+1];
	return min(f[l][t],f[r-(1<<t)+1][t]);
}
int findl(int l, int r, int w)
{
	while(l<=r)
	{
		int mid=l+r>>1;
		if(qmin(l,mid)<w) r=mid-1;
		else l=mid+1;
	}
	return r+1;
}
int findr(int l, int r, int w)
{
	while(l<=r)
	{
		int mid=l+r>>1;
		if(qmin(mid,r)<w) l=mid+1;
		else r=mid-1;
	}
	return l-1;
}
void init(int l, int r, int d)
{
	if(l>r) return ;
	int x=rid[qmin(l,r)];
	b[x]=d;
	init(l,x-1,d+1),init(x+1,r,d+1);
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),rid[a[i]]=i,a[i+n]=a[i];
	for(int i=1;i<=(n<<1);++i) f[i][0]=a[i];
	for(int j=1;(1<<j)<=(n<<1);++j)
		for(int i=1;i+(1<<j-1)<=(n<<1);++i)
			f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
	init(1,n,1);
	build(1,1,(n<<1));
	int del=0,ans=query(1,1,(n<<1),1,n);
	for(int i=1;i<n;++i)
	{
		int lp=findl(i+1,i+n-1,a[i]),rp=findr(i+1,i+n-1,a[i]);
		update(1,1,(n<<1),i+1,lp-1,-1);
		update(1,1,(n<<1),rp+1,i+n-1,1);
		if(a[i]==1) update(1,1,(n<<1),i+n,i+n,1);
		else update(1,1,(n<<1),i+n,i+n,qq(1,1,(n<<1),rp)+1);
		int tmp=query(1,1,(n<<1),i+1,i+n);
		if(tmp<ans) ans=tmp,del=i;
	}
	printf("%d %d\n",ans,del);
}

1216F. Wi-Fi

\(\texttt{Difficulty:2300}\)

题意

\(n\) 个位置,有一些位置可以放基站,在位置 \(i\) 建设基站覆盖信号的范围为 \([\max(1,i-k),\min(n,i+k)]\),代价为 \(i\) ,未被基站覆盖的位置 \(j\) 需要花费 \(j\) 的代价直接连到信号塔。问信号覆盖所有点的最小代价。

\(n,k\le 2\times 10^5\) .

题解

显然一个点的决策有三种:

  • 被能覆盖它的最左边的基站覆盖;
  • 被右边能覆盖它的最近的基站覆盖;
  • 没有基站覆盖,直接连。

根据以上三个决策分别 DP 即可。 \(O(n)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,k;
char s[N];
long long f[N];
int main()
{
	scanf("%d%d%s",&n,&k,s+1);
	for(int i=1;i<=n;++i) s[i]-='0';
	int j=1;
	for(int i=1;i<=n;++i)
	{
		for(;j<i&&(j<i-k||!s[j]);++j);
		if(s[j]) f[i]=f[max(0,j-k-1)]+j;
		else f[i]=f[i-1]+i;
		if(s[i])
			for(int j=i-1;j>=i-k&&j>=0&&!s[j];--j) f[j]=min(f[j],f[i]);
	}
	printf("%lld",f[n]);
}

1215E. Marbles

\(\texttt{Difficulty:2200}\)

题意

\(n\) 个珠子 , 第 \(i\) 个珠子颜色是 \(c_i\) 。每次操作把相邻的两个珠子交换。现在要把相同颜色的珠子排列在相连的一段,问至少要多少次操作 。

\(n\le 4\times 10^5, c_i\le 20\)

题解

状压DP睿智题,可以轻松预处理 \(c[i][j]\) 表示把颜色为 \(i\) 的全部移到 \(j\) 的外面的步数。

\(O(nm+2^m\times m)\) ,其中 \(m\) 为颜色集大小。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5,M=22;
int n,a[N];
long long c[M][M],f[1<<M];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int x=1;x<=20;++x)
	{
		int r=0;
		for(int i=n;i;--i)
		{
			if(a[i]==x) ++r;
			else c[x-1][a[i]-1]+=r;
		}
	}
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int s=0;s<(1<<20);++s)
	{
		for(int i=0;i<20;++i) if(~s&(1<<i))
		{
			long long r=0;
			for(int j=0;j<20;++j) if(i!=j&&(~s&(1<<j))) r+=c[i][j];
			f[s^(1<<i)]=min(f[s^(1<<i)],f[s]+r);
		}
	}
	printf("%lld",f[(1<<20)-1]);
}

1208F. Bits And Pieces

\(\texttt{Difficulty:2600}\)

题意

给你一个长度为 \(n\) 的序列 \(\{a_n\}\),求 \(\max\{a_i|(a_j\&a_k)\}(i<j<k)\) .

\(n\le 10^6, a_i\le 2\times 10^6\) .

题解

经典套路和模型还是要熟悉。

模型是:对于一个 \(i\) ,考虑求一个 \(j>i\) ,使得 \(a_i|a_j\) 最大。预处理每个数出现的最右位置,并做一个高维后缀和求出每个集合出现的最右位置。从高往低位贪心,\(a_i\)\(1\) 的位不考虑。如果当前数加上当前位的集合出现的最右位置在 \(i\) 的右边就加上当前位。

本题套上一个 \(a_j\&a_k\) 依然不难,相当于把最右位置改成次右位置。直接做即可。

代码

#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
	char c=getchar(); int x=0;
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
	return x;
}
const int N=3e6+5;
int n,m,a[N],f[N],g[N],ans;
int main()
{
	n=gi();
	for(int i=1;i<=n;++i)
	{
		a[i]=gi();
		g[a[i]]=f[a[i]],f[a[i]]=i;
		m=max(m,a[i]);
	}
	int l; for(l=0;(1<<l)<=m;++l); --l;
	for(int j=0;j<=l;++j)
		for(int i=0;i<=m;++i)
			if(!(i&(1<<j)))
			{
				if(f[i^(1<<j)]>f[i]) g[i]=f[i],f[i]=f[i^(1<<j)];
				else if(f[i^(1<<j)]>g[i]) g[i]=f[i^(1<<j)];
				if(g[i^(1<<j)]>f[i]) g[i]=f[i],f[i]=g[i^(1<<j)];
				else if(g[i^(1<<j)]>g[i]) g[i]=g[i^(1<<j)];
			}
	for(int i=1;i<=n;++i)
	{
		int now=0;
		for(int j=l;~j;--j)
			if(!(a[i]&(1<<j))&&g[now|(1<<j)]>i) now|=(1<<j);
		ans=max(ans,now|a[i]);
	}
	printf("%d",ans);
}

1187F. Expected Square Beauty

\(\texttt{Difficulty:2600}\)

题意

对于一个正整数序列 \(\{x_n\}\),定义 \(B(x)\) 表示相同的数的极长连续段的数量。

\(n\) 个区间 \([l_i,r_i]\)\(x_i\)\([l_i,r_i]\) 中随机取值。求 \(B(x)^2\) 的期望。

\(n\le 2\times 10^5,l_i<r_i\le 10^9\) .

题解

\(f(i)=[x_i\neq x_{i-1}]\) ,特别的,\(f(1)=1\) (即 \(P(x_1\neq x_0)=1\) )。

\[E(B(x)^2) = E((\sum_{i=1}^n f(i))^2)=\sum_{i=1}^n\sum_{j=1}^n E(f(i)\cdot f(j)) \]

\(|i-j|>1\)\(E(f(i)\cdot f(j))=E(f(i))\cdot E(f(j))=P(x_i\neq x_{i-1})\cdot P(x_j\neq x_{j-1})\) .

\(i+1=j\) ,原式 \(=P(x_i\neq x_{i-1}\and x_i\neq x_{i+1})\)

\[=1-P(x_i=x_{i-1})-P(x_i= x_{i+1})+P(x_i= x_{i-1} \and x_i= x_{i+1}) \]

\(i=j\) ,原式 \(=P(x_i=x_{i-1})\) .

直接计算即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,Mod=1e9+7;
int l[N],r[N],p[N],suf[N],n,ans;
#define mul(x,y) (1ll*(x)*(y)%Mod)
int add(int x, int y)
{
	return (x+y>=Mod?x+y-Mod:x+y);
}
inline int inv(int x)
{
	int y=Mod-2,r=1;
	while(y)
	{
		if(y&1) r=mul(r,x);
		x=mul(x,x), y>>=1;
	}
	return r;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&l[i]);
	for(int i=1;i<=n;++i) scanf("%d",&r[i]);
	p[1]=1;
	for(int i=2;i<=n;++i)
		p[i]=mul(max(0,min(r[i],r[i-1])-max(l[i],l[i-1])+1),inv(mul(r[i]-l[i]+1,r[i-1]-l[i-1]+1))),
		p[i]=(1-p[i]+Mod)%Mod;
	suf[n]=p[n];
	for(int i=n-1;i;--i) suf[i]=add(suf[i+1],p[i]);
	for(int i=1;i<=n-2;++i) ans=add(ans,mul(p[i],suf[i+2]));
	for(int i=1;i<n;++i)
	{
		int tmp1=max(0,min(r[i],min(r[i-1],r[i+1]))-max(l[i],max(l[i-1],l[i+1]))+1),
			tmp2=mul(r[i]-l[i]+1,mul(r[i-1]-l[i-1]+1,r[i+1]-l[i+1]+1)),
			tmp3=((1-(1-p[i])-(1-p[i+1]))%Mod+Mod)%Mod,
			tmp=add(tmp3,mul(tmp1,inv(tmp2)));
		ans=add(ans,(i==1?p[2]:tmp));
	}
	ans=mul(2,ans);
	for(int i=1;i<=n;++i) ans=add(ans,p[i]);
	printf("%d",ans);
}

1168C. And Reachability

\(\texttt{Difficulty:2400}\)

题意

给你一个长度为 \(n\) 的序列 \(\{a_n\}\) 。定义 \(x\) 可到达 \(y\) 当且仅当存在一个 \(a_x\sim a_y\) 的子序列满足相邻两项按位与大于 0 。

\(q\) 次询问,每次询问 \(x\) 能否到达 \(y\)

\(n,q,a_i\le 3\times 10^5\) .

题解

感觉又裸搬题解了,真的菜。

考虑预处理 \(f(i,j)\) 表示 \(a_i\) 能跳到的最近的第 \(j\) 位为 \(1\) 的位置,类似序列自动机。

转移和询问都比较显然。

预处理和处理询问都比较显然。

预处理的复杂度貌似是两个 \(\log\) ,询问是一个。

代码

#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
	char c=getchar(); int x=0;
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
	return x;
}
const int N=3e5+5,L=20;
int n,q,a[N],f[N][L+5],l[L+5];
int main()
{
	n=gi(),q=gi();
	for(int i=1;i<=n;++i) a[i]=gi();
	for(int j=0;j<L;++j) l[j]=n+1,f[n+1][j]=n+1;
	for(int i=n;i;--i)
	{
		for(int j=0;j<L;++j) f[i][j]=n+1;
		for(int j=0;j<L;++j) if(a[i]&(1<<j))
		{
			for(int k=0;k<L;++k) f[i][k]=min(f[i][k],f[l[j]][k]);
			f[i][j]=l[j]=i;
		}
	}
	while(q--)
	{
		int x=gi(),y=gi();
		bool flag=false;
		for(int i=0;i<L;++i) if(a[y]&(1<<i)&&f[x][i]<=y) flag=true;
		puts(flag?"Shi":"Fou");
	}
}

posted @ 2019-10-19 21:42  x_faraway_x  阅读(261)  评论(0编辑  收藏  举报