
思路:直接预处理二维前缀和,然后对于每个符合情况的矩阵直接O(1)枚举求和即可
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=203;
char ss[N][N];
int s[N][N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%s",ss[i]+1);
for(int j=1;j<=n;j++)
{
if(ss[i][j]=='1') s[i][j]=1;
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
}
for(int len=1;len<=n;len++)
{
if(len%2==1)
{
printf("0\n");
continue;
}
int ans=0;
for(int i=len;i<=n;i++)
for(int j=len;j<=n;j++)
if(s[i][j]-s[i-len][j]-s[i][j-len]+s[i-len][j-len]==len*len/2)
ans++;
printf("%d\n",ans);
}
return 0;
}

思路:最小情况对应把所有0都替换为l,最大情况对应把所有0都替换为r
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long a[N];
int main()
{
int n,q;
cin>>n>>q;
int cnt=0;
long long sum=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum+=a[i];
if(a[i]==0) cnt++;
}
long long l,r;
while(q--)
{
scanf("%lld%lld",&l,&r);
printf("%lld %lld\n",sum+l*cnt,sum+r*cnt);
}
return 0;
}

思路:可以先把原来字符串中存在的M和T统计出来,然后如果其余字符小于等于k个那么直接输出n,否则再将其余字符中的k个换为MT即可
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
char s[N];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
scanf("%s",s+1);
int ans=0;
for(int i=1;i<=n;i++)
{
if(s[i]=='M'||s[i]=='T')
ans++;
}
ans=min(n,ans+k);
printf("%d",ans);
return 0;
}

