杭二集训 2019.8.16

Posted nldqy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了杭二集训 2019.8.16相关的知识,希望对你有一定的参考价值。

T1

题目意思:给定一个01串,你可以进行区间异或操作,最少用几次能让这个串完全相同

数据范围:\(n\le 1e7\)

Solution:

\(f[i]\)表示全变成1的最小操作数,\(g[i]\)表示全变成0,输出min值

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+1;
int n,f[N],g[N];char s[N];
int main()
    scanf("%s",s+1);n=strlen(s+1);
    s[1]=='0'?f[1]=1:g[1]=1;
    for(int i=2;i<=n;i++)
        f[i]=f[i-1],g[i]=g[i-1];
        if(s[i]!=s[i-1])
            s[i]=='0'?f[i]++:g[i]++;
        f[i]=min(f[i],g[i]+1);
        g[i]=min(g[i],f[i]+1);
    return printf("%d\n",min(f[n],g[n])),0;

T2

题目意思:给定长度为\(n\)的一个数列,求\(\sum_i=1^n\sum_j=1^n lcm(a_i,a_j)\)

数据范围:\(n\le1e6,a_i\le1e6\)

Solution:

\[ \sum_i=1^n\sum_j=1^na_i\times a_j \over gcd(a_i,a_j)\d=gcd(a_i,a_j)\\sum_i=1^n\sum_j=1^na_i\times a_j \over d\M=max \,a_i\\sum_d=1^M \sum_d|a_i\sum_d|a_j[gcd(a_i \over d,a_j \over d)=1] a_i\times a_j \D=gcd(a_i \over d,a_j \over d)\\sum_d=1^M \sum_d|a_i\sum_d|a_j \sum_t|D \mu(t) a_i\times a_j\\]

\[ \sum_d=1^M \sum_t=1^M \mu(t)\sum_d|a_i\sum_d|a_j\sum_t|a_i \over d\sum_t|a_j\over d a_i\times a_j \over d\\sum_d=1^M \sum_t=1^M \mu(t)\sum_d\times t|a_i\sum_d \times t|a_ja_i\times a_j \over d\\T=a_i\times a_j\\sum_T=1^M\sum_t|T \mu(t) t\sum_T|a_i\sum_T|a_j a_i \times a_j \over T\\]

推式子就到此为止了(并没有),考虑如何预处理出后面的两个式子,设\(v_i\)表示\(i\)的出现次数
\[ \sum_T|a_i\sum_T|a_ja_i\times a_j \over T\T(\sum_k=1^M v_kT\times k)^2\\]
于是后面的式子可以做到\(O(n \, log \, n)\)预处理,前一个式子则在线筛中处理

Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+1;
const int p=998244353;
int M,n,a[N],mu[N],v[N];
int tot,pri[N],vis[N];
long long ans,tmp,f[N],g[N],sum[N];
void prepare()mu[1]=1;f[1]=1;
    for(int i=2;i<=M;i++)
        if(!vis[i]) pri[++tot]=i,mu[i]=-1,f[i]=1-i;
        for(int j=1;j<=tot&&i*pri[j]<=M;j++)
            vis[i*pri[j]]=1;
            if(i%pri[j]==0)
                int w=pri[j],x=i,u=w;
                while(x%w==0)x/=w;u*=w;
                if(x==1) f[u*x]=(f[u/w]+(mu[u]*1ll*u)%p)%p;
                else f[u*x]=(f[u]*1ll*f[x])%p;
                break;
            
            f[i*pri[j]]=f[i]*1ll*f[pri[j]];
            f[i*pri[j]]%=p;mu[i*pri[j]]=-mu[i];
        
    
    for(int i=1;i<=M;i++)
        long long re=0;
        for(int j=1;j<=M/i;j++)
            re+=j*1ll*v[i*j],re%=p;
        g[i]=i*((re*1ll*re)%p);
    

int read()
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch))if(ch=='-')f=-f;ch=getchar();
    while(isdigit(ch))x=x*10+ch-48;ch=getchar();
    return x*f;

int gcd(int x,int y)
    return !y?x:gcd(y,x%y);

long long lcm(int x,int y)
    long long re=x*1ll*y;
    return re/gcd(x,y);

signed main()
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),v[a[i]]++,M=max(M,a[i]);
    prepare();
    for(int i=1;i<=M;i++)
        ans+=f[i]*1ll*g[i],ans%=p;
        ans=(ans+p)%p;
    
    printf("%lld\n",ans);
    return 0;

