//由于本题数据量较大,使用常规的O(n^2)的LIS算法会超时
//故本题采用二分+贪心的算法求解:
//维护一个low数组,low[i]表示长度为i的最长上升子序列的末尾元素
//使用贪心思想来对low数组进行更新,核心思想是末尾元素越小,越容易凑出最长的子序列
//遍历每一个元素,若当前元素比low[i]更大,则直接加入low数组的末尾
//若当前元素小于low[i],则从low数组中找到一个大于等于它的最小值将其替换
//由于low数组是递增的,故使用二分算法进行搜索和替换
//最终输出low数组的长度即为答案
//例1:对于序列arr[8]={3,1,2,6,4,5,10,7}
//扫描arr[1],low[1]=3;扫描arr[2],low[1]替换为1;扫描arr[3],low[2]=2;扫描arr[4],low[3]=6
//扫描arr[5],low[3]替换为4;扫描arr[6],low[4]=5;扫描arr[7],low[5]=10;扫描arr[8],low[5]替换为7
//最终low数组长度为5,即为答案,此时low[5]={1,2,4,5,7}
//例2:对于序列arr[8]={1,4,7,2,5,9,10,3}
//扫描arr[1],low[1]=1;扫描arr[2],low[2]=4;扫描arr[3],low[3]=7;扫描arr[4],low[2]替换为2
//扫描arr[5],low[3]替换为5;扫描arr[6],low[4]=9;扫描arr[7],low[5]=10;扫描arr[8],low[3]替换为3
//最终low数组长度为5,即为答案,此时low[5]={1,2,3,9,10};
//注意此算法的缺陷:low数组只有长度是有意义的,其保存的元素是无意义的
//比如上例中的low={1,2,3,9,10},原序列中不存在此子序列
//实际上low数组中每个元素只代表一种排列可能性,不代表最终的排列结果
#include <bits/stdc++.h>
using namespace std;
int arr[300009];
int low[300009];
int main(){
int N;int length=1;
cin>>N;
for(int i=1;i<=N;i++)cin>>arr[i];
low[1]=arr[1];//初始化,长度为1的子序列初始化为第一个元素
for(int i=2;i<=N;i++)//依次遍历后面的元素,更新low数组
{
if(arr[i]>low[length])//若当前元素大于low数组末尾元素,直接插入
{
low[++length]=arr[i];
}
else{ // 当arr[i]介于low[1]和low[length]之间时,在low数组中进行二分查找并替换
int l = 1, r = length;
while (l < r){
int mid = (l + r) / 2;
if (low[mid] >= arr[i])
r = mid;
else
l = mid + 1;
}
low[r] = arr[i]; // 将low数组中第一个大于等于arr[i]的元素替换为arr[i]
}
}
cout<<length<<'\n';//输出low数组长度即为答案
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+6;
int a[N],b[N];
int n,m;
int dp[N][N];//dp[i][j]表示a的前i个字符 b的前j个字符的相同序列
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
cout<<dp[n][m];
return 0;
}
14.二进制中 1 的个数 - 蓝桥云课 (lanqiao.cn)
#include <bits/stdc++.h>
using namespace std;
int main(){
unsigned int n;cin>>n;
int ans=0;
while(n){
if(n&1==1)ans++;
n>>=1;
}
cout<<ans;
return 0;
}
//本题是一道字典树的模板题
//字典树是一种高效率存储多个字符串的数据结构
//其每个结点的权值代表以该结点结尾的字符串的数量,每条边存储一个字符
//从根结点开始,按某一路径遍历到某一结点,即得到一种字符串,其个数等于当前结点存储的数值
//如从根结点开始,依次走过'a''b''c'三条边到达9号结点,9号结点保存的数字是3
//则得到字符串"abc",其数量为3个
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+100;
int nex[N][27];//nex[i][0]表示从结点i出发,边为'a'的下一个结点地址(假设字符串全由小写字母构成)
//如1号结点与2号结点间存在一条记录字母'a'的边,则nex[1]['a'-'a']=2
//如8号结点与9号结点间存在一条记录字母'c'的边,则nex[8]['c'-'a']=9
int cnt[N];//cnt[i]表示以结点i结尾的字符串的数量,即每个结点的权值
int idx=2;//用于动态开点,初始时只有一个根结点1
void insert(char *S)//在字典树中插入字符串S的信息
{
int x=1;//x表示结点编号,初始从根结点(1号)开始
for(int i=0;S[i]!='\0';i++)//遍历字符串S
{
//先检查x是否存在S[i]的边
if(nex[x][S[i]-'a']==0)//从结点x出发,目前还没有记录当前字母的边
{
nex[x][S[i]-'a']=idx++;//则新建一个边记录之,同时动态开点
}
x=nex[x][S[i]-'a'];//到达下一个结点编号
}
//cnt[x]++;
//最终x到达字符串末尾字符对应的结点上,其计数值加1
}
bool check(char *T)//在字典树中查找字符串T(计算出现的次数)
{
int x=1;//x表示结点编号,初始从根结点(1号)开始
for(int i=0;T[i]!='\0';i++)//遍历字符串T
{
x=nex[x][T[i]-'a'];//根据当前字符,x不断向下追溯,最终到达结尾
//若不存在这个字符(记录这个字符的边),则x=0,后续x将一直为0
}
//return cnt[x];//返回字符串T出现的次数,即结尾字符对应的结点所记录的权值
return x;//本题返回x即可,只需判断x是否为0
}
int main(){
int n,m;
cin>>n>>m;
while(n--)//N个字符串
{
char S[N];
cin>>S;
insert(S);//每输入一个字符串,就将其信息插入字典树
}
while(m--)//M个询问
{
char T[N];
cin>>T;
if(check(T))cout<<"Y"<<endl;//在字典树中找到T,输出Y
else cout<<"N"<<endl;//没找到,输出N
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+6;
int st[N][22];//st[i][j]表示从i开始2^j个数的区间最大值
int n,q;
int a[N];
int get_max(int l,int r){
int k=log(r-l+1)/log(2);//长度2^k
//右端点是r,长度为2^k时候 左端点是r-2^k+1
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;j<=20;j++){//枚举区间长度
for(int i=1;i<=n;i++){//枚举左端点
if(i+(1<<j)-1<=n){//右端点合法 //分出来的右端点的右边
st[i][j]=max(st[i][j-1],st[(1<<(j-1))+i][j-1]);
}//
}
}
while(q--){
int l,r;cin>>l>>r;
cout<<get_max(l,r)<<'\n';
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+7;//开2倍
char S[N],T[N];
int p[N];//表示回文半径
int main(){
cin>>S+1;int n=strlen(S+1);
for(int i=2*n+1;i>=1;i--){
int t=i;
T[t]=(t&1)?'#':S[t>>1];
}//用一个字符串也可以
T[0]='^',T[2*n+2]='$';
int C=0,R=0;
for(int i=1;i<=2*n+1;i++){//R表示当前已知的回文子串的最右边界位置
// 如果 i < R,说明当前位置 i 在当前已知的回文子串范围内。此时,R-i 表示当前位置 i 到已知回文子串右边界 R 的距离。
// 如果 R-i 的值更小,说明当前位置 i 距离已知回文子串右边界更近,因此可以直接利用该距离更新当前位置 i 处的回文半径
p[i]=(i<R)?min(R-i,p[2*C-i]):1;//保证合法 右端点不超出R因为R右边不知道
// 且长度至少是1
while(T[i+p[i]]==T[i-p[i]])p[i]++;//再前面的基础上 半径还可以更大
if(i+p[i]>R){//下一个右边的顶点已经顶到'$'了
C=i;//更换中心
R=i+p[i];//更换右端点
}
}
int ans=0;
for(int i=1;i<=2*n+1;i++)ans=max(ans,p[i]-1);
cout<<ans;
return 0;
}
//这是一道区间dp的模板题
//可以将一个大区间的问题拆分成若干个子区间合并的问题
//两个连续的子区间可以进行合并成一个大区间
//基本步骤为:
//1、从小到大枚举区间长度
//2、对每一个区间长度,枚举所有可能的区间(左右端点)
//3、对每一个区间,枚举所有可能的分割点,以分割点为界将其划分为两个子区间
//4、计算这两个子区间的合并代价
//5、通过比较所有的子区间组合,求出当前区间的最优值
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=250;
int stone[maxn];
int dp[maxn][maxn];
//dp[i][j]表示将[i,j]的区间合并为一堆所需的最小代价
signed main(){
int n;
cin>>n;
memset(dp,0x3f,sizeof(dp));//由于不断更新最小值,故dp数组初始化为极大值
for(int i=1;i<=n;i++)
{
cin>>stone[i];
dp[i][i]=0;//初始化:对于单独的一堆石头,合并代价是0(无需合并)
stone[i]+=stone[i-1];//计算前缀和,便于后续计算合并代价
}
for(int len=2;len<=n;len++)//枚举所有可能的区间长度2~n
{
for(int i=1;i+len-1<=n;i++)//枚举所有可能的左端点
{
int j=i+len-1;//j为对应的右端点
for(int k=i;k<j;k++)//枚举区间[i,j]中的所有分割点k
{
//k将区间分成[i,k]和[k+1,j]两部分
//先将[i,k]合并为一堆,代价为dp[i][k];再将[k+1,j]合并为一堆,代价为dp[k+1][j]
//最后将这两堆合并为一堆,代价为cost,cost可用前缀和算出
//合并[i,j]区间的代价为以上三者之和,即dp[i][j]=dp[i][k]+dp[k+1][j]+cost
//取最小花费更新dp[i][j]即可
int cost=stone[j]-stone[i-1];//计算将[i,j]的两堆石子合并的代价
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+cost);
}
}
}
cout<<dp[1][n]<<'\n';//将[1,n]区间合并的最小代价即为最终答案
return 0;
}
唯一分解定理:任何一个>1的正整数 都能以一种唯一的方式分解为若干个质因子的乘积
约数个数定理:分解的质因子的次方分别+1相乘
约数和定理:从0次方加到对应次方的和 相乘
//求n!的约数和
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int maxn=2e5+100;
map<int,int>factor;//键存储质因数,值存储其出现的次数
void cal_factor(int x)//计算x的质因数出现的次数
{
for(int i=2;i<=x/i;i++)//从2遍历到根号x
{
if(x%i)continue;
while(x%i==0)//i是x的质因数
{
if(factor.find(i)!=factor.end())
{
factor[i]++;//出现次数加1
}
else factor.insert(make_pair(i,1));
x=x/i;
}
}
//经过以上处理,x可能剩下一个大于1的数,如x=12,将分解为2*2*3,最后将剩下3
if(x>1)factor[x]++;//特判,最后一个因子出现的次数加1
}
ll qmi(ll a,ll b,int m ){
ll res=1;
while(b){
if(b&1){res=(res*a+m)%m;b--;}//记得--
a=a*a%m;//不断取模
b>>=1;
}
return res%m;
}
int main(){
int n;
cin>>n;
ll ans=1;
map<int,int>::iterator it1;
for(int i=1;i<=n;i++)cal_factor(i);
for(it1=factor.begin();it1!=factor.end();it1++)//遍历所有出现的质因数
{
ll base=it1->first;//取出当前的质因数
ll power=it1->second;//取出其出现的次数
ll tmp=0;//tmp为(1+pi^1+pi^2+...+pi^ki)
for(int i=0;i<=power;i++)
{
tmp=(tmp+qmi(base,i,mod)+mod)%mod;
}
ans=(ans*tmp+mod)%mod;
//ans为 ∏(1+pi^1+pi^2+...+pi^ki)
}
cout<<ans<<endl;
return 0;
}
#include <bits/stdc++.h>
#define int long long
using namespace std;
vector<pair<int,int> >v;
signed main(){
int n;cin>>n;
for(int i=2;i<=n/i;i++){
if(n%i)continue;//不能整除直接跳过
//如果可以整除那么一定是一个质因子
int cnt=0;//表示当前这个质因子i的指数
while(n%i==0){n/=i;cnt++;}
v.push_back({i,cnt});
}
//可能有一个大于平方根的因子
if(n>1)v.push_back({n,1});
for(auto i:v)cout<<i.first<<" "<<i.second<<'\n';
//2 2
//3 1
//5 1
return 0;
}
//裴蜀定理:设a,b是不全为0的整数,则存在整数x,y使得ax+by=k*gcd(a,b)
//扩展裴蜀定理:
//a,b为不小于0的整数,n为整数,是否存在不小于0的x和y使得ax+by=n有解?
//1、若n>ab-a-b,有解
//2、若n=0,有解(x=y=0)
//3、若n<0,无解(a,b,x,y均不小于0,无法线性变换出负数)
//4、若ax+by=n有解,则ax+by=ab-a-b-n无解
//本题需要求解最小的ax+by=n,使得n>0
//设a和b的最大公约数为gcd(a,b),因为a,b,x,y均为整数,其线性组合同样是gcd(a,b)的倍数
//故ax+by=k*gcd(a,b)
//令k=1,可得最小的ax+by=gcd(a,b)
//故本题直接求解gcd(a,b)即可
//注意当ab异号时gcd(a,b)返回负数,需要取绝对值
#include <bits/stdc++.h>
using namespace std;
int main(){
int T;
cin>>T;
while(T--){
int a,b;
cin>>a>>b;
cout<<abs(__gcd(a,b))<<'\n';//ab异号将返回负数,需要取绝对值
}
return 0;
}
4|12==7|21