智算之道复赛
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;
}