T3

题目意思:给你一棵n个点n-1条边的树,每个点i上有一个可正可负的整数权值ai。我们定义树上一个连通块的权值为其中所有点的权值和。有q次询问,每次询问给出k个点(保证包含1号点),请你告诉他所有包含这k个点的联通块中,权值最大的连通块权值是多少

Solution:

\(f[x]\)表示\(x\)的子树内,包含\(x\)点的最大连通块权值和

那么每次询问的答案可以表示为:\(f[1]+\sum_i\in V[f[i]<0]\,\,f[i]\)

其中集合\(V\)表示这\(k\)个点构成联通块所需的最少的点的点集

这个其实很好理解,考虑\(f[x]\)的转移:\(f[x]=\sum_i\in son_x [f[i]>0]\,\,f[i]\)

也就是说,对于所有\(f[x]>0\)的点来说,他们的贡献都是加入到了答案里的

那么我们只需要考虑如何快速的算出上面的式子就行了

考虑\(g[x]\)\(g[x]=\sum_v\in up_x [f[v]<0]\,\,f[v]\),其中\(up_x\)表示从\(x\)到根节点需要经过的所有点的集合(包过\(x\),不包过根)

那么我们只需要用set维护dfs序,再求lca维护\(g\)值就OK了(维护参考异象石)

Code:

#include<bits/stdc++.h>
#define IT set<int,Pos>:: iterator
using namespace std;
const int N=1e5+1;
int n,q,cnt,tim,head[N];
int dep[N],a[N],p[22][N],dfn[N];
long long f[N],g[N];
struct Edgeint nxt,to;edge[N<<1];
struct Pos
    bool operator () (int a,int b)return dfn[a]<dfn[b];
;set<int,Pos> s;
void ins(int x,int y)
    edge[++cnt].nxt=head[x];
    edge[cnt].to=y;head[x]=cnt;

void dfs1(int x,int fa)
    dfn[x]=++tim;
    dep[x]=dep[fa]+1;p[0][x]=fa;
    for(int i=head[x];i;i=edge[i].nxt)
        int y=edge[i].to;
        if(y==fa) continue;
        dfs1(y,x);if(f[y]>0) f[x]+=f[y];
    f[x]+=a[x];

void trans()
    for(int i=1;i<=log2(n)+1;i++)
        for(int j=1;j<=n;j++)
            p[i][j]=p[i-1][p[i-1][j]];

int lca(int x,int y)
    if(x==1||x==y) return x;
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=log2(n)+1;i>=0;i--)
        if(dep[p[i][x]]>=dep[y]) x=p[i][x];
    if(x==y) return x;
    for(int i=log2(n)+1;i>=0;i--)
        if(p[i][x]!=p[i][y]) x=p[i][x],y=p[i][y];
    return p[0][x];

void dfs2(int x,int fa)
    g[x]=g[fa];if(f[x]<0) g[x]+=f[x];
    for(int i=head[x];i;i=edge[i].nxt)
        int y=edge[i].to;
        if(y==fa) continue;
        dfs2(y,x);
    

int read()
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch))if(ch=='-')f=-f;ch=getchar();
    while(isdigit(ch))x=x*10+ch-48;ch=getchar();
    return x*f;

int main()
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<n;i++)
        int x=read(),y=read();
        ins(x,y),ins(y,x);
    
    dfs1(1,0);trans();dfs2(1,0);
    q=read();
    for(int i=1;i<=q;i++)
        int k=read();s.clear();
        for(int j=1;j<=k;j++)
            int x=read();
            s.insert(x);
        
        IT it=s.begin();int lst=*it;
        long long ans=0;
        while(it!=s.end())++it;
            if(it==s.end())
                ans+=g[lst];
                break;
            int x=*it;
            ans+=g[x]+g[lst]-2*g[lca(x,lst)];
            lst=x;
        printf("%lld\n",(ans/2)+f[1]);
    
    return 0;

以上是关于杭二集训 2019.8.16的主要内容,如果未能解决你的问题,请参考以下文章

OI学习过程记录

CSU OJ2151 集训难度

「集训」2020集训记录

9月半集训总结 & 10月集训规划

2021软件创新实验室暑假集训总结篇

2021软件创新实验室暑假集训总结篇