心得
北邮出的一场自闭场,所幸发了数据题解和标程
然而题目难度告诉我们,即使发了这些,也有一半没学过
1002.(hdu6579)Operation(可持久化异或线性基)
n(n<=1e5)个数,m(m<=1e5)次操作,操作分两种
0 l r 询问[l,r]内取出一些数异或的最大值
1 x 将x插入n个数的尾部,同时令n+1
题目强制在线
思路来源:https://www.jianshu.com/p/866143c93409
就觉得应该有可持久化异或线性基,搜了一下板子,就过了
可持久化异或线性基原理:
对于每个线性基,将出现位置靠右的数字尽可能地放在高位,
在插入新数字的时候,要同时记录对应位置上数字的出现位置,
并且在找到可以插入的位置的时候,如果新数字比位置上原来的数字更靠右,就将该位置上原来的数字向低位推。
在求最大值的时候,从高位向低位遍历,
如果该位上的数字出现在询问中区间左端点l的右侧且可以使答案变大,就异或到答案里。
#include<bits/stdc++.h>
using namespace std;
struct PersistentXorBasis
{
#define sz(b) b.size()
#define fi first
#define se second
#define pb push_back
static const int __=1e6+5;
typedef int type;
struct XorBasis
{
vector<pair<type,int> >G;
int zero; //最后出现的位置
XorBasis() {clear();}
void operator=(const XorBasis &b)
{
for(int i=0;i<sz(b.G);++i)
G.push_back(b.G[i]);
zero=b.zero;
}
int size() {return G.size()+bool(zero);}
void clear()
{
zero=0;
G.clear();
}
}X[__];
int n;
void insert(pair<type,int>x)
{
vector<pair<type,int> >&G=X[x.se].G;
for(int i=sz(G)-1;~i && x.fi;--i)
{
type t=x.fi^G[i].fi;
if(t<x.fi)
{
if(t>G[i].fi)break;
if(x.se>G[i].se)swap(G[i],x);
x.fi^=G[i].fi;
}
}
if(!x.fi){X[x.se].zero=x.se;return;}
G.pb(x);
for(int i=sz(G)-1;i && G[i].fi<G[i-1].fi;--i)
swap(G[i],G[i-1]);
}
void build(type a[],int _n)
{
n=_n;
for(int i=1;i<=n;++i)
{
X[i]=X[i-1];
insert(make_pair(a[i],i));
}
}
type get_max(int l,int r)
{
vector<pair<type,int> >&G=X[r].G;
type res=0;
for(int i=sz(G)-1;~i;--i)
if(G[i].se>=l && (res^G[i].fi)>res)
res^=G[i].fi;
return res;
}
void clear()
{
for(int i=1;i<=n;++i)
X[i].clear();
}
}X;
int t,n,m,v;
int op,l,r,lastans;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
X.n=n;
for(int i=1;i<=X.n;++i)
{
scanf("%d",&v);
X.X[i]=X.X[i-1];
X.insert(make_pair(v,i));
}
lastans=0;
for(int i=1;i<=m;++i)
{
scanf("%d",&op);
if(op==0)
{
scanf("%d%d",&l,&r);
l=(l^lastans)%X.n+1;
r=(r^lastans)%X.n+1;
if(l>r)swap(l,r);
printf("%d\n",lastans=X.get_max(l,r));
}
else if(op==1)
{
X.n++;
X.X[X.n]=X.X[X.n-1];
scanf("%d",&v);
v=v^lastans;
X.insert(make_pair(v,X.n));
}
}
X.clear();
}
return 0;
}
写个简短的版本,用两个数组实现的
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int lg=31;
int base[N][lg],pos[N][lg];
int t,n,m,v;
int op,l,r,lastans;
void ins(int i,int x)//
{
memcpy(base[i],base[i-1],sizeof base[i]);
memcpy(pos[i],pos[i-1],sizeof pos[i]);
int k=i;
for(int j=30;j>=0;--j)
{
if(!(x>>j&1))continue;
if(!base[i][j])
{
base[i][j]=x;
pos[i][j]=k;
break;
}
else
//把等大的线性基 换成后面这个 把x异或变小 再向低位找
//否则直接向低位找
{
if(k>pos[i][j])
{
swap(base[i][j],x);
swap(pos[i][j],k);
}
x^=base[i][j];
}
}
}
int ask(int l,int r)
{
int res=0;
for(int j=30;j>=0;--j)
{
if(pos[r][j]>=l)//有基的pos的值了 就代表有基
res=max(res,res^base[r][j]);
}
return res;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&v);
ins(i,v);
}
lastans=0;
for(int i=1;i<=m;++i)
{
scanf("%d",&op);
if(op==0)
{
scanf("%d%d",&l,&r);
l=(l^lastans)%n+1;
r=(r^lastans)%n+1;
if(l>r)swap(l,r);
printf("%d\n",lastans=ask(l,r));
}
else if(op==1)
{
n++;
scanf("%d",&v);
v=v^lastans;
ins(n,v);
}
}
for(int i=1;i<=n;++i)
{
memset(base[i],0,sizeof base[i]);
memset(pos[i],0,sizeof pos[i]);
}
}
return 0;
}
1004.(hdu6581)Vacation(二分)
数轴上x轴正方向有n(n<=1e5)辆车,第i辆的车头位于原点右侧si,长度为li,以vi速度满速向左开(1<=si,li,vi<=1e9)
当追上左侧车辆时,无缝衔接保持前车速度跟车,问最右边的车的车头碰到原点时的时间,
保证输入按si降序输入,即从右到左输入车的位置
二分时间t,考虑t时间内右车能到的数轴位置,
如果在左车的车尾的右侧就为左车车尾,否则为右车原位置-这段时间实际开的距离
答案为最小的让最右边的车到达原点的t
# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <vector>
# include <queue>
# include <stack>
# include <map>
# include <set>
# include <cmath>
# include <algorithm>
using namespace std;
typedef long long ll;
const double eps=1e-8;
const int N=1e5+10;
int n;
double l,r;
struct node
{
double len,pos,v;
}e[N],tmp[N];
bool ok(double mid)
{
for(int i=0;i<=n;++i)
tmp[i]=e[i];
tmp[0].pos=tmp[0].pos-mid*tmp[0].v;
for(int i=1;i<=n;++i)
tmp[i].pos=max(tmp[i].pos-mid*tmp[i].v,tmp[i-1].pos+tmp[i-1].len);
return tmp[n].pos<=eps;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=0;i<=n;++i)
scanf("%lf",&e[i].len);
for(int i=0;i<=n;++i)
scanf("%lf",&e[i].pos);
for(int i=0;i<=n;++i)
scanf("%lf",&e[i].v);
reverse(e,e+n+1);
l=0;r=1e14+1;
while(r-l>eps)
{
double mid=(l+r)/2;
if(ok(mid))r=mid;
else l=mid;
}
printf("%.7lf\n",l);
}
return 0;
}
1005.(hdu6582)Path
n(n<=1e4)个点,m(m<=1e4)条单向边,第i条边u->v有权值w(w<=1e9)
求删掉的边的最小权值和,使得原图中的最短路大于没删边之前的最短路
显然只有删最短路上的边才有意义,所以要先处理出最短路的图
先跑一遍最短路,求得距离,满足dis[v]==dis[u]+w的边是在最短路径图上的
用这些边新建一个图,跑最小割,即为答案,
Dinic复杂度似乎在这里玄学了一次,弧优化就完事了,bzoj1266原题警告
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3fll;
const int maxn=1e4+10;
const int maxm=1e4+10;
int level[maxn];
int head[maxn],cnt,num;
int t,n,m,u,v;
ll w;
int ss,ee;
bool vis[maxn];
ll dis[maxn];
struct edg{int u,v;ll w;}tmp[maxm];
struct edge{int v,nex;ll w;}e[maxm];
priority_queue<pii,vector<pii>,greater<pii> >q;
void add(int u,int v,ll w)
{
e[cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt++;
}
bool bfs(int s,int t)
{
queue<int>q;
memset(level,0,sizeof level);
level[s]=1;
q.push(s);
while(!q.empty())
{
int x=q.front();
q.pop();
if(x==t)return 1;
for(int u=head[x];~u;u=e[u].nex)
{
int v=e[u].v;ll w=e[u].w;
if(!level[v]&&w)
{
level[v]=level[x]+1;
q.push(v);
}
}
}
return 0;
}
ll dfs(int u,ll maxf,int t)
{
if(u==t)return maxf;
ll ret=0;
for(int i=head[u];~i;i=e[i].nex)
{
int v=e[i].v;ll w=e[i].w;
if(level[u]+1==level[v]&&w)
{
ll MIN=min(maxf-ret,w);
w=dfs(v,MIN,t);
e[i].w-=w;
e[i^1].w+=w;
ret+=w;
if(ret==maxf)break;
}
}
if(!ret)level[u]=-1;//优化,防止重搜,说明u这一路不可能有流量了
return ret;
}
ll Dinic(int s,int t)
{
ll ans=0;
while(bfs(s,t))
ans+=dfs(s,INF,t);
return ans;
}
void dijkstra(int u)
{
while(!q.empty())q.pop();
for(int i=1;i<=n;++i)
vis[i]=0,dis[i]=1e18;
dis[u]=0;
q.push(pii(dis[u],u));
while(!q.empty())
{
pii tmp=q.top();
q.pop();
int pos=tmp.second;
ll d=tmp.first;
if(vis[pos])continue;
vis[pos]=1;
for(int i=head[pos];~i;i=e[i].nex)
{
int v=e[i].v;
ll w=e[i].w;
if(dis[v]>dis[pos]+w)
{
dis[v]=dis[pos]+w;
q.push(pii(dis[v],v));
}
}
}
}
void dfs2(int u)
{
vis[u]=1;
for(int i=head[u];~i;i=e[i].nex)
{
int v=e[i].v;
ll w=e[i].w;
if(dis[v]==dis[u]+w)
tmp[num++]=edg{u,v,w};
if(vis[v])continue;
dfs2(v);
}
}
int main()
{
scanf("%d",&t);
//n个点 m条边
while(t--)
{
cnt=0;num=0;
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
ss=1;ee=n;
for(int j=0;j<m;++j)
{
scanf("%d%d%lld",&u,&v,&w);
add(u,v,w);
}
dijkstra(ss);
memset(vis,0,sizeof vis);
dfs2(ss);
cnt=0;
memset(head,-1,sizeof head);
for(int i=0;i<num;++i)
{
add(tmp[i].u,tmp[i].v,tmp[i].w);
add(tmp[i].v,tmp[i].u,0);
}
ll ans=Dinic(ss,ee);
printf("%lld\n",ans);
}
return 0;
}
1009.(hdu6586)String(贪心)
串长不超过1e5的全小写字母构成的字符串|s|,要求构造它的一个长度为k的子序列,
满足第i个小写字母的个数在[L[i],R[i]]之间
倒序预处理每个位置每个字母还有多少个,每个字母开vector存同字母的位置
正序填入k个字母,每次从小到大遍历字母j,试在上一次填的位置last后填这一位j,
如果j无剩余或已填到上限,放弃,否则试填j,填完之后检查j,
若后续所有剩余j,加上已填j,仍不够下限,回滚
若把所有字母填到上限,也填不满k位,回滚
若把所有字母都只填到下限,也超过k位,回滚
否则,说明这一位合法,填上即可,有点银行家算法的意思啊……
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int k,n,v;
int now[N][26],l[26],r[26],used[26];
vector<int>pos[26];
vector<int>::iterator head[26];
int last;
char s[N],ans[N];
bool ok;
int main()
{
while(~scanf("%s%d",s,&k))
{
for(int j=0;j<26;++j)
scanf("%d%d",&l[j],&r[j]);
n=strlen(s);
memset(now[n],0,sizeof now[n]);
for(int i=n-1;i>=0;--i)
{
memcpy(now[i],now[i+1],26*sizeof(int));
v=s[i]-'a';
now[i][v]++;
}
for(int j=0;j<26;++j)
used[j]=0,pos[j].clear();
for(int i=0;i<n;++i)
pos[s[i]-'a'].push_back(i);
for(int j=0;j<26;++j)
head[j]=pos[j].begin();
ok=1;last=-1;
for(int i=0;i<k&&ok;++i)
{
bool upd=0;
for(int j=0;j<26;++j)
{
if(used[j]==r[j])continue;
while(head[j]!=pos[j].end()&&(*head[j])<=last)head[j]++;
if(head[j]==pos[j].end())continue;
bool flag=1;
used[j]++;
int sum=0,to=*head[j];
for(int j=0;j<26;++j)
{
if(now[to+1][j]+used[j]<l[j])flag=0;//该字符选不够
sum+=max(l[j]-used[j],0);//最少还要选的字符
}
if(sum>k-1-i)flag=0;//还要选的字符比当前剩余位置多
sum=0;
for(int j=0;j<26;++j)
{
sum+=min(r[j]-used[j],now[to+1][j]);//还能选的字符
}
if(sum<k-1-i)flag=0;//还能选的字符比当前剩余位置少
if(!flag)used[j]--;
else
{
ans[i]=j+'a';
last=to;
upd=1;
break;
}
}
if(!upd)ok=0;
}
if(ok)
{
ans[k]='\0';
printf("%s\n",ans);
}
else puts("-1");
}
return 0;
}
1011.(hdu6588)Function(线性筛)
求
,1<=n<=1e21
把枚举i变成枚举j=,从而
,
对于每个j,i从到
,但是最后一个j可能由于n的限制取不满,所以分成两部分

注意枚举约数和枚举倍数的转化,T=d*t,
还有就是
这种题还是多练试推吧,不一定要按题解那么列,但也要搞出来啊
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
#include<algorithm>
using namespace std;
const __int128 h=100;
const int maxn=1e7+10;
const int mod=998244353;
typedef long long ll;
bool ok[maxn];
int prime[maxn];
int phi[maxn],square[maxn],sigma[maxn],cnt;
int inv6,inv2;
int modpow(int x,int n,int mod)
{
int res=1;
for(;n;n>>=1,x=1ll*x*x%mod)
if(n&1)res=1ll*res*x%mod;
return res;
}
void sieve()
{
phi[1]=1;
for(int i=2;i<maxn;++i)
{
if(!ok[i])
{
prime[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;j<cnt;++j)
{
if(1ll*i*prime[j]>=maxn)break;
ok[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
}
}
inv2=modpow(2,mod-2,mod);
inv6=modpow(6,mod-2,mod);
for(int w=1;w<maxn;++w)
{
int tmp=1ll*3*w*(w+1)%mod;
sigma[w]=1ll*tmp*inv2%mod;
square[w]=1ll*tmp*(2*w+1)%mod*inv6%mod;
}
}
template <class T>
void read(T &x)
{
static char ch;static bool neg;
for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
x=neg?-x:x;
}
int t;
__int128 n,u,v;
int solve1()
{
int ans=0,up;
for(int i=1;i*i*i<=n;++i)
{
up=min((int)n,(i+1)*(i+1)*(i+1)-1);
for(int j=i*i*i;j<=up;++j)
{
ans=ans+__gcd(i,j);
if(ans>=mod)ans-=mod;
}
}
return ans;
}
int cal(__int128 d,__int128 up)//sigma i从1到up gcd(i,d)
{
int ans=0;
for(int i=1;i*i<=d;++i)
if(d%i==0)
{
ans=(ans+up/i%mod*phi[i]%mod)%mod;
if(d/i!=i)ans=(ans+up/(d/i)%mod*phi[d/i]%mod)%mod;
}
return ans;
}
int solve2()
{
__int128 l=0,r=1e7+10;
while(l<=r)
{
__int128 m=(l+r)/2;
if(m*m*m>n)r=m-1;
else l=m+1;
}
u=r;v=r-1;
int ans=(cal(u,n)-cal(u,u*u*u-1)+mod)%mod;
for(int i=1;i<=v;++i)
{
int w=v/i;//倍数上界
ans=(ans+(1ll*i*square[w]%mod+sigma[w]+w)*phi[i]%mod)%mod;
}
return ans;
}
int main()
{
sieve();
read(t);
while(t--)
{
read(n);
if(n<h)printf("%d\n",solve1());
else printf("%d\n",solve2());
}
return 0;
}
本文分享了一场北邮自闭场的算法竞赛心得,详细解析了包括可持久化异或线性基、二分搜索、最短路径最小割、贪心算法及线性筛等高级数据结构和算法的应用技巧。
99

被折叠的 条评论
为什么被折叠?



