目录
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))
标签:int,题解,2022SWJTU,元素,矩阵,小幻,选拔赛,sum,坐标 From: https://www.cnblogs.com/yceachan/p/17045160.html