2022SWJTU寒假选拔赛1题解

任何tle,终将mle之 ヾ^_^💧

[3/?]

A - 马宝の皮颜矩阵

Description

给定矩阵\(a[N][M],1\le N·M\le1e5,1\le a[i][j] \le 1e5\),求所有相同元素的曼哈顿距离和\(X\)

Input

给出矩阵的行列\(n,m\)

给出矩阵所愿元素\(a[i][j]\)

Solution

此题难在N,M的范围比较飘,比如1*1e5(test8)

暴力算法是:记录每种元素的所有坐标,在遍历元素坐标数组或遍历矩阵位置时,曼哈顿距离和即为当前坐标与所有前驱坐标的曼哈顿距离和

复杂度\(O(nm*cnt)\),tle on test 9 \((5*20000*multi(6146))\)

//遍历矩阵,每个坐标与所有前驱产生权值。tle on test 9
for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{			
			for(auto it:mey[a[i][j]])
			{
				X+=abs(i-it.x)+abs(j-it.y);
			}
			mey[a[i][j]].push_back(Pair(i,j));
		}
	}

假优化是:x轴y轴离散化,统计每个元素在每行每列出现的次数,离散化求出曼哈顿距离。

假优化中,离散化后是\(C_n^2\)的方法来两行/两列间的权值。

乍一看是不错的优化,处理有大量元素重复的数据会快很多。

但是,复杂度\(O(count*(n^2+m^2))\)tle on test8\(1*1e5\)

//遍历元素的坐标数组
for(int i=1;i<=1e5;i++)
	{
        //vec[i]为i元素在矩阵中的各个坐标。
		if(vec[i].size()==0) continue;
		vector<int> row(n,0);
		vector<int> col(m,0);
        //统计在各行各列的词频
		for(auto it:vec[i])
		{
			row[it.first]++;
			col[it.second]++;
		}
        //每次计算出两行/两列间这size*size对元素的曼哈顿距离和
		for(int j=0;j<row.size();j++)
		{
			for(int k=j+1;k<row.size();k++) X+=row[j]*row[k]*(k-j);
		}
		
		for(int j=0;j<col.size();j++)
		{
			for(int k=j+1;k<col.size();k++) X+=col[j]*col[k]*(k-j);
		}
	}

这个不固定形状的\(1\le N·M\le1e5\)说明本题不能用二维的方式来求曼哈顿距离。

离散化的思想是正确的,此外还需二维降一维的思想。

结合一下暴力和假优化,有以下point:

1.不同值的元素的曼哈顿距离和是独立的

2.同一元素的坐标中,x和y的贡献是独立的。

考查某个元素在某一轴的坐标vector,这个元素在这个方向的权和是这个数组\(C^2_n\)元素间差值的绝对值的和

不维护矩阵,维护每种元素的各个坐标,x轴和y轴坐标可以分开维护。

\(Ans=\sum Elem.(xval +yval) ,val=\sum a[j]-a[i]\)

优化可以发生在\(val=\sum a[j]-a[i]\),若数组有序,val不必用两层for\(C^2_n\)的方式来求

将这个元素的坐标数组升序排序,在遍历中,每新入一个坐标,都会与前面的所有坐标产生权值,总权值:

\(\sum a[j]-a[i]=cnt*a[j]-\sum a[i]\),cnt即为\(a[j]\)的序号,\(\sum a[i]\)即为前缀和

Code

#include<bits/stdc++.h>
using namespace std;
using ll= long long;
const int N=1e5+5;
vector<int> x[N];
vector<int> y[N];
ll X=0;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int n,m;
	cin>>n>>m;
	//维护出元素a的坐标vector
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int a;cin>>a;
		    x[a].push_back(i);
		    y[a].push_back(j); 
		}
		
	}	
	for(int i=1;i<=1e5;i++)
	{
		if(x[i].empty()) continue;
		//升序排序
		sort(x[i].begin(),x[i].end());		
		ll xsum=0,cnt=0;
        //一次求出it这个坐标与所有前驱坐标的组合权值。
		for(auto it:x[i])
		{
		
			X+=1ll*cnt*it-xsum;
			cnt++;
			xsum+=it;
		}
	}
	for(int i=1;i<=1e5;i++)
	{
		if(y[i].empty()) continue;
		
		sort(y[i].begin(),y[i].end());		
		ll ysum=0,cnt=0;
		for(auto it:y[i])
		{
			X+=1ll*cnt*it-ysum;
			cnt++;
			ysum+=it;
		}
	}
	
    cout<<X;
    return 0;
}

I - 小幻777

Description

小幻同学新学了一个魔法,每次消耗一点1点体力可以变换某个数的任意一位数的大小,但是不能变换出前导0.

比如 344->244, 但是不能044

小幻同学喜欢7,问给你一个数,在最少消耗体力的情况下,输出你变换后的数能够被7整除

如果你用暴力做法,那小幻同学不是很认可  ̄へ ̄

Solution

可以给出的是,只修改个位数就能整除7了,对于数x,余项(x%7),x,个位较大,就减x%7,各位较小,就加7-x%7。

Code

不贴

J - 小幻考考你

Description

小幻给出一个序列A,长度为n, 0-n-1的数都会出现在队列A里面,且出现一次, 你现在可以用你聪明的小脑瓜对这个队列排序,使得 $MAX (ai\oplus a(i+1)) $的值最小

Solution

遇到位运算,我们可以考虑二进制一位一位的思考
发现对于位数最多的数, 无论他们整么放置, 最后都会产生一个^出来的数有这么多位
于是就让这个^产生出的最大数为 100000..(二进制下)
于是利用0把这些数分开, 一边是 数的位数是小于 最高位数的, 一边都是最高位数, 当然和0挨着的 要是 10000..(二进制下)


当然,如果你观察能力够强的话,可以直接通过样例来找规律, 这也是一种方法 ——某幻老师

\(MAX(ai\oplus a(i+1))\)有非常大影响的是那些位数最多的数,形如0000-0111,1000,1001这段序列的异或最小也是1___了,首位是消不掉的(考查1000,右侧可以放形如1__1,左侧捏...),考查低三位000,放任何数与其异或都没有意义。

所以只能在左侧放0,然后期望这个1000...就是最小的最大值。

这是非常容易达成的,甚至只需要按自然序排列就行,例如,形如n=15的构造策略:

0001,0010,0011,0100,0101,0110,0111,!!0000,1000!!,1001,1010,1011,1100,1101,1111

把位数高的那一坨数字都排在一起,那一块的最高位的异或就都是零了(较小的),分界发生在0000,1000,这就是所能构造出来的最小的最大值。

Code

void solve(int x)
{
    if(x==2) {cout<<"0 1\n";return;}
    int m=1;
    //找到离(x-1)最近的(1000...)
    while(m<=(x-1)) m<<=1;
	m>>=1;
    for(int i=1;i<x;i++)
    {
      if(i==m) cout<<0<<" ";
      cout<<i<<" ";
    }
}

位运算请贪心地自高位向低位思考

[友链蓝桥:选数异或,异或博弈](蓝桥备赛 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

posted @ 2023-01-11 23:14  yceachan  阅读(62)  评论(0)    收藏  举报