1.a题 模拟
思路:
分四种情况进行考虑即可
2.b题 贪心
题意:
一个n*m网格,某个位置有一格“黑洞”,所有和黑洞的曼哈顿距离小于等于s的格子都不能踩否则会被吸进去,求从左上角到右下角的最短步数,若不能输出-1
思路:
题目只保证起点不在黑洞范围,故先判断终点是否在黑洞范围中。注意到只要不走回头路,到达终点步数就一样,不难发现沿着网格边缘到终点的两条路(左上、右下)一定是最优解,假如这两条路都被阻断,其他的任意一条路径都不可能到达终点。
证明:
可以想象一个以黑洞为中心的十字架,若两条最优路径都不行,十字架必然会把起点与终点完全隔开。
完整代码
#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define makp make_pair
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int Mod=1e9+7,N=2e5+10;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
void acc()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
}
int n,m,sx,sy,d;
// 1111:3
// 211:2
// 31:1
// 4:0
int sol()
{
if(abs(sx-n)+abs(sy-m)<=d)return -1;
if(sx-d>1&&sy+d<m)return n+m-2;
if(sx+d<n&&sy-d>1)return n+m-2;
return -1;
}
int main()
{
int t;
cin>>t;
while(t--){
cin>>n>>m>>sx>>sy>>d;
cout<<sol()<<endl;
}
}
3.c题 贪心
题意:
数组a(单调不减)每个数加上一个非负增量再正序排列后得到数组b(单调不减),现在给你数组a、b,依次询问你增量数组d的每个元素的最大、最小可能值(每个询问间相互独立)。
思路:
注意到a数组中的下标为i的元素加上d[i]后对应到b数组的元素下标j未必满足i==j,那么我们的任务就是找到某种对应方式使得d[i]最大或最小。
发现让a、b数组对应元素下标相等,是一定符合题意的,这一点用反证法即可。
求d[i]最小可能值:对于每个下标i,找d[i]min就是找到bi数组第一个大于等于a[i]的元素下标j,并且让b[j]与a[i]对应。可以证明,这样是一定可行的。
求d[i]的最大可能值:我们可不可以直接找到b数组最大的元素让其与a[i]对应,这样d[i]max=b[j]max-a[i]?答案是不可以。如a=[1 2 3],b=[1 2 4] 。这是因为,如果让a[i]和b[j]对应,那么i到j之间的元素a[k]的最优解就是集体向前错位,与b[k-1]对应(如下图所示),而我们知道,b[k-1]可能小于a[k],这与d[k]>=0矛盾。所以我们要二分得到最大的j,使得i到j之间的元素都满足b[k-1]>=a[k]。这可以用前缀和进行优化。
完整代码
#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define makp make_pair
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int Mod=1e9+7,N=2e5+10;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
void acc()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
}
int n,a[N],b[N],sum[N];
bool check(int x0,int x1){
return (sum[x1]-sum[x0]==x1-x0);
}
int main()
{
int t;
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=2;i<=n;i++){
if(a[i]<=b[i-1])sum[i]=sum[i-1]+1;//可补位
else sum[i]=sum[i-1];
}
for(int i=1;i<=n;i++){
cout<<b[lower_bound(b+1,b+1+n,a[i])-b]-a[i]<<" ";
}
cout<<endl;
for(int i=1;i<=n;i++){
int l=i,r=n;
while(l<r){
int mid=l+r+1>>1;
if(check(i,mid))l=mid;
else r=mid-1;
}
cout<<b[l]-a[i]<<" ";
}
cout<<endl;
}
}
4.d题 贪心
题意:
已知数组a、b,而数组c满足c[i] = a[i] ^ b[i] (^是异或)。让你适当排列数组b,求得到的数组c所有元素相与(&)的最大值。
思路:
设数组c所有元素相与(&)的结果为res,不妨考虑某一位,假设res第j位为1,说明对任意i,a[i]、b[i]恰有一个第j位为0,一个第j位为1,等价于a数组第j位为1的个数=b数组第j位为0的个数,数学表示:a&(2^j) ^ b&(2^j)=1 (式子1)
为了让res更大,我们贪心地从高位到低位枚举,看每一位是否满足a数组该位为1的个数=b数组该位为0的个数,若满足,则将a数组的1与b数组的0分在一组,a数组的0与b数组的1分在一组。以此类推。
然而需要注意的是,本题中每一位之间其实并不独立,当我们针对某一位进行分组时,会对每一位进行同样的分组操作。
一种解法是模拟,每次分组,相当于产生了一个新组,只需要用变量维护组数信息即可;利用map,可以把同一组的元素集合在一起,进而判断该位是否满足。这种做法常数较大。
另一种解法稍微转了个弯。设截止到第j位时,之前所有满足的位为[k1,k2,…,km],发现第j位满足,等价于对任意的i,a[i],b[i]的[k1,k2,…,km,j]这些位相反。数学表示为:a[i]&binary(k1,k2,…,km,j) ^ b[i]&binary(k1,k2,…,km,j) = binary(k1,k2,…,km,j) ,发现这个式子与上面的式子1类似,因此,我们只需对a,b数组按a[i]&binary(k1,k2,…,km,j)分组,对于某一组判断其是否与对应组元素相等即可。
解法1:完整代码(较麻烦)
#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define makp make_pair
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int Mod=1e9+7,N=2e5+10;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
void acc()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
}
int n,a[N],b[N],cnta[35][N],cntb[35][N],gra[N],grb[N],idx,power[35];
int main()
{
acc();
int t;
cin>>t;
//
int pp=1;
for(int j=0;j<=30;j++){
power[j]=pp;
pp<<=1;
}
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
//ini
int ans=0;
for(int i=1;i<=n;i++)
for(int j=0;j<30;j++)cnta[j][i]=cntb[j][i]=0;
for(int i=1;i<=n;i++)gra[i]=grb[i]=0;
idx=0;
//process
for(int i=1;i<=n;i++){
int x=a[i];
for(int j=0;j<30;j++){
if(x&1)cnta[j][i]++;
x>>=1;
}
x=b[i];
for(int j=0;j<30;j++){
if(x&1)cntb[j][i]++;
x>>=1;
}
}
//solve
for(int k=29;k>=0;k--){
map<int,vector<int>>mpa,mpb;
for(int i=1;i<=n;i++){
mpa[gra[i]].pb(i);
mpb[grb[i]].pb(i);
}
//统计是否每组都满足cnta+cntb=组中元素个数
bool flag=1;
for(auto i:mpa)
{
int grid=i.fi;
vector<int> va=i.se;
vector<int> vb=mpb[grid];
int cnt=0;
for(int j:va)cnt+=cnta[k][j];
for(int j:vb)cnt+=cntb[k][j];
if(cnt!=va.size()){
flag=0;
break;
}
}
if(!flag)continue;//假如存在某组不满足,则该位不可以
ans+=power[k];//维护答案
//分组
for(auto i:mpa)
{
int grid=i.fi;
vector<int> va=i.se;
vector<int> vb=mpb[grid];
if(va.size()+vb.size()<=2)continue;//人数不够,不分组
idx++;
for(int j:va)
if(cnta[k][j])gra[j]=idx;
for(int j:vb)
if(!cntb[k][j])grb[j]=idx;
}
}
cout<<ans<<"\n";
}
return 0;
}
解法2:完整代码
#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define makp make_pair
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int Mod=1e9+7,N=2e5+10;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
void acc()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
}
int n,a[N],b[N],power[35];
int main()
{
int t;
cin>>t;
//
int pp=1;
for(int j=0;j<=30;j++){
power[j]=pp;
pp<<=1;
}
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
//ini
int ans=0;
for(int j=29;j>=0;j--){
int res=ans|power[j];
bool flag=1;
map<int,int> mpa,mpb;
for(int i=1;i<=n;i++)mpa[a[i]&res]++;
for(int i=1;i<=n;i++)mpb[b[i]&res]++;
for(auto i:mpa){
int x=i.fi,cnt=i.se;
int cnt1=mpb[x^res];
if(cnt!=cnt1){
flag=0;
break;
}
}
if(flag)ans=res;
}
cout<<ans<<"\n";
//a ++ b = c
//a++c=a++a++b=0++b=b
}
return 0;
}
5.e题 KMP+优化
题意:
给你一个模式串s,现在有多次询问,每次给你一个新串s’,求将其拼在s后面,其每个下标的最大公共前后缀。
思路:
最大公共前后缀必然想到kmp,我们考虑对模式串s用kmp进行预处理,然后每次询问时,对于新拼接的串,从拼接处向后执行kmp。根据kmp算法的意义,这样得到的结果就是拼接串的最大公共前后缀。
但很不幸,这样是不够的,虽然新串很短,但模式串s很长,一次询问的最坏复杂度达到了惊人的O(n+m)。这是为啥呢?发现问题主要是 while(j&&s[i-1]!=s[j])j=ne[j],如果一开始的j很大,j减小的步幅很小,就会使得复杂度趋于n+m;如果我们能让这个循环变为O(1),那么一次询问的复杂度只有O(m),m很小。
具体做法是:维护一个数组negood[i][j],表示待匹配位下标为j,字母为i时,上述循环执行到终点时j的新下标j’,这样就可以让循环一步到位。维护negood数组的方法就是从前往后递推的思想,类似于dp
实现的时候注意由于模式串和拼接串要维护2个ne数组,所以要对原kmp算法做一些小修改,如下文的get_s和funcne函数
完整代码
#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define makp make_pair
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int Mod=1e9+7,N=1e6+100;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
int n,ne[N],m,negood[30][N];
bool st[N];
string s;
void acc()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
}
char get_s(int x,string s1)
{
if(x<n)
{
return s[x];
}
else return s1[x-n];
}
int funcne(int x,int y)
{
if(x<n){
//cout<<x<<" "<<y<<" "<<negood[y][x]<<endl;
return negood[y][x];
}
else return ne[x];
}
int main()
{
//考虑预处理26个英文字母
cin>>s;
n=s.length();
//求ne函数
for(int i=2,j=0;i<=n;i++)
{
while(j&&s[i-1]!=s[j])j=ne[j];//匹配不了,就后退,直到匹配成功或无路可退
if(s[j]==s[i-1])j++;//匹配了,进度++
ne[i]=j;//记录最大公共前后缀,即ne[i]
}
//求negood,一步到位
for(int i=2;i<=n;i++){
int tmp=i;
tmp=ne[tmp];
negood[s[tmp]-'a'][i]=tmp;
for(int j=0;j<26;j++)negood[j][i]=max(negood[j][i],negood[j][tmp]);
}
int t;
cin>>t;
while(t--)
{
string s1;
cin>>s1;
m=s1.length();
int ans[15];
for(int j=ne[n],i=1;i<=m;i++)
{
while(j&&s1[i-1]!=get_s(j,s1))j=funcne(j,s1[i-1]-'a');//匹配不了,就后退,直到匹配成功或无路可退
if(s1[i-1]==get_s(j,s1))
{
//cout<<j<<" "<<s1[i-1]<<" "<<get_s(j,s1)<<endl;
j++;//匹配了,进度++
}
ne[i+n]=j;//记录答案
}
for(int i=1;i<=m;i++)cout<<ne[i+n]<<" ";
cout<<"\n";
}
return 0;
}