NOIP2013提高组 day1

这篇博客探讨了信息技术中数学和算法的重要性。第一部分介绍了快速幂取模在解决转圈游戏问题中的应用,强调了数学推导和细节处理对于正确解题的关键作用。第二部分涉及火柴排队问题,通过数学分析和归并排序算法寻找最小交换次数。最后一部分讨论了最大生成树在货车运输问题中的应用,揭示了解决图论问题的思路。博客展示了数学和算法在信息技术中的实际运用及其价值。
摘要由CSDN通过智能技术生成

在这里插入图片描述这道题考察的是数学推导和快速幂取模
从0号开始推,发现只需加上每次走的距离*次数再mod总人数就是答案,其他情况同理。

所以就有公式
a n s ans ans = = = ( x + m ∗ 1 0 k ) (x+m*10^k) (x+m10k) m o d mod mod n n n

AC代码如下

#include<bits/stdc++.h> //转圈游戏 
using namespace std;
int main(){
	//freopen("circle.in","r",stdin);
    //freopen("circle.out","w",stdout);
	long long n,m,k,x,tot=1,now=10,ans;
	cin>>n>>m>>k>>x;
	while(k){
		if(k&1) tot=tot*now%n;
		k/=2;
		now=now*now%n;
	}
	tot=tot*m%n;
    ans=(x+tot)%n;
	cout<<ans;
	return 0;  
}

考试的时候敲完了代码,以为AC了,结果犯了一个细节错误,导致只有30分……

tot*=now%n; //tot=tot*(now%n)
tot=tot*now%n; //这里没有括号

2.火柴排队

在这里插入图片描述这道题考察的是数学推导,树状数组以及归并排序

通读一遍题后,发现需要进行数学推导。

要求 m i n ∑ i = 1 n ( a i − b i ) 2 min\sum_{i=1}^{n}(a_i-b_i)^2 mini=1n(aibi)2
即求 m i n ∑ i = 1 n ( a i 2 − 2 a i ∗ b i + b i 2 ) min\sum_{i=1}^{n}(a_i^2-2a_i*b_i+b_i^2) mini=1n(ai22aibi+bi2)
由于 ∑ i = 1 n ( a i 2 + b i 2 ) \sum_{i=1}^{n}(a_i^2+b_i^2) i=1n(ai2+bi2) 为定值
则问题等价于求 m a x ∑ i = 1 n ( 2 a i ∗ b i ) max\sum_{i=1}^{n}(2a_i*b_i) maxi=1n(2aibi)
引理:设 a 1 > a 2 , b 1 > b 2 a_1>a_2,b_1>b_2 a1>a2,b1>b2 有:
a 1 ∗ b 2 + a 2 ∗ b 1 ≤ a 1 ∗ b 1 + a 2 ∗ b 2 a_1*b_2+a_2*b_1\leq a_1*b_1+a_2*b_2 a1b2+a2b1a1b1+a2b2

一番证明后发现,要让大数乘大数,小数乘小数才会有距离最小值,会想到排序。要让我们求最小交换次数,也就是排序后的数组与排序前的数组进行比较,求逆序对的个数,会想到用归并排序或树状数组。这里我使用的是归并排序。

AC代码如下

#include<bits/stdc++.h> //火柴排队 
using namespace std;
const int MAXN=100001,mod=99999997;
struct data{                                  //定义结构体 
	int x,num;
}a[MAXN],b[MAXN];
int step[MAXN],q[MAXN],n,tot=0;
bool compare(data a,data b){                  //比较函数 
	return a.x<b.x;
}
void gb(int l,int r){                         //归并排序 
	if(l==r) return;
	int mid=(l+r)/2;
	gb(l,mid);
	gb(mid+1,r);
	int vis=l,i=l,j=mid+1;
	while(i<=mid&&j<=r){
		if(step[i]<=step[j]){
			q[vis]=step[i];
			vis++,i++;
		}
		else{
			q[vis]=step[j];
			vis++,j++;
			tot=(tot+(mid-i+1))%mod;
		}
	}
	while(i<=mid){
		q[vis]=step[i];
		i++,vis++;
	}
	while(j<=r){
		q[vis]=step[j];
		j++,vis++;
	}
	for(int i=l;i<=r;i++){
		step[i]=q[i];
	}
} 
int main(){
	//freopen("match.in","r",stdin);
	//freopen("match.out","w",stdout); 
	cin>>n;
	for(int i=1;i<=n;i++){
	    scanf("%d",&a[i].x);
	    a[i].num=i;
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&b[i].x);
		b[i].num=i;
	}
	sort(a+1,a+n+1,compare);
	sort(b+1,b+n+1,compare);
	for(int i=1;i<=n;i++){
		step[a[i].num]=b[i].num;
	}
	gb(1,n);
	cout<<tot;
	return 0; 
}

