文章目录
A. Square(简单模拟)
思路 :简单的模拟,将
a
i
a_{i}
ai的总和与
b
i
b_{i}
bi的总和加起来后进行比较即可。
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;cin>>t;
while(t--)
{
vector<int>x(4),y(4);
for(int i=0;i<4;i++)
{
cin>>x[i]>>y[i];
}
sort(x.begin(),x.end());
sort(y.begin(),y.end());
cout<<(y[2]-y[0])*(x[2]-x[0])<<"\n";
}
return 0;
}
B. Arranging Cats(简单思维)
题意:给定两个01序列a,b,求用以下操作将序列a修改成序列b所需最少的操作数。
操作1:将序列a的某一位0变成1。
操作2:将序列a的某一位1变成0。
操作3:将序列a中的任意一对
(
0
,
1
)
(0,1)
(0,1)进行交换
思路:首先求出序列a和b中的1的总数cnta,cntb,再减去序列a与b中的同时为1的位置的个数,答案就是
m
a
x
(
c
n
t
a
,
c
n
t
b
)
max(cnta,cntb)
max(cnta,cntb)
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
int n;cin>>n;
string s,t;
cin>>s>>t;
int cnts=0,cntt=0;
for(int i=0;i<n;i++)cnts+=(s[i]=='1');
for(int i=0;i<n;i++)cntt+=(t[i]=='1');
for(int i=0;i<n;i++)
{
if(s[i]=='1'&&t[i]=='1')cnts--,cntt--;
}
cout<<max(cnts,cntt)<<"\n";
}
return 0;
}
C. Sending Messages(贪心)
题意:给定一个序列arr表示时间,一个人需要在这些时间发送短信,初始时这个人的手机电量为f,每过一个单位的时间,手机的电量会减少a,这个人也可以选择关机,关机后每过一个单位的时间,手机的电量不会改变,但是再次开机时会损耗b的电量。问这个人以最优操作来操作手机,是否能发完所有的短信
思路:我们可以枚举序列arr每两个时间直接扣电和关机时损耗的电量,每次操作都选择最小的操作进行操作,看最后电量是否>0即可。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
ll n,f,a,b;cin>>n>>f>>a>>b;
vector<int>arr(n+1);
arr[0]=0;
for(int i=1;i<=n;i++)cin>>arr[i];
for(int i=1;i<=n;i++)
{
f-=min(1ll*(arr[i]-arr[i-1])*a,b);
}
if(f>0)cout<<"YES"<<"\n";
else cout<<"NO"<<"\n";
}
return 0;
}
D. Very Different Array(贪心)
题意:给定一个长度为n的序列a,再给定一个长度为m的序列b
(
m
≥
n
)
(m \ge n)
(m≥n),要求在序列b中挑选n个数,组成序列c,使得
∑
i
=
1
n
∣
a
i
−
c
i
∣
\sum_{i=1}^{n}|a_i-c_i|
∑i=1n∣ai−ci∣最大
思路:首先将序列a和序列b从小到大排序,我们贪心的选择,序列a的两端i,j,开始向中间,一个一个的来选取对应的
c
i
c_i
ci,那么对于
a
i
a_i
ai和
a
j
a_j
aj如何选取能使得答案最大呢:
定义序列b的两端为 L , R L,R L,R。
那么对于 a i a_i ai有两种选择选择 b L b_L bL和 b R b_R bR。
同样的对于 b j b_j bj也有两种选择 b L b_L bL和 b R b_R bR。
对于这四种情况,我们每次选择最大的情况即可
双指针从两端向中间遍历
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
int n,m;cin>>n>>m;
vector<ll>a(n),b(m);
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<m;i++)cin>>b[i];
sort(a.begin(),a.end());
sort(b.begin(),b.end());
ll ans=0;
int L=0,R=m-1;
for(int i=0,j=n-1;i<=j;)
{
int res1=max(abs(a[i]-b[L]),abs(a[i]-b[R]));
int res2=max(abs(a[j]-b[L]),abs(a[j]-b[R]));
if(res1>=res2)
{
ans+=res1;
if(abs(b[L]-a[i])>=abs(b[R]-a[i]))
{
i++;
L++;
}
else
{
i++;
R--;
}
}
else
{
ans+=res2;
if(abs(b[L]-a[j])>=abs(b[R]-a[j]))
{
j--;
L++;
}
else
{
j--;
R--;
}
}
}
cout<<ans<<"\n";
}
return 0;
}
E. Eat the Chip(贪心)
题意: 在一个
n
∗
m
n*m
n∗m 的网格中,放入一个黑色棋子与白色棋子,每一次,白色棋子可以选择向正下方,左下,右下,三个方向移动棋子,黑色棋子每次可以选择正上方,左上,右上三个方向移动棋子。两个棋子都以最优方案操作,如果白色棋子能抓大棋子那么Alice获胜,否则Bob获胜。如果白色棋子走到最底下,黑丝棋子走到最上方,那么就是平局,输出Draw
思路:每一次白色棋子会向下走一步,黑色棋子会向上走一步,因此,我们只需要判断两棋子相遇前的最后一步是谁走的,然后再判断每个棋子应该往哪个方向一直走,例如,最后一步是白色棋子走,那么白色棋子就是白色棋子往黑色棋子的方向走,如果最后一步是黑色棋子走,那么就是黑色棋子向白色棋子的方向走,另一个棋子与之相同的方向走。最后再判断以下二者的位置即可
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
int h,w,xa,xb,ya,yb;
cin>>h>>w>>xa>>ya>>xb>>yb;
if(xa>=xb)
{
cout<<"Draw"<<"\n";
continue;
}
int col_dis=xb-xa-1;//求出黑白两色棋子的距离,判断最后是白色棋子走还是黑色棋子走
int col_a=0,col_b=0;
if(col_dis&1)
{
col_dis/=2;
col_dis++;
if(ya<=yb)//判断是白色棋子向黑色棋子的方向走还是黑色棋子向白色棋子的方向走
{
col_a=max(ya-col_dis,1);
col_b=max(yb-col_dis,1);
if(col_a>=col_b)cout<<"Bob"<<"\n";
else cout<<"Draw"<<"\n";
}
else
{
col_a=min(ya+col_dis,w);
col_b=min(yb+col_dis,w);
if(col_b>=col_a)cout<<"Bob"<<"\n";
else cout<<"Draw"<<"\n";
}
}
else
{
col_dis/=2;
if(ya<=yb)
{
col_a=min(ya+col_dis+1,w);
col_b=min(yb+col_dis,w);
if(col_a>=col_b)cout<<"Alice"<<"\n";
else cout<<"Draw"<<"\n";
}
else
{
col_a=max(ya-col_dis-1,1);
col_b=max(yb-col_dis,1);
if(col_b>=col_a)cout<<"Alice"<<"\n";
else cout<<"Draw"<<"\n";
}
}
}
return 0;
}
F. Sum of Progression(根号分治)
题意:给一个长度为n的序列a,有q个询问,对于每个询问q,给定三个参数
s
,
d
,
k
s,d,k
s,d,k来求
a
s
+
a
s
+
d
∗
2
+
…
…
a
s
+
(
k
−
1
)
∗
d
∗
k
a_s+a_{s+d}*2+……a_{s+(k-1)*d}*k
as+as+d∗2+……as+(k−1)∗d∗k的总和
思路:这题如果暴力求解很简单,但是一定是会超时的。有一种算法专门处理这类问题,那就是根号分治,根号分治的的思想就是取一个值X,用X把问题分成两个部分,当操作次数
≤
X
\le X
≤X时,可以暴力计算,当操作次数
≥
X
\ge X
≥X时,可以采用之前预处理好的东西直接得到答案。
那么这个 X 取什么值合适呢?对于上述的第一种操作,时间复杂度是
O
(
x
)
O(x)
O(x),对于第二种操时间复杂度为
O
(
n
x
O(\frac{n}{x}
O(xn),那么一次操作的时间复杂度最坏就是
m
a
x
(
O
(
x
)
,
O
(
n
x
)
)
max(O(x),O(\frac{n}{x}))
max(O(x),O(xn)),毫无疑问,选择
X
=
n
X=\sqrt{n}
X=n时,时间复杂度是最低的。
现在看一道例题,来更好的了解根号分治这个算法: 洛谷P3396 哈希冲突
这题我们可以把模数看成上述的X,用
m
o
d
[
x
]
[
y
]
mod[x][y]
mod[x][y]表示modx为y的数的总和,预处理出来模数
X
≤
n
X\le \sqrt{n}
X≤n时所有的情况,对于
X
≥
n
X\ge \sqrt{n}
X≥n的情况, 暴力计算即可,修改也是同样
那么总的时间复杂度就是
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)
代码在https://www.luogu.com.cn/record/143536024
现在接着看这道题:我们同样可以用根号分治的方案,首先预处理出来所有
d
≤
s
q
r
t
(
n
)
d \le sqrt(n)
d≤sqrt(n)的不带权的前缀和,用
p
r
e
pre
pre数组记录下来,也就是首先不管上述题目中的K,然后再计算一个带权的前缀和,用
s
p
r
e
spre
spre记录。最后我们对于每个s,判断一个是否是
s
p
r
e
spre
spre数组中的第一项,如果不是,所有的权值都要减小
(
s
−
1
)
/
d
∗
k
(s-1)/d*k
(s−1)/d∗k,具体先代
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
ll pre[N][320];
ll spre[N][320];
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
int n,m;cin>>n>>m;
vector<int>a(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
int D=320;//即sqrt(n)
for(int d=1;d<=min(D,n);d++)//预处理所有d<sqrt(n)的情况
{
for(int i=1;i<=d;i++)
{
for(int j=i;j<=n;j+=d)
{
pre[j][d]=(j<=d?0:pre[j-d][d])+1ll*a[j];
spre[j][d]=(j<=d?0:spre[j-d][d])+1ll*a[j]*((j-i)/d+1);
}
}
}
while(m--)
{
int s,d,k;cin>>s>>d>>k;
if(d>D)//d>sqrt(n)时暴力计算
{
ll ans=0;
for(int j=1;j<=k;j++)ans+=1ll*a[s+(j-1)*d]*j;
cout<<ans<<" ";
}
else
{
ll p1=spre[s+(k-1)*d][d]-(s-d<=0?0:spre[s-d][d]);
ll p2=pre[s+(k-1)*d][d]-(s-d<=0?0:pre[s-d][d]);///注意要特判
ll ans=p1-1ll*(s-1)/d*p2;
cout<<ans<<" ";
}
}
cout<<"\n";
}
return 0;
}
G. Mischievous Shooter(前缀和+思维)
题意:在一个
n
∗
m
n*m
n∗m的网格中有许多个得分点,给定参数k,可以向一个点的四个方向投放炸弹,会得到该方向长度为
(
k
+
1
)
(k+1)
(k+1)的三角形内的所有的得分点的总和,问能得到的得分点的总和最大为多少。
思路:这题看似复杂,有四个方向,其实我们只需要选取一个方向,然后旋转网格四次即可,那么我们如果我们选择这个方向的三角形,如何通过递推从
(
i
,
j
−
1
)
(i,j-1)
(i,j−1)得到
(
i
,
j
)
(i,j)
(i,j)下一个三角形的值呢,只需要加上红色区域的值减去绿色区域的值即可
因此只要预处理出来列方向上的前缀和和一个对角线方向上的一个前缀和即可。注意要判断一个边界问题。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--)
{
int n, m, k;
cin >> n >> m >> k;
k++;
vector<string> s(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> s[i];
s[i] = " " + s[i];
}
int ans = 0;
for (int t = 0; t < 4; t++)//四个方向,每次旋转90度
{
vector<vector<int>> col(n + 1, vector<int>(m + 1, 0));//列方向上的前缀和
vector<vector<int>> dia(n + 1, vector<int>(m + 1, 0));//对角线上的前缀和
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
col[i][j] = col[i - 1][j] + (s[i][j] == '#');
dia[i][j] = dia[i - 1][j - 1] + (s[i][j] == '#');
}
for (int i = 1; i <= n; i++)
{
int now = 0;
for (int j = 1; j <= m; j++)
{
now += col[min(n, i + k - 1)][j] - col[i - 1][j];
int x = i + k - 1, y = j - 1;
if (x >= n)//注意判断对角线上的边界问题
y -= (x - n), x = n;
now -= (y >= 1 ? dia[x][y] : 0);
now -= (i - 1 >= 1 && j - k - 1 >= 1 ? -dia[i - 1][j - k - 1] : 0);
ans = max(ans, now);
}
}
swap(n, m);//旋转网格需要交换n和m
vector<string> p(n + 1, string(m + 1, '.'));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
p[i][j] = s[j][n - i + 1];//找到对应关系然后求得翻转后的网格
s = p;//复制回去
}
cout << ans << "\n";
}
return 0;
}