10.26模拟赛

预感最近的模拟赛都是CF重组场。

T1

考场思路:

纯暴力模拟。

先求出所有点到\(0\)号点的路径上经过的节点。

然后再转化为两个点之间的路径(0号点到左端点和0号点到左端点减去0号点到lca),记录哪些点在路径上,对于每个路径求出mex。

最后统计mex的贡献。

瓶颈在于求解mex需要依赖于求出具体每条路径经过了哪些节点。

时间复杂度应该在\(O(n^2logn)\)左右,应该可以拿30分,但是实际上挂到了5分。

正解思路:

一条路径的mex为\(k\)的充要条件为:\([0,k-1]\)在这条路径上都出现了,但是\(k\)没有出现。

维护\(k\)在一条路径上没有出现是不好维护的,考虑容斥出来用\(mex\ge k\)的路径条数,减去\(mex\ge k+1\)的路径条数,即为\(mex\)\(k\)的路径条数。

现在问题转化为了求\(mex\)\([0,n-1]\)的路径条数。

考虑依次枚举每个点。

如果当前枚举的点是\(k\)\([0,k-1]\)都在一条链上,链上的端点为\(st\)\(ed\)

如果\(k\)也在这条链上,那么它有可能成为链的端点。

如果\(st\)\(ed\)的lca不为其中一个,说明此时链不被算在st或ed的子树中(类似一下这种情况)。那么路径条数为\(siz[st]\times siz[ed]\)

image

从st的子树(不包含当前链)中的任一节点到ed的子树(不包含当前链)中的任一节点都经过了当前链,这些路径的mex一定大于等于\(k\)

如果\(st\)\(ed\)的lca为其中一个,说明此时链被算在st或ed的其中一个子树中(类似于以下这种情况)。

image

此时的路径条数为\(siz[st]\times(n-siz[ed'])\)

如果\(k\)不在这条链上,那么\([k,n]\)之间的\(mex\)一定为\([0,k-1]\)之间的数,而\(mex\)\([0,k-1]\)的路径数已经统计过了,此时就可以跳出了。

code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
int const maxe=400101;
int const maxn=200101;
int const Log=19;
int t,n;
struct Edge{
    int nxt,to;
}e[maxe];
int head[maxn],tot;
int dep[maxn],f[maxn][Log+1];
int siz[maxn];
void addedge(int x,int y){
    e[++tot].nxt=head[x];
    head[x]=tot;
    e[tot].to=y;
}
void dfs(int x,int fa){
    dep[x]=dep[fa]+1;siz[x]=1;
    f[x][0]=fa;
    for(int k=1;k<=Log;k++)
        f[x][k]=f[f[x][k-1]][k-1];
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        dfs(y,x);
        siz[x]+=siz[y];
    }
}
int get_lca(int x,int y){
    if(dep[x]>dep[y]) swap(x,y);
    for(int i=Log;i>=0;i--)
        if(dep[f[y][i]]>=dep[x])
            y=f[y][i];
    if(x==y) return x;
    for(int i=Log;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
int dist(int x,int y){
    int lca=get_lca(x,y);
    return dep[x]+dep[y]-2*dep[lca];
}
LL ans[maxn];
int main(){
    scanf("%d",&t);
    while(t--){
        memset(head,0,sizeof(head));tot=0;
        memset(ans,0ll,sizeof(ans));
        scanf("%d",&n);
        for(int i=1;i<=n-1;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            addedge(x,y);addedge(y,x);
        }
        dfs(0,0);
        ans[0]=1ll*(n)*(n-1);ans[0]>>=1ll;

        for(int i=head[0];i;i=e[i].nxt){
            int y=e[i].to;
            ans[1]+=1ll*siz[y]*(n-siz[y]);
        }
        ans[1]+=1ll*(siz[0]-1)*(n-siz[0]+1);ans[1]>>=1ll;

        int st=0,ed=0;
        for(int i=1;i<n;i++){
            int len1=dist(st,ed),len2=dist(st,i)+dist(i,ed);
            if(len1!=len2){
                if(len2-len1==2*dist(i,st)) st=i;
                else if(len2-len1==2*dist(i,ed)) ed=i;
                else break;
            }
            int lca=get_lca(st,ed);
            if((st!=lca)&&(lca!=ed)) ans[i+1]=1ll*siz[st]*siz[ed];
            else{
                int x=st,y=ed;
                if(dep[x]<dep[y])swap(x,y);
                ans[i+1]=1ll*siz[x];
                for(int k=Log;k>=0;k--)
                    if(dep[f[x][k]]>dep[y])
                        x=f[x][k];
                ans[i+1]*=1ll*(n-siz[x]);
            }
        }
        for(int i=0;i<n;i++) ans[i]-=ans[i+1];
        for(int i=0;i<=n;i++)
            printf("%lld ",ans[i]);
        puts("");
    }
    return 0;
}


T2

考场思路:

看见完全平方数就奔着数学去了…最后想到了DP,但其实我看出来了也不会写转移嘿……最后\(k=0\)的简单贪心分也没拿到QwQ。

正解思路:

其实是个DP。

DP方程需要啥设啥。

状态表示:\(dp[i][j]\)表示当前划分到第\(i\)个数,做了\(j\)次修改后的最少划分段数。

是个区间划分问题,最基础的转移是枚举最后一段的起点进行转移,时间复杂度至少是\(O(n^2)\)级别的。

考虑优化。

观察到当修改次数为定值时,最少划分段数一定是与划分的数成正相关的。也就是说当dp方程的第二维为定值时,dp方程第一维增长,dp方程的值一定是一个单调不下降序列。

基于这样的性质,只要找到尽可能靠前的合法位置进行转移。

dp方程在第二维固定,第一维增长时,方程的值是个定值。尽可能靠前一定是最优的,方程的值不变。

可以用尺取法预处理出来一个\(l\)数组,其中\(l[i][j]\)表示最小的\(pos\)使得\([pos,i]\)作为一个整段消耗了\(j\)次修改。

code
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int const maxn=200101;
int const maxa=10000101;
int const maxk=23;
int t;
int n,k;
int a[maxn];
int vis[maxa];
int l[maxn][maxk],f[maxn][maxk];
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            int tmp=a[i];a[i]=1;
            for(int j=2;j*j<=tmp;j++){
                if(tmp%j==0){
                    int cnt=0;
                    while(tmp%j==0) tmp/=j,++cnt;
                    if(cnt&1) a[i]*=j;
                }
            }
            if(tmp>1) a[i]*=tmp;
        }
        for(int lim=0;lim<=k;lim++){
            int cnt=0;
            for(int i=1,j=1;i<=n;i++){
                vis[a[i]]++;
                if(vis[a[i]]>=2) ++cnt;
                if(cnt>lim){
                    while(cnt>lim){
                        if(vis[a[j]]>=2) --cnt;
                        vis[a[j]]--;
                        j++;
                    }
                }
                l[i][lim]=j;
            }
            for(int i=1;i<=n;i++)
                vis[a[i]]=0;
        }
        f[0][0]=0;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=k;j++){
                for(int x=0;x<=j;x++)
                    f[i][j]=min(f[i][j],f[l[i][x]-1][j-x]+1);
            }
        }
        int ans=0x7f7f7f7f;
        for(int i=0;i<=k;i++)
            ans=min(ans,f[n][i]);
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2021-10-26 19:40  RapunzelOnly  阅读(42)  评论(0)    收藏  举报