群赛 round#6 解题报告 (math,cover,monogatari)

本文提供了群赛Round#6的解题报告,详细介绍了三道题目的解题思路与代码实现,包括数学题原根的求解、道路覆盖问题的二分+动态规划方法,以及物语问题中使用SPFA算法进行最短路径计算。

群赛 round#6 解题报告

赛制: OI 难度: noip
得分: math 90/100
cover 0/100
monogatari 0/100

T1 原根(math)

这里写图片描述
原根是一个数学概念,不知道的可以参见百度百科:原根
这道题我首先预处理出指数和n的欧拉函数值,然后根据n=1,2,4,p,2p,p^n(p为奇质数,n∈N*)来初步判断n是否有原根.之后,利用phi判断(2~n-1)^i%n是否会等于1,如果都符合条件的话,求出原根并输出.不过特别地,phi(1)=1,所以1的原根是1,我因为这一点wa了一个点.
代码如下:

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<vector>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
bool f[10005];
vector<int> g;
int v[10005],n,id;
int fi(int x)
{
    if(f[x])return x-1;
    int ans=x;
    for(int i=2;i<=x;i++)
        if(x%i==0)
        {
            while(x%i==0) x/=i;
            ans=ans-ans/i;
        }
    if(x>1) ans=ans-ans/x;
    return ans;
}
int ksm(int x,int p,int mod)
{
    ll s=1,a=x;
    while(p)
    {
        if(p&1) s=(s*a)%mod;
        a=a*a%mod;
        p>>=1;
    }
    return (int)s;
}
void cal(int x)
{
    g.clear();
    if(f[x]) return;
    else{
            for(int i=2;i*i<=x;i++)
            if(x%i==0)
            {
                g.push_back(i);
                if(i*i!=x)  g.push_back(x/i);
            }
        }
}
bool can(int n)
{
    if(n%2==0)  n/=2;
    if(f[n]) return 1;
    for(int i=3;i*i<=n;i+=2)
    {
        if(n%i==0)
        {
            while(n%i==0) n/=i;
            return n==1;
        }
    }
    return 0;
}
void solve(int n)
{
    if(n==2 || n==1)
    {
        cout<<"1"<<endl;return;
    }
    if(n==4)
    {
        cout<<"3"<<endl;return;
    }
    if(!can(n))
    {
        cout<<"-1"<<endl;return;
    }
    int p=fi(n);cal(p);
    int x=-1;
    for(int i=2;i<n;i++)
    {
        bool flag=1;
        if(ksm(i,p,n)!=1) continue;
        for(int j=0;j<g.size();j++)
        {
            if(ksm(i,g[j],n)==1)
            {
                flag=0;break;
            }
        }
        if(flag)
        {
            v[0]=x=i;
            id++;
            break;
        }
    }
    if(x==-1)
    {
        cout<<x<<endl;return;
    }
    for(int i=2;i<p;i++)
    {
        if(__gcd(i,p)==1) v[id++]=ksm(x,i,n);
    }
    sort(v,v+id);
    int now=v[0];
    bool u[10005];
    for(int i=1;i<id;i++)
    {
        if(v[i]!=v[0]) now=v[i];
        else u[i]=1;
    }
    for(int i=0;i<id;i++) if(!u[i]) printf("%d\n", v[i]);
}
int main()
{
    freopen("math.in","r",stdin);
    freopen("math.out","w",stdout);
    memset(f,1,sizeof(f));
    f[0]=f[1]=0;
    for(int i=2;i<10005;i++)
    if(f[i]) for(int j=i+i;j<10005;j+=i)  f[j]=0;
    scanf("%d",&n);
    solve(n);
}

利用原根的性质,我们还可以进行简化如下:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
int m;
int phi_m;
int main() 
{
    freopen("math.in", "r", stdin);
    freopen("math.out", "w", stdout);
    cin >> m;
    phi_m = 0;
    for (int i = 1; i < m; ++i) if (__gcd(m, i) == 1) ++phi_m;
    int flag = false;
    for (int i = 1; i <= m; ++i)
        if (__gcd(i,m) == 1)
    {
            int x = 1;
            int d = 0;
            for (int j = 1; j <= phi_m; ++j) 
        {
                x = x * i % m;
                if (x == 1) 
        {
                    d = j;
                    break;
                }
            }
            if (d == phi_m) 
        {
                flag = true;
                printf("%d\n",i);
            }
        }

    if (!flag)
        printf("-1\n");

    return 0;
}

