10月21日考试 题解(数学+二分答案+动态规划+Tarjan+倍增)

T1 math

题目大意:求$\sum\limits_{i=1}^n (-1)^{\sum\limits_{j=1}^m d(i\times j)}$,其中$d(i)$表示$i$的因数个数。$n\leq 10^7,m\leq 10^{14}$。

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

考虑将$i$化成$p\times q^2$的形式,$j$化成$p\times k^2$的形式。$k$即为值域内完全平方数的个数。对于每个$i$求$p$的复杂度是$O(n\sqrt 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{a\sqrt b}{c}$,请输出$a,b,c$的值。$n\leq 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,q\leq 2\times 10^5,m\leq 4\times 10^5$。

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

 

上一篇:tarjan找有向图强连通分量


下一篇:图论--割边--Tarjan模板