[省选集训2022] 模拟赛2

A

题目描述

有 \(n\) 个在 \([0,2^w)\) 内的非负整数,你需要执行下面的操作 \(n-1\) 次,使得剩下的数最小:

  • 选择两个非负整数 \(x,y\),将其合并成一个非负整数 \(z\),其中 \(z=\lfloor\frac{(x|y)}{2}\rfloor\)
  • 选择一个数 \(x\) 将其删去。

\(n\leq 10^5,w\leq 60\)

解法

可以把第一步操作拆分:\(\lfloor\frac{(x|y)}{2}\rfloor=\lfloor\frac{x}{2}\rfloor|\lfloor\frac{y}{2}\rfloor\),对于具有复杂过程的题目可以考虑向结果的方向猜结论,就像 To make one 这题一样,设 \(d_i\) 表示最后第 \(i\) 个数被除二的次数,那么最后的答案可以表示成 \(\frac{a_1}{2^{d_1}}|\frac{a_2}{2^{d_2}}...|\frac{a_n}{2^{d_n}}\)

结合删除操作考虑,一组 \(\{d\}\) 合法的充要条件是 \(\sum_{i=1}^n\frac{1}{2^{d_i}}\geq 1\)

有了这个结论我们从高位到低位考虑,假设现在考虑到了数位 \(w\),对于每个数我们维护 \(d_i\),然后考虑把答案的第 \(w\) 位变成 \(0\) 看是否可行,那么现在要验证的答案是 11010(0)??...,由于或操作我们不能出现答案是 \(0\) 但是第 \(i\) 个数右移 \(d_i\) 之后的对应位是 \(1\)(可以先或再异或来直接判断),我们要让 \(d_i\) 尽可能小,不合法我们再一步一步增加。

时间复杂度 \(O(nw^2)\),但是非常不满所以可以随便跑。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,w,ans,a[M],d[M],td[M];
void work()
{
	n=read();w=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	ans=(1ll<<w)-1;
	memset(d,0,sizeof d);
	for(int t=w-1;t>=0;t--)
	{
		int now=ans^(1ll<<t),sum=0;
		for(int i=1;i<=n;i++)
		{
			td[i]=d[i];
			while(((a[i]>>d[i])|now)^now) d[i]++;
			sum+=(1ll<<w-d[i]);
			if(sum>=(1ll<<w)) break; 
		}
		if(sum>=(1ll<<w)) ans=now;
		else memcpy(d,td,sizeof td);
	}
	printf("%lld\n",ans);
}
signed main()
{
	freopen("merge.in","r",stdin);
	freopen("merge.out","w",stdout);
	T=read();
	while(T--) work();
}

B

题目描述

对于字符串 \(T\),定义一次行走位选择一个任意长度的正整数序列 \(t_1,t_2...t_m\),其中 \(\forall i\in[1,m],t_i\in [1,|T|],|t_i-t_{i-1}|=1,t_1\not=t_m\),并且 \(T_{t_1},T_{t_2}...T_{t_m}\) 是一个回文串。

称一个字符串是好的当且仅当可以通过若干次行走使得这个字符串的每个位置被经过至少一次。现在有 \(q\) 次询问,每次问一个子串 \(s[l_i...r_i]\) 是不是好的。

\(n,q\leq 10^6\)

解法

手玩样例可以发现:如果存在 ABA 这样的小奇回文串,那么一定存在解。这是因为从一个点访问所有点再回到这个点可以构成回文串,但是不满足首尾下标不同的限制,而这种情况可以让我们最后走到另一个 A 得到解。

现在的问题是:考察偶回文串是否能覆盖完整个区间,我们考虑把这个限制分解到每个位置上,设某个点向左的最近对称点是 \(l_i\),向右的最近对称点是 \(r_i\),我用如下的图做进一步解释:

[省选集训2022] 模拟赛2

上图表示的是不合法的点 \(i\),那么一个询问不能被偶回文串覆盖的充要条件是 \(\exist i\in[L,R],[L,R]\subseteq (l_i,r_i)\),其中 \(l_i,r_i\) 可以在一开始用二分哈希\(+\)线段树简单预处理出来,特别地,如果左边没有覆盖它对称中心那么 \(l_i=0\),如果右边没有覆盖它的对称中心那么 \(r_i=n+1\)

那么剩下的就是一个偏序问题了,我们考虑按左端点的顺序做扫描线,扫描的过程是这样的:

  • 加入所有的 \(l_i<L\),这样我们解决了四分之一的偏序关系。
  • 如果右端点落在 \([i,r_i)\) 中一定不合法,那么我们在这个区间中打个不合法标记。
  • 最后还需要解决 \(i\geq L\),所以我们在查询前需要删除 \(i<L\) 的点。

