T1.01序列
思路:
题目要求我们找的是子串中1的个数为k的情况有多少,我们当然可以枚举字串的长度,然后遍历字串的组成情况,如果满足要求则计数器++,但这样的时间复杂度为O(n^2),在这题1e6的数据下显然是会超时的,所以我们想办法把题目转化一下。
既然是子串,那就说明是连续的,且字符串除了0就是1,那么我们可以用前缀和的思路来写,这样问题就变成了,区间和为k的情况有多少。
我们计算子串的前缀和数组,并把相同前缀和的数量用哈希表记录下来。每一个前缀和为sum的位置,都能和所有前缀和为sum-k的位置形成好子串,所以我们计数器记录的是(前缀和为sum的数量 * 前缀和为sum-k的数量)。最后只要把计数器输出即可。
即k=0的情况,如果k=0,那么我们记录的子串数量就是(前缀和为sum的情况 * 前缀和为sum的情况),这显然是不对的,会出现重复的好子串。所以遇到k=0时我们应该特殊处理,它的子串数为:连续ans个0组成的子串,它能形成的不同好子串为:(ans+1) * ans/2。
代码:
#include<bits/stdc++.h>
using namespace std;
long long k,ans,p[1000005];
map<int,long long>tot;
string s;
int main()
{
cin>>k>>s;
long long n=s.size();
if(k==0)
{
for(long long i=0;i<n;i++)
if(s[i]=='0')
{
long long st=i,ed=i;
while(s[ed]=='0')ed++;
long long sum=ed-st;
ans=ans+(sum*(sum+1)/2);
i=ed;
}
cout<<ans;
return 0;
}
tot[0]++;//记得初始化
for(long long i=1;i<=n;i++)
{
p[i]=p[i-1]+(s[i-1]-'0');//前缀和
tot[p[i]]++;//tot[i]->前缀和为i的列数量
}
long long cnt=0;
while(tot[cnt+k]!=0)
{
ans+=tot[cnt]*tot[cnt+k];
cnt++;
}
cout<<ans;
}
T2.出栈序列判断
思路:
所以可以直接来push栈顶top的大小,直到与x相等后pop。只有top小于x的情况下才会push,当top大于等于x时直接pop就行
但是这题要输出的次数太多,所以要用'\n'来换行,用endl会超时,最好加一个输出优化
代码:
#include<bits/stdc++.h>
using namespace std;
int n,out[100005];
stack<int>s;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++)
cin>>out[i];
s.push(1);
cout<<"push 1"<<'\n';
int idx=1,now=2;
for(int i=1;i<2*n;i++)
{
if(!s.empty())
{
int tmp=s.top();
if(tmp==out[idx])
{
cout<<"pop"<<'\n';
idx++;
s.pop();
}
else
{
s.push(now);
cout<<"push "<<now<<'\n';
now++;
}
}
else
{
s.push(now);
cout<<"push "<<now<<'\n';
now++;
}
}
}
T3.加一
思路:
一开始没写出来,看了别人的题解才发现是用动态规划做..
大概的思路就是先打个表,找到0-9进行2e5轮加法后的长度,然后对于每次输入都直接打表的数据乘上给定数中0-9的个数就行了
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int t,ans,dp[200010][10];//dp[i][j]原本j数位经过i次操作变成dp[i][j]位个数
int main()
{
ios::sync_with_stdio(false);
cin>>t;
for(int i=0;i<=9;i++)dp[0][i]=1;
for(int i=1;i<=200005;i++)
{
for(int j=0;j<=9;j++)
dp[i][j]=dp[i-1][j+1];
dp[i][9]=(dp[i-1][1]+dp[i-1][0])%mod;
}
while(t--)
{
char s[20];
int m,ans=0;
cin>>s>>m;
int len=strlen(s);
for(int i=0;i<len;i++)
{
ans+=dp[m][s[i]-'0'];
ans%=mod;
}
cout<<ans<<'\n';
}
T4.路径计数
思路:
是一道dp题目,每个格子的到达方式只有两种,要么从左侧,要么从上方
所以要知道到达map[n][n]的方法数,只需要知道到达map[n - 1][n]和map[n][n - 1]的方法数即可
依此类推,再考虑一下障碍物的存在,动态规划的状态转移方程就有了
但是初始化状态转移方程的时候要注意一旦有障碍这一次的初始化就无法进行下去了
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,mp[105][105],dp[105][105];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>mp[i][j];
for(int i=1;i<=n;i++)
{
if(mp[1][i]==1)dp[1][i]=1;
else break;//!!!!
}
for(int i=1;i<=n;i++)
{
if(mp[i][1]==1)dp[i][1]=1;
else break;//!!!!
}
for(int i=2;i<=n;i++)
for(int j=2;j<=n;j++)
if(mp[i][j]==1)
dp[i][j]=(dp[i-1][j]%mod+dp[i][j-1]%mod)%mod;
cout<<dp[n][n];
}
T5.任务分配
思路:
假设现在我们已经完成了前 i 个任务,我们需要选择下一个任务来执行。我们发现在第 i 个任务结束的时间点之前,有很多任务都已经开始了,但是我们之前并没有选择执行它们。如果这些任务的收益都比当前选择的任务少,那么我们在这个时间点之前选择它们并不会带来更多的收益。因此,我们只需要选择当前时间点之后开始,收益最大的那个任务。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,st[1005],ed[1005],val[1005],dp[1005];//前i时间最大收益
int main()
{
int maxT=0;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>st[i]>>ed[i]>>val[i];
maxT=max(maxT,ed[i]);
}
for(int i=1;i<=maxT;i++)
{
dp[i+1]=max(dp[i],dp[i+1]);//啥都不干
for(int j=1;j<=n;j++)
if(st[j]==i)//如果这个时刻有任务刚开始
dp[ed[j]]=max(dp[i]+val[j],dp[ed[j]]);//做这个任务
}
cout<<dp[maxT];
}
T6.跳跳
思路:
对于一个点 (x, y),我们考虑在其周围所有点上使用魔法使其能够到达该点。如果能够找到一种魔法,使得所有的点都能到达该点,那么该魔法就是一种合法的解。
因此,我们可以考虑枚举每个点 (x, y),并计算出它和其他点之间的距离(即在这个点上使用魔法需要的 A 和 B 的值),然后使用并查集或者哈希表等数据结构记录所有距离相同的点,最后找到一个距离相同的点集合,其大小大于等于所有点数的一半即可。
具体地,我们可以对于每个点 (x, y),计算它和其他点之间的距离 (a, b),然后将这些距离相同的点加入到一个哈希表中,最后遍历哈希表,找到一个大小大于等于所有点数的一半的距离相同的点集合即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,ans,x[505],y[505];
int main()
{
cin>>n;
ans=n*(n-1);
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i];
for(int i=1;i<=n;i++)//从第i个魔法阵传送到第j个魔法阵
for(int j=1;j<=n;j++)
{
if(j==i)continue;
int xPlus=x[i]-x[j],yPlus=y[i]-y[j];
for(int k=1;k<=n;k++)
{
if(k==i||k==j)continue;
if((x[k]-x[i])*yPlus==(y[k]-y[i])*xPlus)
ans--;
}
}
cout<<ans;
}//有问题..这明显错了还ac..ww
//应该这样
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<map>
#include<unordered_map>
#include<stack>
typedef long long ll;
typedef pair<ll, ll>PII;
const int MOD = 1e9 + 7;
const int N = 200010;
ll f[N+50][10];
int gcd(int x, int y)
{
while (y ^= x ^= y ^= x %= y);
return x;
}
int main()
{
ll n, x, y;
vector<PII>v;
cin >> n;
map<PII, int>mymap;
for (int i = 0; i < n; i++)
{
cin >> x >> y;
v.push_back({ x,y });
}
ll res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (j == i)continue;
int x = v[j].first - v[i].first;
int y = v[j].second - v[i].second;
if (x == 0 || y == 0)
{
x = x != 0 ? x / abs(x) : x;
y = y != 0 ? y / abs(y) : y;
}
else
{
int num = abs(gcd(x, y));
x /= num;
y /= num;
}
if (mymap[{x, y}] == 0)
{
mymap[{x, y}] = 1;
res++;
}
}
}
cout << res << endl;
return 0;
}
T7.网络判断
思路:
本题是一道比较经典的二维矩阵问题,可以通过统计每一行每一列黑白正方形的数量来判断矩阵是否正确。同时,由于行和列不能有连续三个及以上相同颜色的正方形,可以使用哈希表等数据结构来判断是否有连续三个及以上相同颜色的正方形。
具体来说,对于每一行和每一列,我们可以分别用两个变量 $b$ 和 $w$ 分别表示黑色和白色正方形的数量,如果在遍历的过程中发现有连续三个及以上相同颜色的正方形,就将这一行或者这一列标记为不正确。最后再判断每一行和每一列的 $b$ 和 $w$ 的数量是否相等即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int n;
char mp[25][25];
bool sameR(int r)
{
int cntb=0,cntw=0;
for(int i=0;i<n;i++)
{
if(mp[r][i]=='W')cntw++;
else cntb++;
}
return cntb==cntw;
}
bool sameC(int c)
{
int cntb=0,cntw=0;
for(int i=0;i<n;i++)
{
if(mp[i][c]=='W')cntw++;
else cntb++;
}
return cntb==cntw;
}
bool sameThree()
{
bool flag2=true;
for(int i=0;i<n;i++)
for(int j=0;j<n-2;j++)//不能用连续'== '
if((mp[i][j]==mp[i][j+1]&&mp[i][j+1]==mp[i][j+2])||(mp[j][i]==mp[j+1][i]&&mp[j+1][i]==mp[j+2][i]))flag2=false;
return flag2;
}
int main()
{
bool flag=true;
cin>>n;
for(int i=0;i<n;i++)
cin>>mp[i];
for(int i=0;i<n;i++)
if(!sameR(i)||!sameC(i))
flag=false;
if(!sameThree())flag=false;
cout<<flag;
}
T8.序列维护
思路:
这道题可以使用来实现,链表支持插入和删除操作,而查询只需要遍历链表即可。具体来说,每个节点包含一个值和指向下一个节点的指针,插入操作在指定位置插入一个新的节点,删除操作直接删去目标节点,查询操作从链表头开始遍历,找到第 k 个节点即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int m,s[1005];
int main()
{
cin>>m;
for(int i=1;i<=m;i++)
{
string func;
cin>>func;
if(func=="insert")
{
int x,y;
cin>>x>>y;
for(int i=x+1;i<1005;i++)
s[i+1]=s[i];
s[x+1]=y;
}
if(func=="delete")
{
int x;
cin>>x;
for(int i=x+1;i<=1005;i++)
s[i-1]=s[i];
}
if(func=="query")
{
int k;
cin>>k;
cout<<s[k]<<endl;
}
}
}
//vector做法
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int a[N];
vector<int>v;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin>>n;
string s;
int start,val;
while(n--)
{
cin>>s;
if(s=="insert")
{
cin>>start>>val;
v.insert(v.begin()+start,val);//
}
else if(s=="delete")
{
cin>>start;
v.erase(v.begin()+start-1);//
}
else if(s=="query")
{
cin>>start;
cout<<v[start-1]<<endl;
}
}
return 0;
}
T9.异或和或
思路:
观察题目操作,发现它们是对序列的每一位独立进行操作,那么我们只需要对每一位分别进行操作即可。
先对每一位按位异或,如果有任意一位 s 和 t 不同,那么就无法操作成功,直接输出 NO。
如果所有位都相同,那么对于每一位,如果 t 该位上是 1,那么我们只需要在 s 该位上进行或操作即可。如果 t 该位上是 0,那么我们不需要操作该位。
代码:
#include<bits/stdc++.h>
using namespace std;
int t;
string st,ed;
int main()
{
cin>>t;
while(t--)
{
cin>>st>>ed;
if(st.size()!=ed.size())
{
cout<<"NO"<<endl;
continue;
}
int stOne=0,stZero=0,edOne=0,edZero=0;
for(int i=0;i<st.size();i++)//记得下标从零开始
{
if(st[i]=='1')stOne++;//is '1' not 1!!
else stZero++;
if(ed[i]=='1')edOne++;
else edZero++;
}
if((edOne==0&&stOne!=0)||(edOne!=0&&stOne==0))cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
}
T10.最大和上升子序列
思路:
定义状态 dp[i]表示以 ai结尾的最长上升子序列的和,则状态转移方程为:
dp[i]=max(dp[j])+ai(j<i,aj<ai)
代码:
#include<bits/stdc++.h>
using namespace std;
int n,a[1005],dp[1005];//
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i]=a[i];
}
int maxn=a[1];
for(int i=2;i<=n;i++)//枚举终点
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+a[i]);
maxn=max(dp[i],maxn);
}
cout<<maxn;
}