在这里插入图片描述
这道题考察的是并查集最近公共祖先最大生成树

考试的时候没想到正解(图论快忘完了),用的dfs深搜,结果爆零。后面经过大佬的讲解才完全懂了 ,就当复习知识点。

为什么要用最大生成树?
在这里插入图片描述
如图,有三条边,1-2,1-3,2-3,边权分别为45,1,50,由题目要求,我们肯定会选择走1-2,2-3这条路径,可以载运更多的货物,所以我们推出要求的是最大生成树。

由于有的数据它的图不一定联通,需要加入并查集来判断。

AC代码如下

#include<bits/stdc++.h> //货车运输 
#define MAXN 10005 
#define INF 1e8 
using namespace std; 
struct data{  
    int x,y,dis;
}a[5*MAXN];
struct node{
    int to,next,dis;
}b[10*MAXN];
int cnt,n,m,head[MAXN],deep[MAXN],f[MAXN],fa[MAXN][21],dis[MAXN][21];
bool vis[MAXN]; 
void addedge(int from, int to, int dis){           //链式前向星存图
    b[++cnt].next=head[from];
    b[cnt].to=to;
    b[cnt].dis=dis;
    head[from]=cnt;
    return;
}
bool compare(data a,data b){
    return a.dis>b.dis;
}
int find(int x){
    if(f[x]!=x) f[x]=find(f[x]);
    return f[x];
}
void kruskal(){                                     //最大生成树 
    sort(a+1,a+m+1,compare); 
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
        if(find(a[i].x)!=find(a[i].y)){
            f[find(a[i].x)]=find(a[i].y);
            addedge(a[i].x,a[i].y,a[i].dis);
            addedge(a[i].y,a[i].x,a[i].dis);  
        }
    return;
}
void dfs(int node){
    vis[node]=true;
    for(int i=head[node];i;i=b[i].next){ 
        int to=b[i].to;
        if(vis[to]) continue;
        deep[to]=deep[node]+1;
        fa[to][0]=node;
        dis[to][0]=b[i].dis; 
        dfs(to);
    }
    return;
}
int lca(int x, int y){                     //最近公共祖先 
    if(find(x)!=find(y)) return -1; 
    int ans=INF;
    if(deep[x]>deep[y]) swap(x,y); 
    for(int i=20;i>=0;i--)
        if(deep[fa[y][i]]>=deep[x]){
            ans=min(ans,dis[y][i]);  
            y=fa[y][i];
        }
    if(x==y) return ans; 
    for(int i=20;i>=0;i--)
        if(fa[x][i]!=fa[y][i]){
            ans=min(ans,min(dis[x][i],dis[y][i])); 
            x=fa[x][i]; 
            y=fa[y][i];
        }
    ans=min(ans,min(dis[x][0],dis[y][0]));
    return ans;
}
int main(){
    int x,y,z,q;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        a[i].x=x,a[i].y=y,a[i].dis=z;
    }
    kruskal();
    for(int i=1;i<=n;i++)
        if(!vis[i]){
            deep[i]=1; 
            dfs(i);
            fa[i][0]=i;
            dis[i][0]=INF;
        }
    for(int i=1;i<=20;i++)
        for(int j=1;j<=n;j++){
            fa[j][i]=fa[fa[j][i-1]][i-1]; 
            dis[j][i]=min(dis[j][i-1],dis[fa[j][i-1]][i-1]);
        }
    cin>>q;
    for(int i=1;i<=q;i++){
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值