那么时间复杂度 \(O(n\log n)\),简单指针加树状数组做一下即可。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 1000005;
#define pii pair<int,int>
#define ull unsigned long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],b[M],id[M],ans[M],mx[M<<2],mi[M<<2];
ull pw[M],hs[M][2];char s[M];multiset<pii> z;
struct node
{
	int l,r,id;
	bool operator < (const node &b) const
		{return l<b.l;}
}h[M],q[M];
//segment tree
ull check(int l,int r,int op)
{
	if(op==0) return hs[r][0]-hs[l-1][0]*pw[r-l+1];
	return hs[l][1]-hs[r+1][1]*pw[r-l+1];
}
void updmax(int i,int l,int r,int L,int R,int c)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R) {mx[i]=max(mx[i],c);return ;}
	int mid=(l+r)>>1;
	updmax(i<<1,l,mid,L,R,c);
	updmax(i<<1|1,mid+1,r,L,R,c);
}
void updmin(int i,int l,int r,int L,int R,int c)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R) {mi[i]=min(mi[i],c);return ;}
	int mid=(l+r)>>1;
	updmin(i<<1,l,mid,L,R,c);
	updmin(i<<1|1,mid+1,r,L,R,c);
}
void dfs(int i,int l,int r)
{
	if(l==r) {id[l]=i;return ;}
	int mid=(l+r)>>1;
	mx[i<<1]=max(mx[i<<1],mx[i]);
	mx[i<<1|1]=max(mx[i<<1|1],mx[i]);
	mi[i<<1]=min(mi[i<<1],mi[i]);
	mi[i<<1|1]=min(mi[i<<1|1],mi[i]);
	dfs(i<<1,l,mid);
	dfs(i<<1|1,mid+1,r);
}
void init()
{
	pw[0]=1;
	for(int i=1;i<=n;i++)
	{
		if(i+2<=n && s[i]==s[i+2]) a[i]++;
		pw[i]=pw[i-1]*371;a[i]+=a[i-1]; 
		hs[i][0]=hs[i-1][0]*371+s[i];
	}
	for(int i=n;i>=1;i--)
		hs[i][1]=hs[i+1][1]*371+s[i];
	for(int i=1;i<=4*n;i++) mi[i]=n+1;
	for(int i=1;i<n;i++)
	{
		int l=1,r=min(i,n-i),len=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(i-mid+1,i,0)==check(i+1,i+mid,1))
				len=mid,l=mid+1;
			else r=mid-1;
		}
		if(len)
			updmax(1,1,n,i+1,i+len,i+1),
			updmin(1,1,n,i-len+1,i,i);
	}
	dfs(1,1,n);
	for(int i=1;i<=n;i++)
	{
		int t1=mx[id[i]],t2=mi[id[i]];
		h[i]=node{!t1?0:2*t1-i-1,t2>n?n+1:2*t2-i+1,i};
	}
}
//tree-array
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int c)
{
	for(int i=x;i<=n;i+=lowbit(i))
		b[i]+=c;
}
int ask(int x)
{
	int r=0;
	for(int i=x;i>0;i-=lowbit(i))
		r+=b[i];
	return r;
}
signed main()
{
	freopen("pass.in","r",stdin);
	freopen("pass.out","w",stdout);
	n=read();scanf("%s",s+1);
	init();m=read();
	for(int i=1;i<=m;i++)
	{
		q[i].l=read(),q[i].r=read(),q[i].id=i;
		if(q[i].r>2) ans[i]|=(a[q[i].r-2]-a[q[i].l-1]>0);
	}
	sort(h+1,h+1+n);sort(q+1,q+1+m);
	for(int i=1,j=1;i<=m;i++)
	{
		while(j<=n && h[j].l<q[i].l)
		{
			add(h[j].id,1);add(h[j].r,-1);
			z.insert(make_pair(h[j].id,h[j].r));
			j++;
		}
		while(!z.empty() && z.begin()->first<q[i].l)
		{
			pii t=*z.begin();z.erase(z.begin());
			add(t.first,-1);add(t.second,1);
		}
		ans[q[i].id]|=(ask(q[i].r)==0);
	}
	for(int i=1;i<=m;i++)
		putchar(ans[i]+'0');
}
上一篇:JSOI 2008 一本通1549:最大数


下一篇:SqlServer批量导入C#100万条数据