智算之道複賽
1.數字
大意:輸入aa和b,a的值是aa新增前面三位數字,問有多少種情況使得a≡0(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; }