洛谷團隊內部賽_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. 原題是浙江省選的一題:最大半連通子圖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章