zoukankan      html  css  js  c++  java
  • 10月21日考试 题解(数学+二分答案+动态规划+Tarjan+倍增)

    T1 math

    题目大意:求$sumlimits_{i=1}^n (-1)^{sumlimits_{j=1}^m d(i imes j)}$,其中$d(i)$表示$i$的因数个数。$nleq 10^7,mleq 10^{14}$。

    容易想到我们只需要看幂的奇偶即可。发现只有当$i imes j$是完全平方数时因数个数为奇数,其余情况均为偶数。于是我们可以求值域内完全平方数个数。

    考虑将$i$化成$p imes q^2$的形式,$j$化成$p imes k^2$的形式。$k$即为值域内完全平方数的个数。对于每个$i$求$p$的复杂度是$O(nsqrt n)$的;加速求$p$可以使用线性筛,时间复杂度为$O(n)$。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #define int long long
    using namespace std;
    const int N=10000005;
    int n,m,vis[N],ans;
    signed main()
    {
        cin>>n>>m;
        for (int i=1;i<=n;i++)
        {
            if (vis[i]) continue;
            int q=sqrt(n/i),k=sqrt(m/i);
            if (k%2) ans-=q;
            else ans+=q;
            for (int j=1;j<=q;j++) 
                vis[i*j*j]=1;
        }
        cout<<ans;
        return 0;
    }

    T2 osu

    题目大意:二维平面上会依次出现$n$个坐标为$(x_i,y_i)$的点,每个点出现的时间为$t_i$。人一开始在$(0,0)$。只有当人恰好在$t_i$时刻经过$(x_i,y_i)$才算经过一点。问为使人经过不少于$k$个点的速度最大值的最小值。设$ans=frac{asqrt b}{c}$,请输出$a,b,c$的值。$nleq 2000$。

    最大值最小不难想到二分答案;因为$n$很小,所以check的时候可以考虑dp。时间复杂度$O(n^2 log n)$。

    关于$a,b,c$的处理可以看代码。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #define sqr(x) ((x)*(x))
    using namespace std;
    const int N=2005;
    const double eps=1e-4;
    double ans,dis[N][N],l,r;
    int p,q,f[N],g[N],n,k,a,b,c;
    int t[N],x[N],y[N];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline int gcd(int x,int y){
        return y==0?x:gcd(y,x%y);
    }
    inline bool check(double mid)
    {
        ans=0;
        for (int i=0;i<=n;i++) f[i]=0;
        for (int i=1;i<=n;i++)
        {
            for (int j=0;j<i;j++)
                if (dis[j][i]<=mid&&f[j]+1>f[i])
                    f[i]=f[j]+1,g[i]=j;
            if (f[i]>=k)
            {
                for (int x=i;g[x];x=g[x])
                    if (dis[g[x]][x]>ans)
                        ans=dis[g[x]][x],p=g[x],q=x;
                return 1;
            }
        }
        return 0;
    }
    int main()
    {
        n=read();k=read();
        for (int i=1;i<=n;i++)
            t[i]=read(),x[i]=read(),y[i]=read();
        for (int i=0;i<n;i++)
            for (int j=i+1;j<=n;j++)
            {
                dis[i][j]=sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j]))*1.0/(t[j]-t[i]);
                r=max(r,dis[i][j]);
            }
        r+=eps;
        while(r-l>eps)
        {
            double mid=(l+r)/2.0;
            if (check(mid)) r=mid;
            else l=mid;
        }
        a=1;b=sqr(x[p]-x[q])+sqr(y[p]-y[q]);c=t[q]-t[p];
        for (int i=2;i<=sqrt(b);i++)
            while(!(b%(i*i))) a*=i,b/=i*i;
        int k=gcd(a,c);
        printf("%d %d %d",a/k,b,c/k);
        return 0;
    }

    T3 map

    题目大意:给定一张含$n$个点$m$条边的无向图。定义一个好点对$(a,b)$指$a$到$b$至少有两条不相交路径。$q$次询问,每次新增加一边$(x,y)$,问新增加的好点对的个数之和。每次询问独立。$n,qleq 2 imes 10^5,mleq 4 imes 10^5$。

    首先对图缩点,这样图就变成了一棵树。每次新增加一边$(x,y)$,这样树上就会出现一个环,我们考虑这个环对答案的贡献。可以发现如果两个点在加入边之前不在一个环中,但是加边之后在一个环中,那么这个点对就对答案有贡献;而先前就在一个环中的点对对答案是没有贡献的。我们可以考虑总的点对个数减去本身已经在一个环内的点对个数。倍增可以在$log n$时间内解决问题。时间复杂度$O(qlog n)$。

    代码:

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #define int long long
    using namespace std;
    const int N=400005;
    int n,m,q,x[N*2],y[N*2],ans;
    int size[N],dfn[N],low[N],co[N],st[N],tot,tt,top;
    int fa[N][21],sum[N],qsum[N],dep[N];
    int head[N],Head[N],Cnt,cnt;
    vector<int> v[N];
    struct node{
        int next,to;
    }edge[N*4],Edge[N*4];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void add(int from,int to)
    {
        edge[++cnt]=(node){head[from],to};
        head[from]=cnt;
    }
    inline void Add(int from,int to)
    {
        Edge[++Cnt]=(node){Head[from],to};
        Head[from]=Cnt;
    }
    inline void tarjan(int now,int f)
    {
        dfn[now]=low[now]=++tot;
        st[++top]=now;
        for (int i=Head[now];i;i=Edge[i].next)
        {
            int to=Edge[i].to;
            if ((i%2==0&&f==i-1)||(i%2==1&&f==i+1)) continue;
            if (!dfn[to]) tarjan(to,i),low[now]=min(low[now],low[to]);
            else if (!co[to]) low[now]=min(low[now],dfn[to]);
        }
        if (low[now]==dfn[now])
        {
            tt++;
            while(st[top]!=now)
                size[tt]++,co[st[top--]]=tt;
            size[tt]++;co[st[top--]]=tt;
        }
    }
    inline void dfs(int now,int f)
    {
        dep[now]=dep[f]+1;fa[now][0]=f;
        sum[now]=sum[f]+size[now];
        qsum[now]=qsum[f]+size[now]*(size[now]-1)/2;
        for (int i=1;i<=19;i++)
            fa[now][i]=fa[fa[now][i-1]][i-1];
        for (int i=head[now];i;i=edge[i].next)
            if (edge[i].to!=f) dfs(edge[i].to,now);
    }
    inline int LCA(int x,int y)
    {
        if (dep[x]<dep[y]) swap(x,y);
        for (int i=19;i>=0;i--)
            if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
        if (x==y) return x;
        for (int i=19;i>=0;i--)
            if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
        return fa[x][0];
    }
    signed main()
    {
        n=read();m=read();q=read();
        for (int i=1;i<=m;i++)
        {
            x[i]=read();y[i]=read();
            Add(x[i],y[i]);Add(y[i],x[i]);
        }
        tarjan(1,0);
        for (int i=1;i<=m;i++)
        {
            int xx=co[x[i]],yy=co[y[i]];
            if (xx!=yy) add(xx,yy),add(yy,xx);
        }
        dfs(co[1],0);
        while(q--)
        {
            int a=read(),b=read();
            a=co[a],b=co[b];
            int l=LCA(a,b);
            int s=sum[a]+sum[b]-2*sum[l]+size[l];
            int ss=qsum[a]+qsum[b]-2*qsum[l]+size[l]*(size[l]-1)/2;
            ans+=s*(s-1)/2-ss;
        }
        printf("%lld",ans*2);
        return 0;
    }

    T4 circular

    题目大意:在一个长度为$m$的环上有$n$条线段$[l_i,r_i]$。问最多能选取多少个不相交的线段。

    相似的做法:[[SCOI2015]国旗计划](https://www.luogu.com.cn/problem/P4155)

    首先破环成链。先考虑暴力的做法:每次选取起始线段$i$,然后不停跳合法的$nxt$,直到不能跳为止;然后取最大值。而我们可以优化跳$nxt$的过程,显然倍增可以将复杂度由$n^2$降到$nlog^2 n$。

    代码:

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    const int N=200005;
    const int inf=0x3f3f3f3f;
    int n,m,f[N][20],st[N],vis[N],top,cnt,ans;
    struct node{
        int l,r;
    }s[N],q[N];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    bool cmp(node x,node y){
        return x.l==y.l?x.r<y.r:x.l<y.l;
    }
    inline bool check(int x,int i)
    {
        int p=i;
        for (int j=0;j<=18;j++)
            if ((x>>j)&1) p=f[p][j];
        return q[i].l+m>=q[p].r;
    }
    int main()
    {
        m=read();n=read();
        for (int i=1;i<=n;i++)
        {
            s[i].l=read();s[i].r=read();
            if (s[i].r<s[i].l) s[i].r+=m;
        }
        for (int i=1;i<=n;i++)
            s[i+n].l=s[i].l+m,s[i+n].r=s[i].r+m;
        n*=2;
        sort(s+1,s+n+1,cmp);
        for (int i=1;i<=n;i++){
            while(top&&s[st[top]].r>=s[i].r) vis[st[top--]]=1;
            st[++top]=i;
        }
        for (int i=1;i<=n;i++)
            if (!vis[i]) q[++cnt]=s[i];
        int p=1;q[cnt+1].l=inf;q[0].r=inf;
        for (int i=1;i<=cnt;i++)
        {
            while(p<=cnt&&q[p].l<q[i].r) p++;
            if (p>cnt) break;
            f[i][0]=p;
        }
        for (int j=1;j<=18;j++)
            for (int i=1;i<=cnt;i++)
                f[i][j]=f[f[i][j-1]][j-1];
        for (int i=1;i<=cnt;i++)
        {
            int l=0,r=n/2,p;
            while(l<=r)
            {
                int mid=(l+r)>>1;
                if (check(mid,i)) p=mid,l=mid+1;
                else r=mid-1;
            }
            ans=max(ans,p+1);
        }
        printf("%d",ans);
        return 0;
    }
  • 相关阅读:
    linux系统/var/log目录下的信息详解
    SQL 显示表名显示列名
    P2P平台介绍
    outlook署名最后一行没换行
    CSS3下的渐变文字效果实现
    SSH(poderosa)を使って、さくらのMySQLサーバーに接続する方法
    内网IP外网IP的关联及访问互联网原理
    自己吃的哑巴亏,怎么也要吞下去
    解决Ehcache缓存警告问题
    管理的艺术
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13859824.html
Copyright © 2011-2022 走看看