1. 程式人生 > 實用技巧 >智算之道複賽

智算之道複賽

1.數字

大意:輸入aa和b,a的值是aa新增前面三位數字,問有多少種情況使得a0(modb),a沒有前導0,long long int 範圍。

思路:列舉前面三位(100-999*相應倍數+aa)%b是否等於0

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll ipow(int bit){
    ll sum=1;
    for(int i=1;i<=bit;i++){
        sum*=10;
    }
    return sum;
}
int main(){
    ll temp,aa,b,a,sum
=0; int bit=0; cin>>aa>>b; temp=aa; while(temp!=0){temp/=10;bit++;} for(int i=100;i<=999;i++){ if((aa+i*ipow(bit))%b==0)sum++; } cout<<sum<<endl; return 0; }

2.網路

大意:普通格子(a,b)可以消耗w1個金幣移動到(a+1,b)或(a,b+1),魔法格子多一個選擇可以消耗w2個金幣移動到(a+1,b+1),問從點(0,0)到(n,n)所消耗的最少金幣。

輸入K個魔法格子,w1,w2。

思路:用一個pair陣列儲存魔法格子,進行排序,保證從前往後,開一個dp陣列儲存到每個魔法格子的最小消耗,最後一個魔法格子新增為(n,n),初始值為全部選擇w1的方式。

算出兩個魔法格子之間所需移動普通格子的距離-2(魔法格子可以用掉一次,步數-2)所消耗的金幣與原先到達該點的消耗兩者取最小值。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,k,w1,w2,num;
const int maxx=2005;
ll f[maxx];
pair<int
,int>p[maxx]; int main(){ cin>>n>>k>>w1>>w2; for(int i=0;i<k;i++){ cin>>p[i].first>>p[i].second; } if(w1*2<=w2){cout<<n*w1*2<<endl;return 0;} p[k]=make_pair(n,n);k++; sort(p,p+k); for(int i=0;i<k;i++){ f[i]=(ll)(p[i].first+p[i].second)*w1; } for(int i=1;i<k;i++){ for(int j=0;j<i;j++){ if(p[i].first>p[j].first&&p[i].second>p[j].second){ num=p[i].first+p[i].second-p[j].first-p[j].second-2; f[i]=min(f[i],f[j]+(ll)num*w1+w2); } } } cout<<f[k-1]<<endl; return 0; }

3.有向無環圖

大意:給定k條路徑,不得超過N個點(即n<=N)。生成1.....n的沒有重邊的有向無環圖。輸出點數和邊數。

思路:考慮一種完全的有向無環圖,把圖畫成一條鏈,編號小的依次向編號大加邊,可以發現當n=2時,路徑數為1,

n=3時,路徑數為2,n=4時路徑數為4。有路徑數至多等於2^n-2,我們知道任何數都可以拆成2的冪次和相加。

即我們可以將路徑數轉化為二進位制拆分,對於相應的點進行新增,最終湊成目標的路徑數。

找到一個大於等於該數的2次冪,冪數即為編號數量+2,將k--(因為1到n就有一條路),二進位制數k從低位到高位判斷位數為1的需要新增i-n的邊(i從2開始)

不過程式碼只有60分後面tle,有空再來補。

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
vector<pair<int,int> >v;
int main(){
    ll k,n,cnt=2,temp=1;
    cin>>k>>n;
    while(temp<k){
       temp<<=1;
        cnt++;///編號點
    }
    //ll t=temp-k;
    for(int i=1;i<cnt;i++){
        for(int j=i+1;j<cnt;j++){
            v.push_back(make_pair(i,j));///先把cnt-1的點連好
        }
    }
    k=k-1;
    for(int i=1;i<cnt;i++){
        if(i==1)v.push_back(make_pair(i,cnt));///當等於1時有邊1-cnt,即預設有1條路徑
        else{
            if(((k>>(i-2))&1)){///比如k=7,k-1=6=0110,補3-5,4-5兩條邊
                v.push_back(make_pair(i,cnt));
            }
        }
    }
    cout<<cnt<<" "<<v.size()<<endl;
    for(int i=0;i<v.size();i++){
        printf("%ld %ld\n",v[i].first,v[i].second);
    }
    return 0;
}

4.分數

大意:輸入n,a,b。有一個序列1-n的倒數,每一次找編號最小的且分母不為1的數q,序列的每一個數乘以這個數的分母,直到每個數分母都為1。

a=a*q+b。對a取模mod=2^32輸出。

思路:觀察發現每次進行乘的數的編號,該數是可以拆成最小素數的冪次方,每次乘以的數q也即是這個最小的素數。

如輸入4,序列1,1/2,1/3,1/4。乘以的數分別是2,3,2。我們進行尤拉篩得到所有的素數,再將素數的冪次方進行標記祖先素數的位置。

題目要求以編號最小的,所以依次從左往右遍歷,如果是某個素數的冪次方,我們乘以這個素數,其餘的數會在過程中被約分掉。

一些小細節:mod=1ll>>32,忘記寫ll導致出錯。素數的冪次方num需要開ll,否則溢位re。資料範圍是8e7。開兩個8e7的陣列會mle,所以素數的陣列開小一點。

#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const int maxx=8e7+7,maxn=5e7;
int c[maxn],v[maxx],cnt;
ll a,b,n;
ll mod=1ll<<32;
void init(){
    for(int i=2;i<maxx;i++){
        if(v[i]==0){
            c[++cnt]=i;
        }
        for(int j=1;j<=cnt&&i*c[j]<maxx;j++){
            v[i*c[j]]=1;
            if(i%c[j]==0)break;
        }
    }
}
int main(){
    cin>>n>>a>>b;
    init();
    for(int i=1;i<=n;i++)v[i]=0;
    for(int i=1;i<=cnt;i++){
        ll num=c[i];
        while(num<=n){
            v[num]=i;
            num*=c[i];
        }
    }
    for(int i=2;i<=n;i++){
        if(v[i]){
            a=(a*c[v[i]]%mod+b)%mod;
        }
    }
    cout<<a<<endl;
    return 0;
}