洛谷团队内部赛_7月月赛_题解

比赛题目来自各个OJ,经过数据加强

目录

T0: 送分水题

输入格式:
一行,两个整数a, b

输出格式:
一行,一个整数a * b

说明

0 <= a, b <= 2147483648


由于a和b都是<=2147483648的,所以它们乘积可能超长整型哒。。。
然而!用unsigned long long就好了。。。不用写高精度乘法的。
代码:

【过水已隐藏】

T1: 斐波那契和……欧几里得(???)1

输入格式:
一个n,一个m。都是正整数。

输出格式:
gcd(第n个斐波那契数,第m个斐波那契数)

输出这个公约数的后8位就好了(前面的0不要)

说明

1<=n,m<=10^9


这题我给的数据比较水所以这样就能过了:

#include <cstdio>
#include <iostream>
#include <algorithm> 

using namespace std;

long long n, m, a[1000001];

int gcd(long long x, long long y)
{
    if (!min(x, y))
        return max(x, y);
    return gcd(min(x, y), max(x, y) % min(x, y));
}

int main()
{
    cin >> n >> m;
    long long q = gcd(n, m);
    a[1] = 1;
    a[2] = 1;
    for (int i = 3; i <= q; i++)
        a[i] = (a[i - 1] + a[i - 2]) % 100000000;
    cout << a[q];
    return 0;
}

这样就好了。

有这么一个著名式子:gcd(f(n), f(m))=f(gcd(n, m))。证明有一点难,但是这个式子应该听说过的吧?
以下是luogu ID 为浅色调 巨佬的证明:

设n < m, f[n]=a, f[n+1]=b
则f[n+2]=a+b, f[n+3]=a+2b, … f[m]=f[m-n-1]a+f[m-n]b
因为f[n]=a, f[n+1]=b, f[m]=f[m-n-1]a+f[m-n]b
所以f[m]=f[m-n-1]*f[n]+f[m-n]*f[n+1]
又因为gcd(f[n], f[m])=gcd(f[n],f[m-n-1]*f[n]+f[m-n]*f[n+1])
而f[n]|f[m-n-1]*f[n]
所以gcd(f[n],f[m-n]*f[n+1])
再证一个引理:gcd(f[n],f[n+1])=1
证:由欧几里得定理知gcd(f[n],f[n+1])=gcd(f[n],f[n+1]-f[n])=gcd(f[n],f[n-1])=gcd(f[n-2],f[n-1])=……=gcd(f[1],f[2])=1
得证。
由引理和gcd(f[n],f[m])=gcd(f[n],f[m-n]*f[n+1])
所以gcd(f[n],f[m])=gcd(f[n],f[m-n])
即gcd(f[n],f[m])=gcd(f[n],f[m%n])
继续递归,将m1=m%n, 则gcd(f[n],f[m])=gcd(f[n%m1],f[m1])

不难发现整个递归都在求解gcd(n, m)
最后递归到出现f[0]时,此时的f[n]就是所求gcd。
q.e.d.

之前说了出的数据比较水不会TLE。。。但之前的代码不是标算。
以下巨佬 浅色调 标准答案:(矩阵加速好可怕)

#include <bits/stdc++.h>
#define il inline
#define ll long long
#define mem(p) memset(&p,0,sizeof(p))
using namespace std;
const ll mod=1e8;
ll n,m;
struct mat{ll a[3][3],r,c;};
il mat mul(mat x,mat y)
{
    mat p;
    mem(p);
    for(int i=0;i<x.r;i++)
        for(int j=0;j<y.c;j++)
            for(int k=0;k<x.c;k++)
    p.a[i][j]=(p.a[i][j]+x.a[i][k]*y.a[k][j])%mod;
    p.r=x.r,p.c=y.c;
    return p;
}
il void fast(ll k)
{
    mat p,ans;
    mem(p),mem(ans);
    p.r=p.c=2;
    p.a[0][0]=p.a[0][1]=p.a[1][0]=1;
    ans.r=1,ans.c=2;
    ans.a[0][0]=ans.a[0][1]=1;
    while(k)
    {
        if(k&1)ans=mul(ans,p);
        p=mul(p,p);
        k>>=1;
    }
    cout<<ans.a[0][0];
}
il ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int main()
{
    ios::sync_with_stdio(0);
    cin>>n>>m;
    n=gcd(n,m);
    if(n<=2)cout<<1;
    else fast(n-2);
    return 0;
}

