智算之道复赛

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;
}

 

posted @ 2020-08-14 14:20  mohari  阅读(275)  评论(0编辑  收藏  举报