盖世计划--北京营--0731--C班模拟

A. 数位和(digit)

题意:

\(f(x)\)\(x\) 的数字和。例如 \(f(158)=1+5+8=14\)

给定一个长度为 \(N\) 的正整数序列 \(A\),求 \(\sum_{i=1}^{N}\sum_{j=1}^{N}f(A_i+A_j)\)

分析:

首先明确 \(f(x)\)\(x\)数位和

举例情况:

若有两个数分别为:\(12,21\)

\[f(12+21)=f(12)+f(21)=3 \]

可以发现两个数相加的数位和可以转化为第一个数的数位和第二个数数位和相加。

但总有特殊情况如:

这两个数分别为:\(53,27\)

\[f(53+27)=f(80)=8 \]

\[f(53)+f(27)=8+9=17 \]

可以发现这两个数的数位相加时在个位上进了一位,就不符合上面的情况了,那该怎么办呢?

仔细思考进位数位和的贡献是什么?

相当于此位变为 \(0\) 和更高的一位 \(+1\),则是对总答案相当于贡献了 \(-9\)

那么我们就明确了计算的过程

\(g(a,b)\) 表示 \(a+b\) 进位个数

\[f(a+b)=f(a)+f(b)-9\times g(a,b) \\ f(a)+f(b)=\sum_{i=1}^{N}\sum_{j=1}^{N}f(A_i)+f(A_j)=2N \times \sum_{i=1}^{N}f(A_i) \\ 9\times g(a,b)=9 \times \sum_{i=1}^{N}\sum_{j=1}^{N}g(A_i,A_j) \]

第二个式子为什么是 \(2n\) 呢?因为它作 \(a_i\) 时加了 \(n\) 次,它作 \(a_j\) 时被加了 \(n\) 次。

那怎么求 \(g(a,b)\) 呢?

对于两个数 \(a,b\),如果 \(a+b\) 在第 \(x\) 位上发生了进位,那么有 \(a+b\ge10^x\)

那么我们就可以枚举 \(x\),按照每个前 \(x\) 位的数的大小排序,再枚举 \(j\),二分找到第一个 \(a_j+a_k\ge10^i\) 的数的下标 \(k\)\(k\) 后面的数就全是可以进位的,然后就可以 \((n-k+1)\) 求出 \(g(a,b)\) 的个数了。

时间复杂度为 \(O(n\log{n})\)

下面见代码(有注释不要担心):

注:十年OI一场空,不开long long 见祖宗

#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n;
ll x;
ll a[20][200005];//a[前几位的数][第几个数] 
ll ans=0;
ll p(ll x){//计算数位和 
	ll sum=0;	
	while(x){
		sum+=x%10;
		x/=10;
	}
	return sum;
}

int main() {
	scanf("%d",&n);
	
	for(int i=1;i<=n;i++){
		scanf("%lld",&x);
		ans+=2*n*p(x);//先不考虑进位,单纯地都加上 
		ll mod=10;
		for(int y=1;y<=15;y++){
			a[y][i]=x%mod;//前i的数为记录起来 
			mod*=10;
		}
	}	
	ll w=1;
	for(int i=1;i<=15;i++){//枚举前i位 
		w*=10;
		sort(a[i]+1,a[i]+1+n);//对前i位的数的大小进行排序 
		for(int j=1;j<=n;j++){//对每个数进行排序 
		
			//统计加上a[i][j]后大于等于10^i的个数 
			ll k=n+1-(lower_bound(a[i]+1,a[i]+1+n,w-a[i][j])-a[i]);
			ans-=9*k;//减去进位减少的贡献 
		}
	}
	
	cout<<ans; 
	
    return 0;
}

B. T2--集合变换(trans)

暴力搜索,但要剪枝,考虑如果我们搜到了 \(1\) 就没有必要向下递归了,这样就成了没有只有一个儿子的节点,能省下很大时间,如果 \(k/ge m\) 说明我们 \(m\) 耗尽也只能搜到 \(1\),直接剪掉。

我们对初始值分解质因数,我的因数的因数也只能是我们因数,所以我们只要分解一次即可。

#include <bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1 
#define re register 
const int N=5e5+10;
const int mod=1e9+7;
using namespace std;

int x,k,m;
int d[N],top;
int cnt;
int ans;

void dfs(int z,int x){
	if(z==k||x==1){
		cnt++;
		ans+=x;
		if(cnt>=m){
			exit(0);
		}
		return;
	}
	
	for(int i=1;i<=top&&d[i]<=x;i++){
		if(x%d[i]==0){
			dfs(z+1,d[i]);
		}
	}
} 

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr); 
	
	cin>>x>>k>>m;
	
	if(k>=m){
		cout<<m;
		return 0;
	}
	
	for(int i=1;i*i<=x;i++){
		if(x%i==0){
			d[++top]=i;
			if(i*i!=x){
				d[++top]=x/i;	
			}
		}
	}
	sort(d+1,d+top+1);
	dfs(0,x);
	cout<<ans;	
	return 0;
}

D. 序列(seq)

我们一个连通块内的点可以任意交换,所以连边判断连通性即可,连边都连不太行,我们从高到低连有向边,对于每个数向取反连双向边,最后构造即可。

posted @ 2024-11-09 15:59  sad_lin  阅读(21)  评论(0)    收藏  举报