T3: 毒瘤题_12

题目描述

在集合中找出 k 个出现了奇数次的正整数 a

输入格式:
第一行两个正整数n,k,接下来n 行每行一个正整数表示集合内的元素

输出格式:
从小到大输出 k 行 k 个数,中间用空格分隔。

说明

n<=3000000, 0< ai<=10的10次方。 所有数据正好有k 个数出现了奇数次且 k≤500 保证出现奇数次的 k个数是在 [0,10的10次方] 中均匀随机的。


名副其实。。。之前我想把内存限制加大的不然太可怕了,,,然而洛谷说最大256M。。。然后我想到一个内存128M以内但会超时的算法比原来要简单多了,所以打算把时间限制改为2s的,然而洛谷说比赛已经开始了不能改了。。。
所以这道题答案正确但时间在2s以内都算对吧。。。不然真的太难了。。。
用set来弄

#include <bits/stdc++.h>

#define M 1000000009

struct tim
{
    char a;
    unsigned b;
    tim(const long long &x=0):a(x>>32),b(x){}
    operator long long()const{return (long long)a<<32|b;}
    const bool operator <(const tim &B)const{return a==B.a?b<B.b:a<B.a;}
}x;

std::set<tim>S;
std::set<tim>::iterator it;

int main()
{
    long long t;
    int n,k;
    scanf("%d%d",&n,&k);
    while(n--)
    {
        scanf("%lld",&t);
        x=t;
        if((it=S.find(x))==S.end())
            S.insert(x);
        else
            S.erase(x);
    }
    for(it=S.begin();k--;)
        printf("%lld\n",t=*it++);
    return 0;
}

