目录
一,小美的平衡矩阵
题目
小美拿到了一个 n∗n 的矩阵,其中每个元素是 0 或者 1。
小美认为一个矩形区域是完美的,当且仅当该区域内 0 的数量恰好等于 1 的数量。现在,小美希望你回答有多少个 i∗i 的完美矩形区域。你需要回答 1≤i≤n 的所有答案。
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
第一行输入一个正整数n,代表矩阵大小。接下来的n行,每行输入一个长度为n的01串,用来表示矩阵。1<n<200。
输出描述:
输出n行,第i行输出i*i的完美矩形区域的数量。
示例1:
输入例子: 4
1010
0101
1100
0011
输出例子: 0
7
0
1
解题思路
我们需要统计完美矩阵的区域,我们需要统计矩阵区域中 0 与 1 的数量,我们可以使用前缀和数组记录 0 与 1 的数量,因为矩阵中只有 0 与 1 所以我们只需要记录 0 或 1 一个值的数量。
我们定义一个前缀和数组 s[n+1][n+1] ,s[ i ] [ j ] 表示矩阵中以 ( 0 , 0 ) 为起点的 i * j 矩阵中 1的数量。我们使用动态规划的思想计算前缀和,状态转移方程如下:
s[i][j] = g[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
在计算完前缀和数组后,我们枚举边长为偶数的矩阵,计算是否为完美矩阵,边长为奇数的矩阵一定不是完美矩阵。枚举 i*i 矩阵的时候,我们从位置 ( i , i ) 开始,计算以 ( j , k ) 为右下顶点的矩阵中 1 的数量,计算逻辑如下:
int sum = s[j][k] - s[j][k - i] - s[j - i][k] + s[j - i][k - i];
然后判断 1 的数量,是否为 ( i * i ) / 2 。
代码
#include<iostream>
#include<array>
using namespace std;
int main()
{
int n;
cin >> n;
int g[n+1][n+1];
int s[n+1][n+1];
for (int i = 1; i <= n; i++)
{
string str;
cin >> str;
int k = 0;
for (int j = 1; j <= n; j++)
{
g[i][j] = str[k++] - '0';
//1的数量
s[i][j] = g[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
for (int i = 1; i <= n; i++)
{
if(i % 2)
{
cout << "0" << '\n';
continue;
}
int res = 0;
for (int j = i; j <= n; j++)
{
for (int k = i; k <= n; k++)
{
int sum = s[j][k] - s[j][k - i] - s[j - i][k] + s[j - i][k - i];
if (sum == (i * i) / 2)res++;
}
}
cout << res << '\n';
}
}
二,小美的数组询问
题目
小美拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 0 来表示)。
现在小美想知道,如果那些未知的元素在区间[𝑙,𝑟][l,r]范围内随机取值的话,数组所有元素之和的最小值和最大值分别是多少?
共有q次询问。
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
第一行输入两个正整数n,q,代表数组大小和询问次数。
第二行输入n个整数a_i,其中如果输入的a_i为 0,那么说明a_i是未知的。
接下来的q行,每行输入两个正整数 l,r,代表一次询问。
1<n,q<10^5
0<a_i<10^9
1<=l<=r<=10^9
输出描述:
输出q行,每行输出两个正整数,代表所有元素之和的最小值和最大值。
示例1
输入例子: 3 2
1 0 3
1 2
4 4
输出例子: 5 6
8 8
例子说明:
只有第二个元素是未知的。
第一次询问,数组最小的和是 1+1+3=5,最大的和是 1+2+3=6。
第二次询问,显然数组的元素和必然为 8。
解题思路
我们只需统计所有已知数的和,与未知数的数量,即可根据范围算出最大值与最小值。
代码
#include <iostream>
#include <vector>
using namespace std;
long long know_sum=0;
long long unknow_num=0;
void func(int left,int right)
{
long long min=know_sum+unknow_num*left;
long long max=know_sum+unknow_num*right;
cout << min <<" "<< max << endl;
}
int main()
{
long long n,q;
cin >> n;
cin >> q;
vector<int> arr(n);
for(int i=0;i<n;i++)
{
cin >> arr[i];
if(arr[i])
{
know_sum+=arr[i];
}
else {
unknow_num++;
}
}
for(int i=0;i<q;i++)
{
long long left,right;
cin >> left;
cin >> right;
func(left,right);
}
return 0;
}
三,小美的 MT
题目
MT 是美团的缩写,因此小美很喜欢这两个字母。
现在小美拿到了一个仅由大写字母组成字符串,她可以最多操作 𝑘 k次,每次可以修改任意一个字符。
小美想知道,操作结束后最多共有多少个'M'和'T'字符?
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
第一行输入两个正整数n,k,代表字符串长度和操作次数。
第二行输入一个长度为n的、仅由大写字母组成的字符串。
输出描述:
输出操作结束后最多共有多少个'M'和'T'字符。
示例1
输入例子: 5 2
MTUAN
输出例子: 4
例子说明: 修改第三个和第五个字符,形成的字符串为 MTTAM,这样共有 4 个'M'和'T'。
解题思路
我们只需统计 M 与 T 字符的数量,统计后加上修改次数,减去溢出大小即可。
代码
#include <iostream>
using namespace std;
int main()
{
string s;
int n,k;
cin >> n >> k;
cin >> s;
int ret=0;
for(int i=0;i<n;i++)
{
if(s[i]=='M' || s[i]=='T')
{
ret++;
}
}
ret+=k;
if(ret>n)
{
ret=n;
}
cout << ret << endl;
return 0;
}
四,小美的朋友关系
题目
小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友关系就淡忘了。
现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识?
事件共有 2 种:
1 u v:代表编号 u 的人和编号 v 的人淡忘了他们的朋友关系。
2 u v:代表小美查询编号 u 的人和编号 v 的人是否能通过朋友介绍互相认识。
注:介绍可以有多层,比如 2 号把 1 号介绍给 3 号,然后 3 号再把 1 号介绍给 4 号,这样 1 号和 4 号就认识了。
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
第一行输入三个正整数n,m,q,代表总人数,初始的朋友关系数量,发生的事件数量。
接下来的m行,每行输入两个正整数u,v,代表初始编号u的人和编号v的人是朋友关系。
接下来的q行,每行输入三个正整数op,u,v,含义如题目描述所述。 保证至少存在一次查询操作。
输出描述:
对于每次 2 号操作,输出一行字符串代表查询的答案。
如果编号 u 的人和编号 v 的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"No"。
示例1
输入例子: 5 3 5
1 2
2 3
4 5
1 1 5
2 1 3
2 1 4
1 1 2
2 1 3
输出例子: Yes
No
No
例子说明:
第一次事件,1 号和 5 号本来就不是朋友,所以无事发生。
第二次事件是询问,1 号和 3 号可以通过 2 号的介绍认识。
第三次事件是询问,显然 1 号和 4 号无法互相认识。
第四次事件,1 号和 2 号淡忘了。
第五次事件,此时 1 号无法再经过 2 号和 3 号互相认识了。
解题思路
我们可以使用并查集,存储朋友的关系,在查询时,我们确定关系时,通过寻找根结点,如果找到的根节点相同时,则代表他们可以成为朋友。
但是我们如何处理删除操作呢?我们采用逆向操作,我们接受并存储朋友间的关系,同时初始化并查集,即将他们对应的父节点设为他们自己。
然后我们接受操作输入并存储,如果操作数为 2 直接储存。如果操作数为 1 ,我们判断关系是否存在,不存在我们忽略操作,存在我们删除存储的关系同时存储。
接受操作后我们根据现有的的关系建立并查集,然后逆向遍历操作数组。如果操作数为 1 ,我们将关系加入并查集,如果操作数为 2 , 我们查询二者的关系并存储结果。遍历完成后,逆向输出结果。
代码
#include <iostream>
#include <map>
#include <set>
#include <vector>
using namespace std;
//并查集
map<int,int> fa;
typedef struct
{
int op;
int u;
int v;
}node;
int find(int x)
{
while(fa[x] != x) x = fa[x] = fa[fa[x]];
return x;
}
void merge(int u,int v)
{
int u_fa=find(u);
int v_fa=find(v);
if (u_fa != v_fa)
fa[u_fa] = v_fa;
}
int main()
{
int n,m,q;
cin >> n >> m >> q;
set<pair<int,int>> st; //关系集合
for(int i=0;i<m;i++)
{
int u,v;
cin >> u >> v;
st.insert({u,v}); //存储关系
fa[u]=u;
fa[v]=v;
}
node OP[q];
int num=0;
for(int i=0; i<q; i++)
{
int op, u, v; cin >> op >> u >> v;
//查询操作,可以直接存入
if(op == 1){
if(st.find({u, v}) != st.end()) st.erase({u, v});
else if(st.find({v, u}) != st.end()) st.erase({v, u});
else continue;
}
// 存入新的操作数组中
OP[num++] = {op, u, v};
}
//建立并查集
for(auto& e: st)
{
int u=e.first;
int v=e.second;
merge(u,v);
}
vector<bool> ret;
for(int i=num-1;i>=0;i--)
{
int op=OP[i].op,u=OP[i].u,v=OP[i].v;
if(op == 1)
{
merge(u,v);
}
else
{
if(!fa[u]) fa[u] = u;
if(!fa[v]) fa[v] = v;
if(find(u) == find(v))
ret.push_back(true);
else
ret.push_back(false);
}
}
for(int i=ret.size()-1;i>=0;i--)
{
if(ret[i])
cout << "Yes" <<endl;
else
cout << "No" <<endl;
}
return 0;
}
五, 小美的区间删除
题目
小美拿到了一个大小为n的数组,她希望删除一个区间后,使得剩余所有元素的乘积末尾至少有 k个 0。
小美想知道,一共有多少种不同的删除方案?
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
第一行输入两个正整数n,k。
第二行输入n个正整数a_i,代表小美拿到的数组。
输出描述:
一个整数,代表删除的方案数。
示例1
输入例子: 5 2
2 5 3 4 20
输出例子: 4
例子说明:
第一个方案,删除[3]。
第二个方案,删除[4]。
第三个方案,删除[3,4]。
第四个方案,删除[2]。
解题思路
乘积末尾的 0 数量取决于什么?末尾0的数量由因子2和5中较小的数量决定。所以我们可以通过两个前缀和数组,分别存储 5 与 2 的因子数量。
然后我们可以通过计算区间 [ i , j ] 外的5 与 2 的因子数量的较小值,计算出区间是否可删除。
我们使用双指针探测,找到可删除区间。
代码
#include <iostream>
#include <vector>
using namespace std;
int n,k;
int kzero(vector<int>& prev_two,vector<int>& prev_five,int left,int right)
{
return min(prev_five[n] - prev_five[right] + prev_five[left-1],prev_two[n] - prev_two[right] + prev_two[left-1]);
}
int main()
{
cin >> n >> k;
vector<int> x(n+1,0);
vector<int> prev_two(n+1,0);
vector<int> prev_five(n+1,0);
for(int i=1;i<=n;i++) cin >> x[i];
//统计5与2的前缀和
for(int i=1;i<=n;i++)
{
int five=0,two=0;
while(x[i]%5==0)
{
x[i]/=5;
five++;
}
while(x[i]%2==0)
{
x[i]/=2;
two++;
}
prev_two[i]=prev_two[i-1]+two;
prev_five[i]=prev_five[i-1]+five;
}
int l=0,r=0;
long long ans=0;
//搜素
for(int i=1, j=1; i<=n && j<=n;)
{
//找到或 i=j+1
while(j<=n && kzero(prev_two,prev_five,i,j)>=k) j++;
//[i,j-1]有效 || i==j则 [i,i]不符合
ans+=1LL*(j-i)*(j-i+1)/2;
//重合 删除 [i,r-1]
if(r>=i)
ans-=1LL*(r-i)*(r-i+1)/2;
l=i,r=j; //更新区间
//搜索
while(i<=j && kzero(prev_two,prev_five,i,j)<k)
i++;
j=max(i,j);
}
cout << ans ;
return 0;
}