任何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],1≤N⋅M≤1e5,1≤a[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(nm∗cnt),tle on test 9 ( 5 ∗ 20000 ∗ m u l t i ( 6146 ) ) (5*20000*multi(6146)) (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 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 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 ≤ N ⋅ M ≤ 1 e 5 1\le N·M\le1e5 1≤N⋅M≤1e5说明本题不能用二维的方式来求曼哈顿距离。
离散化的思想是正确的,此外还需二维降一维的思想。
结合一下暴力和假优化,有以下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]=cnt∗a[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(ai⊕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))