是时候祭出可怕的标算了。。。异或的特殊性质要了解一下。。。bitset要用。。。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<bitset>
using namespace std;
char frBB[1<<12],*frS=frBB,*frT=frBB;
#define getchar() (frS==frT&&(frT=(frS=frBB)+fread(frBB,1,1<<12,stdin),frS==frT)?EOF:*frS++)
#define ll long long int
inline ll read()
{
    ll x=0;
    char ch=getchar();
    while(!isdigit(ch))
    {
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x;
}
int n,k;
bool used[502]={};
ll x,ans[502]={};
bitset<1000005> a,b;
bitset<9260820> hash;
int stot=0,ttot=0;
int atot=0;
ll xa,xb,xh;
ll s[502]={},t[502]={};
int main()
{
    n=read();
    k=read();
    for(int i=1;i<=n;++i)
    {
        x=read();
        xa=x%1000000;
        xb=x/10000;
        xh=(x^(x>>2)^(x>>10)^(x<<5))&8388607;
        a[xa]=a[xa]^1;
        b[xb]=b[xb]^1;
        hash[xh]=hash[xh]^1;
    }
    for(int i=0;i<=1000000;++i)
    {
        if(a[i])
        s[++stot]=1ll*i;
        if(b[i])
        t[++ttot]=1ll*i;
    }
    for(int i=1;i<=stot;++i)
        for(int j=1;j<=ttot;++j)
        {
            if(used[j]||(s[i]/10000!=t[j]%100))
            continue;
            ll xx=t[j]*10000+s[i]%10000;
            if(hash[(xx^(xx>>2)^(xx>>10)^(xx<<5))&8388607])
            {
                ans[++atot]=xx;
                used[j]=1;
                break;
            }
        }
    sort(ans+1,ans+1+atot);
    for(int i=1;i<=atot;++i)
    printf("%lld\n",ans[i]);
    return 0;
}

标算看不懂就算了最好看懂。。。但前面那个非标算总要看看吧?

T4: 积木积3

题目背景

。。。有没有发现标题也是个回文串。。。

题目描述

cyx喜欢积木,上面还有字母,她有n个积木,每个上面都有a~z二十六个字母中的一个。然后她总是喜欢把这些积木从左往右排成一列。她觉得如果有一段连续积木从左/右读是一样的,这就被叫做“和cyx一样机智勇敢聪明可爱纯洁善良的一列积木”。 Then。。。cyx找出了这些列积木并把它们按照每列(可能有重复的积木)积木个数从小到大排,取前k个乘起来。cyx想知道这个乘积模19930726的值。

输入格式:
第一行为两个正整数n和k。 接下来一行为n个字符,代表从左到右积木上写的字母。

输出格式:
输出一个整数。如果总的和cyx一样机智勇敢聪明可爱纯洁善良的一列积木个数小于K,输出一个整数-1。

说明
n<=1e6, k<=1e12


这题出现在上题后真的让人身心愉悦心情舒畅~~~感觉像三个模版题。。。
很明显要用Manacher算法。。。原题太长,改短了。马拉车前缀和快速幂三个一起弄一下就好了。。。竟然还是国家集训队的???!!!
代码如下。


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int n,dp[1000001],p[1000001];
char ch[1000001];
long long m,sum[1000001];

void manacher()
{
    int mx=0,id;
    for (int i=1;i<=n;i++)
    {
        if (mx>=i)
            p[i]=min(mx-i,p[2*id-i]);
        else
            p[i]=0;
        for (;ch[i+p[i]+1]==ch[i-p[i]-1];p[i]++);
        if (p[i]+i>mx)
            mx=p[i]+i,id=i;
        sum[0]++,sum[p[i]+1]--;
    }
}

long long quickpow(long long num,long long x)
{
    long long base=num%19930726,ans=1;
    while (x)
    {
        if (x%2==1)
            ans=(ans*base)%19930726;
        x>>=1;
        base=(base*base)%19930726;
    }
    return ans;
}

int main()
{
    scanf("%d%lld",&n,&m);
    scanf("%s",ch+1);
    ch[0]='#';
    manacher();
    for (int i=1;i<=n;i++)
        sum[i]=sum[i-1]+sum[i];
    long long ans=1,now=0;
    for (int i=n/2+1;i>=0;i--)
    {
        if (sum[i]==0)
            continue;
        ans=(ans*quickpow(i*2+1,min(sum[i],m-now)))%19930726;
        now+=sum[i];
        if (now>=m)
            break;
    }
    if (now<m)
    {
        printf("-1");
        return 0;
    }
    printf("%lld",ans);
    return 0; 
}

T5: 毒瘤题_24

题目描述

概念:
有向图G=(V,E),图中任意两点a,b,存在一条a->b或者b->a的单向路径,那么G是半连通的。
有向图G‘=(V’,E‘)满足V‘是V的子集,E’是E中和V‘有关的边,那么G’是G的导出子图。
有向图G‘是G的导出子图,而且G’半连通,那么G‘是G的半连通子图。
有向图G’是G所有半连通子图中包含节点数最多的,则称G‘是G的最大半连通子图。

给定一个有向图G,求G的最大半连通子图拥有的节点数MAXV,以及不同的最大半连通子图的数目C。仅要求输出C对X的余数。

输入格式:
三个整数N,M,X。N,M分别表示G的点数和边数。 接下来M行,每行2个正整数a,b,表示一条有向边(a,b)。图中每个点将编号为1,2,3……N。

输入中同一个(a,b)不会出现两次

输出格式:
第一行一个整数K。 第二行包含整数C Mod X。

说明
N<=1e5,M<=1e6,X<=1e8


代码有点长,但经常写写LCT / FFT或者做过这一题或者做过什么项目的巨神肯定觉得代码巨短啊。。。
主要用的:Tarjan+toposort+DP。
首先Tarjan缩个点,去重连边,然后新图get。题目就成了让你求图中最长链和最长链的个数了。。。
最长链直接用拓扑排序,最长链的个数求法有点像DP,代码中用f[i]表示新图里以i为终点的方案数,那么f[i]就等于连到i并且还满足距离=起点到i的临时最长距离的点的f之和,最后查找距离等于最长链的点,答案是方案数量之和。

祭出代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn=1e5+5,maxm=1e6+5;
int n,m,mo,total,num,top,col,t,w,ans;
int x[maxm],y[maxm],to[maxm],next[maxm],nu[maxm];
int de[maxn],first[maxn],ue[maxn],si[maxn],dfn[maxn],low[maxn],st[maxn],co[maxn],e[maxn],dis[maxn];

int _read()
{
    int x=0;
    char c=getchar();
    while('0'>c || c>'9')
        c=getchar();
    while('0'<=c && c<='9')
    {
        x=(x<<3)+(x<<1)+c-'0';
        c=getchar();
    }
    return x;
}

void ins(int x,int y) //连接一条x到y的边 
{
    next[++total]=first[x];
    first[x]=total;
    to[total]=y;
}

void tarjan(int u) //缩点 
{
    dfn[u]=low[u]=++num;
    st[++top]=u;
    for(int i=first[u];i;i=next[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else
            if(!co[v])
                low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        co[u]=++col;
        si[col]++;
        while(st[top]!=u)
            si[col]++,co[st[top]]=col,top--;
        top--;
    }
}

bool cmp(int a,int b)
{
    if(x[a]!=x[b])
        return x[a]<x[b];
    return y[a]<y[b];
}

void _remove() //去重边(不然可能会影响到方案数qaq) 
{
    for(int i=1;i<=m;++i)
    {
        nu[i]=i;
        x[i]=co[x[i]];
        y[i]=co[y[i]];
    }
    sort(nu+1,nu+1+m,cmp);
}

void _build() //缩点重建图+处理入度准备拓扑排序 
{
    total=0;
    memset(first,0,sizeof(first));
    for(int i=1;i<=m;++i)
    {
        int z=nu[i];
        if((x[z]!=y[z]) && (x[z]!=x[nu[i-1]] || y[z]!=y[nu[i-1]]))
        {
            de[y[z]]++;
            ins(x[z],y[z]);
        }
    }
}

void _reset() //拓扑排序初始入队 
{
    for(int i=1;i<=col;++i)
        if(!de[i])
        {
            ue[++w]=i;
            dis[i]=si[i];
            e[i]=1;
            if(dis[ans]<dis[i])
                ans=i;
        }
}

void tsort() //拓扑排序+递推 
{
    while(t<w)
    {
        int u=ue[++t];
        for(int i=first[u];i;i=next[i])
        {
            int v=to[i];
            de[v]--;
            if(dis[v]<dis[u]+si[v]) //更新临时最长距离+重算方案数
            {
                dis[v]=dis[u]+si[v];
                e[v]=0;
                if(dis[ans]<dis[v])
                    ans=v;
            }
            if(dis[v]==dis[u]+si[v]) //累加
                e[v]=(e[u]+e[v])%mo;
            if(!de[v])
                ue[++w]=v;
        }
    }
}

int anss;

void _ask() //统计答案 
{
    for(int i=1;i<=n;++i)
        if(dis[i]==dis[ans])
        {
            anss=(anss+e[i])%mo;
        }
}

int main()
{
    n=_read();
    m=_read();
    mo=_read();
    for(int i=1;i<=m;++i)
    {
        x[i]=_read(),y[i]=_read();
        ins(x[i],y[i]);
    }
    for(int i=1;i<=n;++i)
        if(!dfn[i])
            tarjan(i);
    _remove();
    _build();
    _reset();
    tsort();
    _ask();
    cout<<dis[ans]<<endl<<anss;
    return 0;
}

  1. 原题为洛谷P1306
  2. 原题为LOJ#6232
  3. 原题为洛谷P1659(也是国家集训队的一题)
  4. 原题是浙江省选的一题:最大半连通子图

猜你喜欢

转载自blog.csdn.net/cxxchen_/article/details/80960448
今日推荐