ACM2023SWJTU寒假选拔赛1不完全题解

衣陈的博客园链接:https://www.cnblogs.com/yceachan/p/17045160.html

任何tle,终将mle之😅

A - 马宝の皮颜矩阵

Description

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

Input

给出矩阵的行列 n , m n,m n,m

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

Solution

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

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

复杂度 O ( n m ∗ c n t ) O(nm*cnt) O(nmcnt),tle on test 9 ( 5 ∗ 20000 ∗ m u l t i ( 6146 ) ) (5*20000*multi(6146)) (520000multi(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 C_n^2 Cn2的方法来两行/两列间的权值。

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

但是,复杂度 O ( c o u n t ∗ ( n 2 + m 2 ) ) O(count*(n^2+m^2)) O(count(n2+m2))tle on test8 1 ∗ 1 e 5 1*1e5 11e5

//遍历元素的坐标数组
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 ≤ N ⋅ M ≤ 1 e 5 1\le N·M\le1e5 1NM1e5说明本题不能用二维的方式来求曼哈顿距离。

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

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

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

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

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

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

A n s = ∑ E l e m . ( x v a l + y v a l ) , v a l = ∑ a [ j ] − a [ i ] Ans=\sum Elem.(xval +yval) ,val=\sum a[j]-a[i] Ans=Elem.(xval+yval),val=a[j]a[i]

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

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

∑ a [ j ] − a [ i ] = c n t ∗ a [ j ] − ∑ a [ i ] \sum a[j]-a[i]=cnt*a[j]-\sum a[i] a[j]a[i]=cnta[j]a[i],cnt即为 a [ j ] a[j] a[j]的序号, ∑ a [ i ] \sum a[i] a[i]即为前缀和

Code

#include<bits/stdcpp.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…(二进制下)


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

M A X ( a i ⊕ a ( i + 1 ) ) MAX(ai\oplus a(i+1)) MAX(aia(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))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值