3-27省选集训

前言

A. 理塘之王

image
image
image
image

官方题解:
image

考场上推了个性质:以每个点为根的答案即该点子树中到根路径上的点的值均\(\leq\)该点值的点个数
考虑证明:image

假设当前有个点 \(z\)\(gfa\) 外部
如果\(val[gfa]>val[v]>val[u]>val[fa]\)
说明正常从\(z\)无法直接到达\(u,v\)
那么可能会有疑问,如果\(z\)能到达\(y\),再从\(y\)中转即可
那么需要满足\(val[y]>val[gfa]\),那如果再从\(y\)\(u,v\)
显然不满足\(val[y]<val[u/v]\)
因此\(u,v\)是无法被访问的,不会产生贡献
然后可以发现,剩下的我们按照每次走能走的点里面尽可能小的
就可以全部走一遍
答案即抛出无法访问的点,剩下的点的个数

结合部分分可以拿到60
考场上一直在想这个问题能不能优化,然后想了一下主席树线段树合并之类的
觉得实现特别麻烦,而且貌似带修
考场还挂了15(嘤嘤嘤

T1 60pts
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<bitset>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=5e5+5;
inline int read()
{
    int x=0,y=1; char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*y;
}
int T,n;
struct edge
{
    int to,next,c;
}g[maxn<<2];
int head[maxn],cnt(0);
inline void add(int a,int b)
{
    g[++cnt].to=b;
    g[cnt].next=head[a];
    head[a]=cnt;
    return ;
}
int top[maxn],son[maxn],fa[maxn];
int dep[maxn],size[maxn];
int seg[maxn],rev[maxn],t(0);
void dfs(int u,int f)
{
    size[u]=1; fa[u]=f;
    dep[u]=dep[f]+1;
    for(int i=head[u];i;i=g[i].next)
    {
        int v=g[i].to;
        if(v==f) continue;
        dfs(v,u);
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
    return ;
}
void dfs2(int u,int p)
{
    top[u]=p;
    seg[u]=++t; rev[t]=u;
    if(son[u]) dfs2(son[u],p);
    for(int i=head[u];i;i=g[i].next)
    {
        int v=g[i].to;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
    return ;
}
int val[maxn];
int f[maxn][25],Log[maxn];
int query(int l,int r)
{
    int k=Log[r-l+1];
    return max(f[l][k],f[r-(1<<k)+1][k]);
}
int ask(int x,int y)
{
    int ret=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ret=max(ret,query(seg[top[x]],seg[x]));
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    ret=max(ret,query(seg[x],seg[y]));
    return ret;
}
int root(0);
int b[maxn],ans(0);
void dfs3(int u,int f)
{
    if(ask(root,u)<=val[u]) ans++;
    for(int i=head[u];i;i=g[i].next)
    {
        int v=g[i].to;
        if(v==f) continue;
        dfs3(v,u);
    }
    return ;
}
void deal_30()
{
    for(int i=1;i<=n;i++)
    {ans=0; root=i; dfs3(i,0); printf("%d ",ans);}
    printf("\n");
    return ;
}
int st[maxn],tail(0);
int lef[maxn],rig[maxn],sum[maxn];
void deal_15()
{
    tail=0;
    for(int i=1;i<=n;i++)
    {
        while(tail&&val[i]>val[st[tail]])
        {rig[st[tail]]=i; tail--;}
        st[++tail]=i;
    }
    for(int i=1;i<=n;i++)
        if(!rig[i]) rig[i]=n+1;
    tail=0;
    for(int i=n;i>=1;i--)
    {
        while(tail&&val[i]>val[st[tail]])
        {lef[st[tail]]=i; tail--;}
        st[++tail]=i;
    }
    for(int i=1;i<=n;i++)
    {sum[lef[i]+1]++; sum[rig[i]]--;}
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for(int i=1;i<=n;i++)
        printf("%d ",sum[i]);
    printf("\n");
    return ;
}
int is[maxn],vec[maxn];
void deal_10()
{
    for(int i=1;i<=n;i++) is[i]=0;
    int tot(0);
    for(int i=1;i<=n;i++)
        if(ask(1,i)<=val[i]) is[i]=1,tot++;
    printf("%d ",tot);
    vec[0]=0;
    for(int i=1;i<=n;i++)
        if(is[i]) vec[++vec[0]]=val[i];
    sort(vec+1,vec+1+vec[0]);
    for(int i=2;i<=n;i++)
    {
        int pos=lower_bound(vec+1,vec+1+vec[0],val[i])-vec;
        printf("%d ",tot-is[i]+1-(pos-1));
    }
    printf("\n");
}
int main()
{
    freopen("ltc.in","r",stdin);
    freopen("ltc.out","w",stdout);
    T=read();
    Log[0]=-1;
    for(int i=1;i<=5e5;i++) Log[i]=Log[i>>1]+1;
    while(T--)
    {
        n=read();
        cnt=0; t=0;
        for(int i=1;i<=n;i++) head[i]=0;
        for(int i=1;i<=n;i++)
        {
            son[i]=top[i]=dep[i]=0;
            fa[i]=seg[i]=rev[i]=0;
            f[i][0]=0; lef[i]=rig[i]=0; sum[i]=0;
        }
        bool flag=1;
        for(int i=1;i<n;i++)
        {
            int u,v;
            u=read(); v=read();
            if(v!=u+1) flag=0;
            add(u,v); add(v,u);
        }
        dfs(1,0); dfs2(1,1);
        for(int i=1;i<=n;i++) b[i]=val[i]=read();
        sort(b+1,b+1+n);
        for(int i=1;i<=n;i++)
            val[i]=lower_bound(b+1,b+1+n,val[i])-b;
        for(int i=1;i<=n;i++) f[i][0]=val[rev[i]];
        for(int j=1;(1<<j)<=n;j++)
            for(int i=1;i+(1<<j)-1<=n;i++)
                f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
        if(flag) deal_15();
        else if(n<=5000) deal_30();
        else deal_10();
    }
    return 0;
}

然后我们考虑正解,题解写的很清楚了,实现只要倒着做很简单

然后我就在想这两个性质有没有相似性
然后发现我一开始的想法里面有一句话:走能走的里面最小的
对应了题解里当前最大值向连通块里面最大值连边,这样可以每次尽可能少的缩小最大值
感觉还是思维狭隘了
应该用处理最短路加边的那种思想来搞
不多说了

T1 accept
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<bitset>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=5e5+5;
inline int read()
{
    int x=0,y=1; char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*y;
}
int T,n;
vector<int> g[maxn],g2[maxn];
int fa[maxn],id[maxn];
int val[maxn],dep[maxn];
bool cmp(int a,int b) {return val[a]<val[b];}
int find(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
    g2[x].push_back(find(y));
    fa[find(y)]=x;
    return ;
}
void dfs(int u,int p)
{
    dep[u]=dep[p]+1;
    for(int v:g2[u]) dfs(v,u);
    return ;
}
void solve()
{
    n=read();
    for(int i=1;i<=n;i++)
        g[i].clear(),g2[i].clear(),fa[i]=id[i]=i;
    for(int i=1;i<n;i++)
    {
        int u,v;
        u=read(); v=read();
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i=1;i<=n;i++) val[i]=read();
    sort(id+1,id+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        int u=id[i];
        for(int v:g[u]) if(val[v]<val[u]) merge(u,v);
    }
    dfs(id[n],0);
    for(int i=1;i<=n;i++) printf("%d ",dep[i]);
    printf("\n");
    return ;
}
int main()
{
    //freopen("ltc.in","r",stdin);
    //freopen("ltc.out","w",stdout);
    T=read();
    while(T--) solve();
    return 0;
}

B. 测你们码

image
image
image
image

官方题解:
image

几个要点说一下:
首先,题解里在一个排列里面的答案是\((n-m+1)m!(n-m)!\)
我们可以这样理解,相当于我们在\(n\)长度的区间里面任取开头,然后添入连续的\(m\)长度的排列,剩下的部分填\(n-m\)个数构成\(n\)长度排列
其次,关于这个的理解需要说一下
image
首先我们看图
image
我们想要把CD拼成长为\(m\)的排列
我们现在假设C中元素已经是\(\leq m\)的了
想要证明此时A中元素也必定\(\leq m\),从而证明A+C是\(m\)的某个排列
然后证明A=D,就可以证明A+D也为\(m\)的某个排列

我们发现,假设如果A里面有个位置\(j\)\(j\)上的数是大于\(m\)
image

那么按照下面这样的方法,我们可以构造出下一个\(n\)长度排列
image

容易发现,想要A+C为\(m\)的某个排列,需要里面的数都\(<m\),我们想要D中的数\(<m\),这样必须把\(j\)换成一个\(\leq m\)的数
由于现在C中的数都是\(\leq m\)的,那么我们只能从B中通过排序找到一个\(\leq m\)的数来替换\(j\),此时需要单调递减的区间为\([j+1,n]\),如下image
由于我们找的是\([j+1,n]\)里面第一个大于\(j\)位置上数的数,那么交换之后,\(j\)位置上的数只能更大
想要令其\(\leq m\)只能考虑排序,那么比\(j\)位置上的数大的数只能是\([j+1,n]\)里面最大的数

由于\([j+1,n]\)是单调递减的,那么排序后一定保证\([j+1,i]\)里面存在C中的数

这样调整之后,得到的即为D
那么C和D就会有公共的数,就不会构成排列了
因此矛盾
证明A里面必须是\(\leq m\)
然后考虑我们需要A=D

只有一种特殊情况需要考虑:image
由于B中元素都是\(>m\)的,不会存在A中某个位置\(j\)\(j<i\),然后\([j,n]\)构成递减序列的,唯一一种情况就是\(j=i\)时,如果B+C恰好构成了单调递减的情况
会导致把\(j\)位置上的数换出去,这样得到的前\(i\)个数就不是原来的前\(i\)个数了,第\(i\)个数会出现不同
那么,只有B+C构成单调递减时,D\(\neq\)A
那么,这样D\(\neq\)A的方案即为,选出\(i\)个数作为A,可以随便排列,剩下的数从大到小放进B和C中
方案数\(C_m^i\times i!\),题解中的写法算出来也是一样的
然后统计就简单了

posted @ 2023-03-27 14:50  Mastey  阅读(3)  评论(0)    收藏  举报