2021 10.14 模拟测试 *
T1
首先把所有连通块缩点,建成一张新图,然后求出树的直径,答案就是直径除以2向下取整,因为从树的直径的中点开始合并,所需次数一定是最小的。
view code
#include<bits/stdc++.h>
#define in read()
using namespace std;
inline int read()
{
int data=0,w=1; char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2e5+10;
struct edge{int u,v,nxt;}e[N<<1],e2[N<<1];
int first[N],cnt,first2[N],cnt2;
int n,c[N],flag[N],st,ans,col,blo[N];
int fa[N],tot;
inline void add(int u,int v){e[++cnt]=(edge){u,v,first[u]};first[u]=cnt;}
inline void add2(int u,int v){e2[++cnt2]=(edge){u,v,first2[u]};first2[u]=cnt2;}
inline int getfa(int x){return fa[x]=fa[x]==x?x:getfa(fa[x]);}
inline void merge(int x,int y)
{
int fx=getfa(x),fy=getfa(y);
fa[fx]=fy;
}
void dfs(int u,int fa,int dep)
{
if(dep>ans) ans=dep,st=u;
// cout<<u<<'\n';
for(int i=first2[u];i;i=e2[i].nxt)
{
int v=e2[i].v;
if(v==fa) continue;
dfs(v,u,dep+1);
}
// cout<<++tot<<'\n';
}
int solve()
{
st=1;
dfs(1,0,1);
ans=0;
dfs(st,0,1);
return ans;
}
int main()
{
#ifdef socc
// freopen("tree2.in","r",stdin);
#else
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
#endif
n=in;
for(int i=1;i<=n;i++) c[i]=in,fa[i]=i;
for(int i=1;i<n;i++)
{
int u=in,v=in;
if(c[v]==c[u]) merge(v,u);
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)
{
int x=getfa(i);
if(!flag[x]) blo[x]=++col,blo[i]=col,flag[x]=1;
else blo[i]=blo[x];
}
for(int i=1;i<=cnt;i++)
if(blo[e[i].u]!=blo[e[i].v])
add2(blo[e[i].u],blo[e[i].v]);
write(solve()>>1);
return 0;
}
T2
如果我们可以二分来找到答案,那我们就可以在\(O(log)\)内完成,所以我们可以考虑随机二分一个点,
结论一 对于一个固定的左端点,区间对应的大小关于右端点单调。
结论二 假设二分的范围为 \(n\),对于任意目标位置,随机取中间点二分 找到答案的期望次数为\(2ln_n+O(1)\)。
那么对于左端点\(i\),维护右端点的区间\(l[i],r[i]\) 。二分的时候根据 \(r[i]-l[i]+1\) 带权随机一个 \(i\),再随机一个\(j \in[l[i],r[i]]\)即可。时间复杂度\(O(nlog^2n)\) 。
view code
#include<bits/stdc++.h>
#define pii pair<int,int>
#define lc (p<<1)
#define rc (p<<1|1)
#define in read()
using namespace std;
inline int read()
{
int data=0,w=1; char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=1e5+10;
int n,m,a[N];
namespace socc{
int l[N],r[N],p[N],num[N],sum;
struct node{int num;bool flag;}t[N<<2];
inline void pushup(int p){t[p].flag=t[lc].flag|t[rc].flag;}
void update(int p,int l,int r,int x,int k)
{
if(l==r) return void(t[p].flag=(t[p].num+=k));
int mid=(l+r)>>1;
if(x<=mid) update(lc,l,mid,x,k);
else update(rc,mid+1,r,x,k);
pushup(p);
}
inline bool query(int p,int l,int r)
{
if(l==r) return t[p].num>0;
int mid=(l+r)>>1;
if(t[lc].flag) return query(lc,l,mid);
if(t[rc].flag) return query(rc,mid+1,r);
return 0;
}
void build(int p,int l,int r)
{
if(l==r) return void(t[p].flag=(t[p].num=-num[l]));
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(p);
}
inline int mul(const register int a,const register int b){return (1ll*a*b)%sum;}
inline int qpow(register int a,register int b){
register int sum=1;
while(b>0){
if(b&1) sum=mul(sum,a);
a=mul(a,a);b>>=1;}
return sum;
}
pii create()
{
sum=0;
for(int i=1;i<=n;i++)
if(r[i]>=l[i]) sum+=r[i]-l[i]+1;
int tmp=qpow(rand(),rand());
for(int i=1,len;i<=n;i++)
{
len=r[i]-l[i]+1;
if(tmp<len) return make_pair(i,l[i]+tmp);
tmp-=len;
}
return make_pair(0,0);
}
int check()
{
pii now=create();
memset(num,0,sizeof(num));
for(int i=now.first,lim=now.second;i<=lim;i++) ++num[a[i]];
build(1,1,n);
int pos=n,ans=0;
for(int i=n;i;--i)
{
update(1,1,n,a[i],1);
while(pos>=l[i]&&query(1,1,n)) update(1,1,n,a[pos--],-1);
p[i]=pos;
if(r[i]>=l[i]) ans+=r[i]-pos;
}
return ans;
}
void solve()
{
for(int i=1;i<=n;i++) l[i]=i,r[i]=n;
for(int i=1;i<=60;i++)
{
int tmp=check();
if(tmp<m)
{
m-=tmp;
for(int j=1;j<=n;j++)
if(l[j]<=r[j]) r[j]=p[j];
}
else for(int j=1;j<=n;j++)
if(l[j]<=r[j]) l[j]=p[j]+1;
}
for(int i=1;i<=n;i++)
if(l[i]<=r[i])
{
sort(a+i,a+l[i]+1);
for(int j=i;j<=l[i];j++) write(a[j]),putchar(' ');
return ;
}
}
}
main()
{
// freopen("multiset2.in","r",stdin);
// freopen("multiset2.out","w",stdout);
n=in,m=in;srand(114514);
for(int i=1;i<=n;i++) a[i]=in;
return socc::solve(),0;
}
T3
对于序列中的一个数\(a[i]\),他要满足可以被计入答案,那么它需要满足以下性质
而对于\(j>i\),如果想要\(a[j]\)对答案有贡献,那么需要满足
这个该这么理解呢\(i-a[i]\)是要满足\(a[i]\)要删除的数的个数,\(j\)同理,所以如果要满足\(j\)成立那么前面要删除的数是必须要大于\(i\)需要的
于是这个时候又有了三个式子
这时候可以发现第二个式子和第三个式子可以推出第一个式子,于是我们就变成了要有一个序列满足
这就很明显是一个二维偏序问题了。
这么做为什么是对的呢,其实是可以证明的。
首先我们要证明这要构造出来的序列是合法的,那么对于\(i,j(i<j)\)来说
这个的意思是说\(i\)和\(j\)之间要删除的数是少于\(i\)与\(j\)之间的位置差,而这个证明是显而易见的。
同时我们还要满足删去\(j\)之后,总共删除的数是比\(k\)大的,于是就有
而这个也是显然成立的
所以这样构造出来的序列是一定合法的。
而用二维偏序来做相当于最长上升子序列也一定满足它是最优的,所以这样做出来一定是正确答案。
view code
#include<bits/stdc++.h>
#define lowbit(x) (x&-x)
#define in read()
using namespace std;
inline int read()
{
int data=0,w=1; char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=5e5+10;
int n,m,maxx,c[N],cnt;
struct node{int id,w;}a[N];
inline void update(int k,int val){for(;k<=maxx;k+=lowbit(k)) c[k]=max(c[k],val);}
inline int query(int k){int ret=0;for(;k;k-=lowbit(k)) ret=max(ret,c[k]);return ret;}
inline bool cmp(node a,node b)
{
if(a.id-a.w==b.id-b.w) return a.w<b.w;
return a.id-a.w<b.id-b.w;
}
int main()
{
n=in,m=in;
for(int i=1;i<=n;i++)
{
int val=in;
if(val>=i-m&&val<=n-m&&val<=i)
a[++cnt]=(node){i,val},maxx=max(maxx,a[cnt].w);
}
sort(a+1,a+cnt+1,cmp);
int ans=0;
for(int i=1;i<=cnt;i++)
{
int tmp=query(a[i].w-1)+1;
// cout<<1<<'\n';
if(n-a[i].w>=m) ans=max(ans,tmp);
update(a[i].w,tmp);
// cout<<2<<'\n';
}
write(ans);
return 0;
}

浙公网安备 33010602011771号