[省选集训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\),我用如下的图做进一步解释:
上图表示的是不合法的点 \(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');
}
C
题目描述
有个二分图,左部的点 \(i\) 连向右部的点 \([1,a_i]\),右部的点 \(i\) 连向左部的 \([1,b_i]\),左部的第 \(i\) 个点最多选择 \(c_i\) 次,右部的第 \(i\) 个点最多选择 \(d_i\) 次。
请选择最大数量的环,环上的每一个节点视作被选择一次。
\(n,m\leq 5\cdot 10^5\)
解法
拿到这题根本没有思路,直到最后摆烂交了个暴力,我才知道:最优解只会存在二元环。因为对于一个长度更大的环,我们把一定可以把它调整成二元环并且使用的点更少,所以更优。
暴力 \(\tt dinic\) 跑匹配肯定是不行的,考虑到这题特殊的连边方式,我们可以大胆的使用贪心。思路大体是:把当前点和范围最小的但是能解决它的点匹配,简称够用就行。
具体来说本题匹配的条件是一个二位偏序:\(a_i\geq j,b_j\geq i\),我们按 \(a_i\) 从小到大扫描,然后把合法的 \(j\) 加入一个 \(\tt set\) 中,然后按 \(i\) 从小到大开始匹配,每次选择大于等于 \(i\) 的第一个 \(b_j\) 匹配,这样暴力 \(\tt lower\_bound\) 就可以获得正确的复杂度。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 500005;
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
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],c[M],d[M];long long ans;
vector<int> v[M];multiset<pii> s;
signed main()
{
freopen("worship.in","r",stdin);
freopen("worship.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read(),v[a[i]].pb(i);
for(int i=1;i<=m;i++) b[i]=read();
for(int i=1;i<=n;i++) c[i]=read();
for(int i=1;i<=m;i++) d[i]=read();
for(int i=1;i<=m;i++)
{
s.insert(mp(b[i],d[i]));
for(int x:v[i]) while(!s.empty())
{
multiset<pii>::iterator
it=s.lower_bound(mp(x,-1e9));
if(it==s.end()) break;
pii y=*it;s.erase(it);
if(y.second>c[x])
{
ans+=c[x];y.second-=c[x];
c[x]=0;s.insert(y);break;
}
else ans+=y.second,c[x]-=y.second;
}
}
printf("%lld\n",ans);
}