T2 道路覆盖(cover)

这里写图片描述
好吧这个题我比赛时没写出来,因为死磕了T3,还错了…╮(╯▽╰)╭
这个题提到"最低高度最高",因此要用二分.用dp[i][j](j=2^k)表示到第i位时前k位的状态为j时的最小花费,二分高度并计算.
代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
#define ll long long
using namespace std;
int n,m,k;
int h[105],e[105],c[105];
int dp[105][2055];
int min(int x,int y)
{
    if(x==-1) return y;if(y==-1) return x;
    if(x<y) return x;
    else return y;
}
int ok(int mm)
{
    memset(dp,-1,sizeof(dp));dp[0][0]=0;
    for(int i=0;i<n;i++)
    for(int j=0;j<2*k;j++)
    if(dp[i][j]!=-1)
    {
        int sum=0;
        for(int t=k-1;t>0;t--) if((1<<t)&j==1) sum+=e[i-k+1+t];
        if(sum+h[i+1]>=mm) dp[i+1][j>>1]=min(dp[i+1][j>>1],dp[i][j]);
        if(sum+h[i+1]+e[i+1]>=mm) dp[i+1][(1<<(k-1))|(j>>1)]=min(dp[i+1][(j>>1)|(1<<(k-1))],dp[i][j]+c[i+1]);
    }
    for(int j=0;j<2*k;j++) if(dp[n][j]!=-1 && dp[n][j]<=m) return 1;
    return 0;
}
int main()
{
    freopen("cover.in","r",stdin);
    freopen("cover.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&h[i],&e[i],&c[i]);
    int l=0,r=1e8;
    while(l<r-1)
    {
        int m=(l+r)/2;
        if(ok(m)) l=m;
        else r=m;
    }
    if(ok(l+1)) l++;
    cout<<l<<endl;
}

T3 物语(monogatari)

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这题我写了spfa,不过只写了单向的,所以dis[n]的值一直都没变.
正确的做法首先把第M个边去掉求最短路,spfa求出1到任何一个点的最短路,反向spfa任何一个点到n的最短路.然后当第M个边(s,t)的长度变化的时候,只需要比较dis[1][s]+lin[s][t]+dis[t][n],dis[1][n]即可.
代码如下:

#include<iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<string.h>
#include<queue>
#include<vector>
#define ll long long
#define inf 1000000001
using namespace std;
int n,m,k,u,v,w,u0,v0,x[30005],vis[200005],dis[200005],d2[200005];
struct nod{
    int nex,w;
};
vector<nod>lin[400005];
queue<int> q;
void spfa(int x)
{
    while(!q.empty()) q.pop();
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) dis[i]=inf;
    q.push(x);vis[x]=1;dis[x]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=0;i<lin[u].size();i++)
        {
            nod v=lin[u][i];
            if(dis[v.nex]>dis[u]+v.w)
            {
                dis[v.nex]=dis[u]+v.w;
                if(vis[v.nex]==0)
                {
                    vis[v.nex]=1;
                    q.push(v.nex);
                }
            }
        }
    }
}
int main()
{
    freopen("monogatari.in","r",stdin);
    freopen("monogatari.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        lin[u].push_back(nod{v,w});
        lin[v].push_back(nod{u,w});
    }
    scanf("%d%d",&u0,&v0);
    spfa(1);
    memcpy(d2,dis,sizeof(d2));
    spfa(n);
    for(int i=1;i<=k;i++)
    {
        scanf("%d",&x[i]);
        int z=min(d2[n],min(d2[u0]+dis[v0],d2[v0]+dis[u0])+x[i]) ;
        if(z==inf) printf("+Inf\n");
        else printf("%d\n",z);
    }
}

明天要分班考试了,祝我RP++.
rating after round #6: 1526.
本人有轻微的压行习惯,敬请谅解.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值