标签:哈希 enc 分块 欧拉定理 限制 strong 差值 *** 严格
目录
dfs(i,i,INT_MAX)
赋值为INT_MAX的妙用#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
template <typename T>inline void rd(T &x){
x=0;char c=getchar();int f=0;
while(!isdigit(c)){f|=c=='-';c=getchar();}
while(isdigit(c)){x=x*10+(c^48);c=getchar();}
x=f?-x:x;
}
#define mem(a,b) memset(a,b,sizeof(a))
#define ee(i,u) for(int i=head[u];i;i=e[i].next)
/******************************************** Header Template **********************************************/
#define lson o<<1
#define rson o<<1|1
const int N=5e5+10;
int n,m;
int val[N];
struct tree{
int l,r;
int lmax,rmax,sum,maxx;
}t[N<<2];
inline int max_3(int a,int b,int c){
if(a<b)a=b;
if(a<c)a=c;
return a;
}
inline void pushup(int o){
t[o].sum=t[lson].sum+t[rson].sum;
t[o].lmax=max_3(t[lson].lmax,t[lson].sum+t[rson].lmax,t[lson].sum);
t[o].rmax=max_3(t[rson].rmax,t[rson].sum+t[lson].rmax,t[rson].sum);
t[o].maxx=max_3(t[lson].maxx,t[rson].maxx,t[lson].rmax+t[rson].lmax);
}
inline void build(int o,int l,int r){
t[o].l=l,t[o].r=r;
if(l==r){
t[o].lmax=t[o].maxx=t[o].rmax=t[o].sum=val[l];
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(o);
}
inline void change(int o,int pos,int k){
int l=t[o].l,r=t[o].r;
if(l==pos && r==pos){
t[o].sum=t[o].lmax=t[o].rmax=t[o].maxx=k;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)change(lson,pos,k);
else change(rson,pos,k);
pushup(o);
}
tree query(int o,int x,int y){
int l=t[o].l,r=t[o].r;
if(l==x && y==r){
return t[o];
}
int mid=(l+r)>>1;
if(y<=mid)return query(lson,x,y);
else if(mid<x)return query(rson,x,y);
else{
tree ls=query(lson,x,mid);
tree rs=query(rson,mid+1,y);
tree ans;
ans.maxx=max_3(ls.maxx,rs.maxx,ls.rmax+rs.lmax);
ans.lmax=max_3(ls.sum,ls.lmax,ls.sum+rs.lmax);
ans.rmax=max_3(rs.sum,rs.rmax,rs.sum+ls.rmax);
ans.sum=ls.sum+rs.sum;
return ans;
}
}
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(n),rd(m);
rep(i,1,n)rd(val[i]);
build(1,1,n);
while(m--){
int op,x,y,k;
rd(op);
if(op==1){
rd(x),rd(y);
if(x>y)swap(x,y);
tree ans=query(1,x,y);
printf("%lld\n",ans.maxx);
}
else if(op==2){
rd(x),rd(k);
change(1,x,k);
}
}
return 0;
}
/*
题目描述
给定长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“1 x y”,查询区间 [x,y] 中的最大连续子段和
2、“2 x y”,把 A[x] 改成 y。
对于每个查询指令,输出一个整数表示答案。
第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行每行3个整数k,x,y,k=1表示查询(此时如果x>y,请交换x,y),k=2表示修改。
对于每个查询指令输出一个整数表示答案。
N≤500000,M≤100000
N≤500000,M≤100000
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
输出样例:
2
-1
sol:
对于一个固定区间,求最大子段和我们有O(n)
的算法。但是如何维护不同区间的LIS
就成了一个问题。我们选择线段树解决区间问题,但使用线段树的话,我们需要明白,维护什么值,以
及如何进行区间操作。
那么我们思考,对于相邻的两个区间,他们的LIS
有几种可能?
1.左侧区间的LIS
2.右侧区间的LIS
3.两个区间合并后,中间新连接的部分
前两点都好理解,针对第三点我们继续思考,中间部分能够成为LIS,其实就是我们从连接部分分别向前向后,获
得一个尽可能大的前/后缀和。那么我们维护或者合并区间的LIS
就需要维护三个值,区间最大子段和,最大前缀和,最大后缀和。而我们在合并区间的时候,如何维护前/后缀和呢?
我们需要多维护一个区间和。
整理我们得到,定义区间ls,rs
合并得到区间d,每个区间维护区间和sum
,区间最大字段和maxs
,区间最大前缀和maxl
,区间最大后缀和maxr
。则合并区间时,可得关系如下
d.sum=ls.sum+rs.sum
d.maxs=max(ls.maxs,rs.maxs,ls.maxr+rs.maxl)
d.maxl=max(ls.maxl,ls.sum+rs.maxl)
d.maxr=max(rs.maxr,rs.sum+ls.maxr)
用线段树维护即可
*/
题目要求\(K^{x}=1 \mod \ M\)的最小x
不难联想到欧拉定理 \(K^{phi(M)}\ =1\mod\ M\)
时间复杂度:\(O(\sqrt{M})\)的复杂度预处理出phi(M),再用\(O(\sqrt{phi(M)})\)的复杂度枚举因子
int K,M,ans=INT_MAX;
inline int get_phi(int x){
int res=x;
for(int i=2;i*i<=x;++i){
if(x%i==0)res=res/i*(i-1);
while(x%i==0)x/=i;
}
if(x>1)res=res/x*(x-1);
return res;
}
inline int ksm(int a,int k,int mod){
int res=1;
for(;k;k>>=1){
if(k&1)res=res*a%mod;
a=a*a%mod;
}
return res;
}
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(M),rd(K);
if(__gcd(K,M)!=1){
puts("Let's go Blue Jays!");
exit(0);
}
int phi=get_phi(M);
for(int i=1;i*i<=phi;i++){
if(phi%i==0){
if(ksm(K,i,M)==1){
ans=min(ans,i);
}
else if(ksm(K,phi/i,M)==1){
ans=min(ans,phi/i);
}
}
}
printf("%lld",ans);
return 0;
}
转化成已知的问题:
如果原问题是非严格单调递增,则直接求出 LIS 的?度,从 n 中减去就行了。
注意到 ai 都是整数,ai < \(a_{i+1}\) 相当于 ai + 1 ≤ \(a_{i+1}\),
于是
a1 < a2 < · · · < an?1 < an
就相当于
a1 + (n ? 1) ≤ a2 + (n ? 2) ≤ · · · ≤ \(a_n-1\)+1 ≤ an
将原数组进行预处理,就可以转化成 不降LIS 问题了。
const int N=1e5+10;
int f[N],a[N];
int len,n;
int main(){
rd(n);
rep(i,1,n){
rd(a[i]);
a[i]+=n-i;
}
f[++len]=a[1];
rep(i,2,n){
if(f[len]<a[i])f[++len]=a[i];
else{
int p=upper_bound(f+1,f+len+1,a[i])-f;
f[p]=a[i];
}
}
printf("%d",n-len);
return 0;
}
众所周知,二叉搜索树其实就是平衡树,平衡树的中序遍历就是一个严格LIS,遍历二叉树求出这个序列后,就转化成了上一题。
推导:
\(key[lson]<key[rt]<key[rson]\)
--->
\(key[lson]+2 \le key[rt]+1 \le key[rson]\)
那么序列的第i项需要加上的值就是n-i
那么求出更新后序列的LIS,答案就是n-len.
const int N=1e5+10;
int len,n,id;
int a[N],f[N],val[N];
int son[N][2];
inline void zhongxu(int u){
if(son[u][0])zhongxu(son[u][0]);
val[++id]=a[u]+(n-id);
if(son[u][1])zhongxu(son[u][1]);
}
int main(){
rd(n);
rep(i,1,n)rd(a[i]);
rep(i,2,n){
int fa,op;
rd(fa),rd(op);
if(op==0)son[fa][0]=i;
else son[fa][1]=i;
}
zhongxu(1);
f[++len]=val[1];
rep(i,2,n){
if(f[len]<val[i])f[++len]=val[i];
else{
int p=upper_bound(f+1,f+len+1,val[i])-f;
f[p]=val[i];
}
}
printf("%d",n-len);
return 0;
}
线性基是一种特殊的基,它通常会在异或运算中出现。
对于自然数集 S,S 的线性基被定义为最小的集合 R,使得 S 的任何一个子集的 S′,都能找到一个 R 中的子集 R′,S′ 中所有元素的异或和等于 R′ 中元素的异或和,且对于 R 的所有子集 R′,也能找到对应的 S′。
形象地说,R 内元素随意异或得到的集合等于 S 内元素随意异或得到的集合。
我们从另一个角度来考虑问题,即 x 被表示为其他数的异或和代表什么。我们把 x 拆成一个向量 x={a0,a1,…,ak?1},x=∑2i×ai,其中 k 是位数。
那么,x 能被表示成 S 的某个子集的异或和 转化为 x 能被表示成 S 中数所对应的向量在模 2 意义下的线性组合。这样一来,R 这个最的小能表示任何数的集合本质上就是 S 的最大线性无关集合。
注意:这里的最小和最大并不矛盾,最小意味着线性无关,最大意味着能表示任何一个数。
现在,我们把数的异或,转换成了向量的线性组合。
高斯消元时,如果某一行被前面的行消为 0,那么代表它可以被表示成前面向量的线性组合,这也就是为什么:
方程组有解、系数矩阵行列式不为 0、系数矩阵满秩、系数矩阵的线性无关集合大小为 n ——这四个命题等价。
对于新加入的数 x,如果它能被表示成 R,就代表它能被 R 消成 0。
我们把向量反过来写,把高位系数写在左边,低位系数写在右边,对所有数做高斯消元。最终矩阵会被消成一个上三角矩阵,剩下的不为 0 的向量就是 R,而且每个向量(数)的最左边的非零数(最高位)都不相同。
因为矩阵行秩等于列秩,所以 R 最多有 k(其中 k 表示位数,一般为 32)个元素。
由于是在模 2 的意义下,所以我们可以直接压位来做。记 ri 表示最高位为 i 的线性基中的数。每次新加进来一个 x 就从其最高位开始消去,如果某一位(假设为第 i 位)无法消除了就把当前的 x′ 放进 ri 中,即 x′ 在线性基 R 中。
时间复杂度:O(nlog?w),其中 w 为值域。
#include <cstdio>
const int N = 1e6 + 5;
int n;
unsigned int a[N], r[32];
void Gauss(int n) {
for (int i = 1; i <= n; i++) {
unsigned int x = a[i];
for (int k = 31; ~k; k--) {
if (!(x >> k) & 1) continue;
if (r[k]) {
x ^= r[k];
} else {
r[k] = x;
break;
}
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%u", &a[i]);
}
Gauss(n);
for (int i = 0; i < 32; i++) {
if (r[i]) printf("%u\n", r[i]);
}
return 0;
}
/*
线性基是一种擅长处理异或问题的数据结构.设值域为 [1,N] ,就可以用一个长度为 \lceil \log_2N
的数组来描述一个线性基。特别地,线性基第 i 位上的数二进制下最高位也为第 i 位。
一个线性基满足,对于它所表示的所有数的集合 S , S中任意多个数异或所得的结果均能表示为
线性基中的元素互相异或的结果,即意,线性基能使用异或运算来表示原数集使用异或运算能表
示的所有数。运用这个性质,我们可以极大地缩小异或操作所需的查询次数。
插入和判断
我们考虑插入的操作,令插入的数为 x ,考虑 x 的二进制最高位 i ,
若线性基的第 i 位为 00 ,则直接在该位插入 x ,退出;
若线性基的第 i位已经有值 a[i],则x=x⊕a[i],重复以上操作直到 x=0.
如果退出时 x=0,则此时线性基已经可以表示原先的 x了;反之,则说明为了表示 x ,往线性基中加
入了一个新元素。
很容易证明这样复杂度为log_2也可以用这种方法判断能否通过原数列异或得到一个数 x 。
void insert(int x){
for(reg int i=I;~i;i--)
if(x&(1ll<<i))
if(!a[i]){a[i]=x;return;}
else x^=a[i];
flag=true;
}
bool check(int x){
for(reg int i=I;~i;i--)
if(x&(1ll<<i))
if(!a[i])return false;
else x^=a[i];
return true;
}
查询异或最值
查询最小值相对比较简单。考虑插入的过程,因为每一次跳转操作, x 的二进制最高位必定单调降低
,所以不可能插入两个二进制最高位相同的数。而此时,线性基中最小值异或上其他数,必定会增大。
所以,直接输出线性基中的最小值即可。
考虑异或最大值,从高到低遍历线性基,考虑到第 i位时,如果当前的答案 x第 i位为 0,就
将 x异或上a[i]则不做任何操作。显然,每次操作后答案不会变劣,最终的 x 即为答案。
同样,我们考虑对于一个数 x,它与原数列中的数异或的最值如何获得。用与序列异或最大值类似
的贪心即可解决。
查询 k小值
我们考虑进一步简化线性基。显然,一个线性基肯定可以表示为若干个形如 2^i的数。从高到低处理线
性基每一位,对于每一位向后扫,如果当前数第 i 位为 0,且线性基第 i 位不为0 ,则将当前数异或
上 a[i]这一操作可以在 O(n^2的时间内解决。
经过这一步操作后,设线性基内共有 cnt个数,则它们共可以表示出 2^cnt个数。当然,对于0 必须特
殊考虑。如果插入的总数 n与 cnt相等,就无法表示 0了。
同样,考虑最小值时,也必须要考虑到 00 的情况。事实上,如果插入时出现了未被加入的元素,
就肯定可以表示出 0。随后,我们考虑将 k二进制拆分,用与快速幂类似的方法就可以求出第 k大值。
学过线性代数的同学应该可以看出,这个过程就是对一个矩阵求解异或意义下的秩的过程。因此, cnt \leq \lceil \log_2N \rceilcnt≤?log
2
? N? 一定成立。而最终,线性基中保存的也是异或意义下的一组极小线性无关组。
同样,有关线性基的一切运算都可以看做矩阵的初等行列变换,也就可以将其看做线性规划问题。
同样,可以离线使用高斯消元来构造极小线性基。
bool flag;//可以表示0
int qmax(int res=0){
for(reg int i=I;~i;i--)
res=max(res,res^a[i]);
return res;
}
int qmin(int res=0){
if(flag)return 0;
for(reg int i=0;i<=I;i++)
if(a[i])return a[i];
}
int query(int k){
reg int res=0;reg int cnt=0;
k-=flag;if(!k)return 0;
for(reg int i=0;i<=I;i++){
for(int j=i-1;~j;j--)
if(a[i]&(1ll<<j))a[i]^=a[j];
if(a[i])tmp[cnt++]=a[i];
}
if(k>=(1ll<<cnt))return -1;
for(reg int i=0;i<cnt;i++)
if(k&(1ll<<i))res^=tmp[i];
return res;
}
代码*//*
reference:
translation:
solution:
trigger:
note:
*
date:
2019.08.03
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define dwn(i,a,b) for(int i=a;i>=b;--i)
template <typename T> void rd(T &x){x=0;char c=getchar();int f=0;while(!isdigit(c)){f|=c=='-';c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}x=f?-x:x;}
#define mem(a,b) memset(a,b,sizeof(a))
#define reg register
const int I=60;
int a[61],tmp[61];
int n,x;
bool flag;
inline void ins(int x){
dwn(i,I,0)
if(x&(1ll<<i))
if(!a[i]){a[i]=x;return;}
else x^=a[i];
flag=true;
}
inline bool check(int x){
for(int i=I;~i;i--)
if(x&(1ll<<i))
if(!a[i])return false;
else x^=a[i];
return true;
}
inline int qmax(int res=0){
dwn(i,I,0)
res=max(res,res^a[i]);
return res;
}
inline int qmin(){
if(flag)return 0;
rep(i,0,I)
if(a[i])return a[i];
}
inline int query(int k){
int res=0,cnt=0;
k-=flag;
if(!k)return 0;
rep(i,0,I){
dwn(j,i-1,0)
if(a[i]&(1ll<<j))a[i]^=a[j];
if(a[i])tmp[cnt++]=a[i];
}
if(k>=(1ll<<cnt))return -1;
rep(i,0,cnt-1)
if(k&(1<<i))res^=tmp[i];
return res;
}
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("xxj.txt","r",stdin);
#endif
rd(n);
rep(i,1,n){
rd(x);
ins(x);
}
printf("%lld\n",qmax());
return 0;
}
BSGS 用于解决高次同余方程的最小整数解,主要思想是分块。
求出下面方程的最小非负整数解 x:
ax≡b(modp)(gcd(a,p)=1)
此处的未知数 x 在指数上,我们就要用到 BSGS(大步小步法,即 Baby Step Giant Step)算法。
BSGS 的本质是分块的思想,我们设 m=?p?(注意是上取整),又设 x=i×m?j,其中 0≤i≤m,0≤j<m;我们可以将原方程转化为:
ai×m?j≡b(modp)
在 x=i×m?j 中,i 即为大步,j 即为小步;我们将 j 移到方程的右边得到:
ai×m≡b×aj(modp)
观察到 j 只有 O(m) 种取值,所以我们对于每个 j,把 b×ajmodp 的值记录下来,存放在哈希表中(有没有一种分块打表的感觉?)。然后枚举左半边的 i,将对应的值在哈希表中查找对应的 j,如果能找到对应的 j 那么说明已经找到答案了。
对于插入哈希表中的 j 的值,显然在同一个值时,j 的值越大越好,那么直接覆盖即可,无需关注细节问题!
注意:代码中必须要判断无解情况!(本来就无解,或者没有找到合法解)
时间复杂度:O(p)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,m,len,a[20];
ll l,r,dp[20][15][25][25][20];
ll dfs(int pos,int pre,ll st,ll sum,int d,int lead,int limit)
//pos搜到的位置
//pre前一位数
//st当前公差最大差值
//sum整个数字的最大价值
//d共差
//lead判断是否有前导0
//limit判断是否有最高位限制
{
if(pos>len) return sum;//dp结束
//记录状态(计划搜索)
//注意d有负数,最小可以到-9,所以记录时数组下标是d+10
if((dp[pos][pre][st][sum][d+10]!=-1&&(!limit)&&(!lead))) return dp[pos][pre][st][sum][d+10];
ll ret=0;
int res=limit?a[len-pos+1]:9;//最高位最大值
for(int i=0;i<=res;i++)
{
//有前导0且这位也是前导0,一切不变只有位数变化
if((!i)&&lead) ret+=dfs(pos+1,0,0,0,-38,1,limit&&(i==res));
//有前导0但这位不是前导0(这位是数字的最高位)开始有前一位,一个数形成等差数列
else if(i&&lead) ret+=dfs(pos+1,i,1,1,-38,0,limit&&(i==res));
//之前刚搜到1位还没有共差,两位数形成等差数列,记录共差
else if(d<-9) ret+=dfs(pos+1,i,2ll,2ll,i-pre,0,limit&&(i==res));
//搜到2位以后,共差若与前两位相同当前等差数列长度增加,若公差变化则更新整个数字的最大价值,等差数列长度又变为2
else if(d>=-9) ret+=dfs(pos+1,i,(i-pre==d)?st+1:2,max(sum,(i-pre==d)?st+1:2),(i-pre==d)?d:i-pre,0,limit&&(i==res));
}
//没有前导0和最高限制时可以直接记录当前dp值以便下次搜到同样的情况可以直接使用。
return (!limit&&!lead)?dp[pos][pre][st][sum][d+10]=ret:ret;
}
ll part(ll x)
{
len=0;
while(x) a[++len]=x%10,x/=10;
memset(dp,-1,sizeof dp);
return dfs(1,0,0,0,-38,1,1);//-38是随便赋的其实赋成-10就行了……
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld",&l,&r);
//l是0的时候要特别注意!
printf("%lld\n",l?(part(r)-part(l-1)):(part(r)-part(l)+1));
}
return 0;
}
把 P1 重新标号为 1 到 n ,P2 进行相应的重标号。 那么 P1 的任意一个子序列都是一个单增序列,且任意一个单增序列都 是 P1 的子序列。 那么只需要对重标号后的 P2 求最长上升子序列即可(因为这样的子序列一定是P1的子序列)。
const int N=1e5+10;
int a[N],b[N],f[N],len;
map<int,int>mp;
int n;
int main(){
rd(n);
rep(i,1,n){
rd(a[i]);
mp[a[i]]=i;
}
rep(i,1,n){
int x;rd(x);
b[i]=mp[x];
}
f[++len]=b[1];
rep(i,2,n){
if(f[len]<b[i])f[++len]=b[i];
else{
int p=lower_bound(f+1,f+len+1,b[i])-f;
f[p]=b[i];
}
}
printf("%d",len);
return 0;
}
令f[l][r][op]
区间l->r可以用op表示出来 目标是求 f 1,n,op ,其中 op ∈ {W, I, N, G} 。
按区间长度从小到大的顺序进行 dp。
对于 f l,r,op ,枚举第一个字母(也 就是 op )变形成的两个字母是什么,及分别对应 [l,r] 中的哪些位置, 即可利用之前的 dp 信息求出。
const int N=210;
bool f[N][N][5];//f[l][r][op]区间l->可以用op表示出来
bool G[5][5][5];//G[i][j][k] 字符i 和 字符j 可以拼出字符 k
int num[N];
int n;
char s[N];
map<char,int>mp;
int main() {
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
mp['W']=1,mp['I']=2,mp['N']=3,mp['G']=4;
rep(i,1,4)rd(num[i]);
rep(i,1,4)
rep(j,1,num[i]){
char a,b;cin>>a>>b;
G[mp[a]][mp[b]][i]=1;
}
scanf("%s",s+1);
n=strlen(s+1);
rep(i,1,n)f[i][i][mp[s[i]]]=1;
rep(len,2,n)
rep(l,1,n-len+1){
int r=l+len-1;
rep(k,l,r-1)//枚举断点
rep(z,1,4)
rep(z1,1,4)
rep(z2,1,4)
f[l][r][z] |= G[z1][z2][z] & f[l][k][z1] &f[k+1][r][z2];
}
bool flag=0;
if(f[1][n][1]){flag=1;printf("W");}
if(f[1][n][2]){flag=1;printf("I");}
if(f[1][n][3]){flag=1;printf("N");}
if(f[1][n][4]){flag=1;printf("G");}
if(!flag)puts("The name is wrong!");
return 0;
}
标签:哈希 enc 分块 欧拉定理 限制 strong 差值 *** 严格
原文地址:https://www.cnblogs.com/sjsjsj-minus-Si/p/11785198.html