思路:这道题的核心是离线处理+逆向思维
并查集只能加边不能删边,但题目要求动态删边并查询连通性
因此需要先读入所有操作,标记哪些边会被删除
初始只加入永远不会被删除的边
从后向前处理操作:删除操作变成添加边,查询操作直接回答
可行性:逆序处理后,所有删边都变成了加边,符合并查集特性且第i次查询时的图状态,等于逆序处理到第i次操作时的图状态
最后需要注意离散化
代码1:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int fu[N],u[N],v[N];
int op[N],op_u[N],op_v[N],ans[N];
map<pair<int,int>, int>mp;
vector<int> alls;//用于离散化
int find_alls(int x)
{
return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
int find(int x)
{
if(x!=fu[x]) return fu[x]=find(fu[x]);
return x;
}
int main()
{
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u[i],&v[i]);
alls.push_back(u[i]);
alls.push_back(v[i]);
}
for(int i=1;i<=q;i++)
{
scanf("%d%d%d",&op[i],&op_u[i],&op_v[i]);
alls.push_back(op_u[i]);
alls.push_back(op_v[i]);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
int len=alls.size();
for(int i=1;i<=len;i++)
fu[i]=i;
// 离散化初始关系
for(int i=1;i<=m;i++)
{
u[i]=find_alls(u[i]);
v[i]=find_alls(v[i]);
mp[{u[i],v[i]}]=mp[{v[i],u[i]}]=1;// 标记初始存在的关系
}
// 处理事件,标记哪些关系会被删除
for(int i=1;i<=q;i++)
{
op_u[i]=find_alls(op_u[i]);
op_v[i]=find_alls(op_v[i]);
if(op[i]==1&&mp[{op_u[i],op_v[i]}]==1)
mp[{op_u[i],op_v[i]}]=mp[{op_v[i],op_u[i]}]=2;//标记这段关系之前存在过
}
// 建立初始图:只加入那些永远不会被删除的边
for(int i=1;i<=m;i++)
{
if(mp[{u[i],v[i]}]==1)//证明关系存在且没被淡忘
{
int f_u=find(u[i]),f_v=find(v[i]);
if(f_u!=f_v) fu[f_u]=f_v;
}
}
// 逆序处理事件
for(int i=q;i>=1;i--)
{
int f_u=find(op_u[i]),f_v=find(op_v[i]);
if(op[i]==1&&mp[{op_u[i],op_v[i]}]==2)//他们之前必须存在朋友关系才能淡忘,否则无效
{
if(f_u!=f_v) fu[f_u]=f_v;
}
else if(op[i]==2)
{
if(f_u==f_v) ans[i]=1;//两者在同一个集合中
}
}
for(int i=1;i<=q;i++)
{
if(op[i]==2)
{
if(ans[i]==1) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}
代码2:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int fu[N],u[N],v[N];
int op[N],op_u[N],op_v[N],ans[N];
map<pair<int,int>, int>mp;
vector<int> alls;//用于离散化
int find_alls(int x)
{
return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
int find(int x)
{
if(x!=fu[x]) return fu[x]=find(fu[x]);
return x;
}
int main()
{
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u[i],&v[i]);
alls.push_back(u[i]);
alls.push_back(v[i]);
}
for(int i=1;i<=q;i++)
{
scanf("%d%d%d",&op[i],&op_u[i],&op_v[i]);
alls.push_back(op_u[i]);
alls.push_back(op_v[i]);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
int len=alls.size();
for(int i=1;i<=len;i++)
fu[i]=i;
// 离散化初始关系
for(int i=1;i<=m;i++)
{
u[i]=find_alls(u[i]);
v[i]=find_alls(v[i]);
mp[{u[i],v[i]}]=mp[{v[i],u[i]}]=1;// 标记初始存在的关系
}
// 处理事件,标记哪些关系会被删除
for(int i=1;i<=q;i++)
{
op_u[i]=find_alls(op_u[i]);
op_v[i]=find_alls(op_v[i]);
if(op[i]==1&&mp[{op_u[i],op_v[i]}]>=1)
{
mp[{op_u[i],op_v[i]}]++;//标记这段关系之前存在过
mp[{op_v[i],op_u[i]}]++;
}
}
// 建立初始图:只加入那些永远不会被删除的边
for(int i=1;i<=m;i++)
{
if(mp[{u[i],v[i]}]==1)//证明关系存在且没被淡忘
{
int f_u=find(u[i]),f_v=find(v[i]);
if(f_u!=f_v) fu[f_u]=f_v;
}
}
// 逆序处理事件
for(int i=q;i>=1;i--)
{
int f_u=find(op_u[i]),f_v=find(op_v[i]);
if(op[i]==1&&mp[{op_u[i],op_v[i]}]>=2)//他们之前必须存在朋友关系才能淡忘,否则无效
{
if(mp[{op_u[i],op_v[i]}]==2)//恰好是这次删除操作导致断裂
{
if(f_u!=f_v) fu[f_u]=f_v;
}
mp[{op_u[i],op_v[i]}]--;
mp[{op_v[i],op_u[i]}]--;
}
else if(op[i]==2)
{
if(f_u==f_v) ans[i]=1;//两者在同一个集合中
}
}
for(int i=1;i<=q;i++)
{
if(op[i]==2)
{
if(ans[i]==1) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}
上述两个代码区别:
两个代码都能AC,但第一个代码弱一些,题目中并未涉及到对一段关系多次淡化的操作,比如:
操作1: 1 1 2 (第一次淡忘)
操作2: 2 1 2 (查询)
操作3: 1 1 2 (第二次淡忘同一个关系)
在代码1中如果倒序的加边会发现,在操作3后会恢复1和2的关系,那么查询1和2就是Yes,但实际上已经在操作1中淡忘了,所以应该输出No,这是代码数据中没有涉及的问题,因此为了处理这个问题,我们应该在第一次淡忘的时候才选择加边,因此我在代码2中利用int型变量来记录淡忘次数,当等于2(代表第一次淡忘时才选择恢复关系)

思路:乘积末尾至少有k个0意味着乘积中的质因子2和质因子5都至少含有k个
那么可以直接预处理前缀和后缀2的质因子数以及前缀5和后缀5的质因子数
接着枚举区间左端点,由于是乘积末尾至少含有k个0,因此我们删除区间越短会导致乘积含有更多的0 ,因此区间长度具备二分性,因此可以二分解决
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int pre_2[N],pre_5[N],suf_2[N],suf_5[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pre_2[i]=pre_2[i-1];
pre_5[i]=pre_5[i-1];
int t=a[i];
while(t%2==0)
{
t/=2;
pre_2[i]++;
}
while(t%5==0)
{
t/=5;
pre_5[i]++;
}
}
for(int i=n;i>=1;i--)
{
suf_2[i]=suf_2[i+1];
suf_5[i]=suf_5[i+1];
int t=a[i];
while(t%2==0)
{
t/=2;
suf_2[i]++;
}
while(t%5==0)
{
t/=5;
suf_5[i]++;
}
}
long long ans=0;
for(int i=1;i<=n;i++)//枚举删除区间的左端点
{
int need_2=k-pre_2[i-1],need_5=k-pre_5[i-1];//记录后缀需要的2和5的个数
int l=i-1,r=n;
while(l<r)
{
int mid=l+r+1>>1;
if(suf_2[mid+1]>=need_2&&suf_5[mid+1]>=need_5) l=mid;
else r=mid-1;
}
// [i,l]任意选一个作为删除区间右端点即可
ans+=l-i+1;
}
printf("%lld",ans);
return 0;
写在最后:时间过得真快,上一次记录题解时还是在大学,转眼间已经要到暑期实习找工作了,之后会不定期更新一些大厂笔试题解,希望能和大家多多交流。