2019CCPC网络赛
hdu6703 array
题意
给定一个1到\(n\)的全排列,两种操作,将\(a_{pos}\)修改为\(a_{pos}+1000000\),询问第一个大于等于\(k\)的且不在\(a_1...a_r\)的数。
分析
- 由于\(k<=n\),因此操作二询问的答案最大是\(n+1\),因此操作一就相当于删去一个数。
- 考虑用权值线段树维护下标(权值区间下标最大值),操作一就将下标置为\(n+1\),而操作二就是在值域\([k,n]\)之间查询下标大于\(r\)的最小数。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
const int INF=0x3f3f3f3f;
int T,n,m,a[N],idx[N],o,p,r,k;
struct VCT{
#define ls i<<1
#define rs i<<1|1
#define mid (l+r)/2
int mx[N*4];
void pushup(int i){
mx[i]=max(mx[ls],mx[rs]);
}
void build(int i,int l,int r){
if(l==r){
mx[i]=idx[l];
return;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(i);
}
void update(int i,int l,int r,int p){
if(l==r && l==p){
mx[i]=n+1;
return;
}
if(p<=mid){
update(ls,l,mid,p);
}else{
update(rs,mid+1,r,p);
}
pushup(i);
}
//查询值域[ql,qr]中第一个大于k的下标
int query(int i,int l,int r,int k,int x){
if(l==r){
return l;
}
int ans=n+1;
if(k<=mid){
if(mx[ls]>x){
ans=min(ans,query(ls,l,mid,k,x));
//因为要找满足条件最小的值,左子树满足直接返回
if(ans<n+1){
return ans;
}
}
}
//这里不能else并列,因为进入左子树查询也有可能查到n+1
if(mx[rs]<=x){
return ans;
}
return query(rs,mid+1,r,k,x);
}
#undef ls
#undef rs
#undef mid
}wa;
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
idx[a[i]]=i;
}
idx[n+1]=INF;
n++;
wa.build(1,1,n);
int lst=0;
while(m--){
scanf("%d",&o);
if(o==1){
scanf("%d",&p);
p=p^lst;
wa.update(1,1,n,a[p]);
}else if(o==2){
scanf("%d%d",&r,&k);
r=r^lst;
k=k^lst;
lst=wa.query(1,1,n,k,r);
printf("%d\n",lst);
}
}
}
return 0;
}
hdu6704 K-th occurence
题意
给定一个字符串,多次询问其中一个子串第\(k\)次出现的位置。
分析
- 子串出现位置考虑后缀数组,第\(k\)小考虑主席树。
- 对于后缀数组来说,\(sa[i]\)表示后缀出现的位置,那么如果这个后缀对应的\(h[i]\)满足给定子串的长度,那就相当于这个子串出现的位置了。
- 所以可以二分找到这个子串出现的位置区间,然后主席树维护后缀的出现位置\(sa[i]\),查询第\(k\)小即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
int T,n,m,l,r,k,s[N];
char str[N];
int sa[N],rk[N],h[N];
int t[N],t2[N],c[N];
int tr[N];
int dp[N][25];
void build_sa(int n,int m=128){
n++;
int *x=t,*y=t2;
for(int i=0;i<m;i++){
c[i]=0;
}
for(int i=0;i<n;i++){
c[x[i]=s[i]]++;
}
for(int i=1;i<m;i++){
c[i]+=c[i-1];
}
for(int i=n-1;i>=0;i--){
sa[--c[x[i]]]=i;
}
for(int k=1;k<=n;k<<=1){
int p=0;
for(int i=n-k;i<n;i++){
y[p++]=i;
}
for(int i=0;i<n;i++){
if(sa[i]>=k){
y[p++]=sa[i]-k;
}
}
for(int i=0;i<m;i++){
c[i]=0;
}
for(int i=0;i<n;i++){
c[x[y[i]]]++;
}
for(int i=1;i<m;i++){
c[i]+=c[i-1];
}
for(int i=n-1;i>=0;i--){
sa[--c[x[y[i]]]]=y[i];
}
swap(x,y);
p=1;
x[sa[0]]=0;
for(int i=1;i<n;i++){
x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
}
if(p>=n){
break;
}
m=p;
}
n--;
for(int i=0;i<=n;i++){
rk[sa[i]]=i;
}
int k=0;
for(int i=0;i<n;i++){
if(k){
k--;
}
int j=sa[rk[i]-1];
while(s[i+k]==s[j+k]){
k++;
}
h[rk[i]]=k;
}
}
void debug(){
//sa 0~n 包括一个特殊字符
for(int i=0;i<=n;i++){
printf("%d ",sa[i]);
}
printf("\n");
//rk 0~n-1 后缀[i...n-1]的排名
for(int i=0;i<n;i++){
printf("%d ",rk[i]);
}
printf("\n");
//h 1~n 排名为i的后缀与排名为i-1的后缀的LCP
for(int i=1;i<=n;i++){
printf("%d ",h[i]);
}
printf("\n");
}
struct CT{
#define mid (l+r)/2
int tot,sum[N*30],lr[N*30],rr[N*30];
void init(){
tot=0;
}
int build(int l,int r){
int rt=++tot;
sum[rt]=lr[rt]=rr[rt]=0;
if(l==r){
return tot;
}
lr[rt]=build(l,mid);
rr[rt]=build(mid+1,r);
return rt;
}
int update(int pre,int l,int r,int v){
int rt=++tot;
sum[rt]=sum[pre]+1;
lr[rt]=lr[pre];
rr[rt]=rr[pre];
if(l>=r){
return rt;
}
if(v<=mid){
lr[rt]=update(lr[pre],l,mid,v);
}else{
rr[rt]=update(rr[pre],mid+1,r,v);
}
return rt;
}
int query(int u,int v,int l,int r,int k){
if(l>=r){
return l;
}
if(sum[v]-sum[u]<k){
return -1;
}
int lc=sum[lr[v]]-sum[lr[u]];
if(k<=lc){
return query(lr[u],lr[v],l,mid,k);
}else{
return query(rr[u],rr[v],mid+1,r,k-lc);
}
}
#undef mid
}ac;
void init(){
for(int i=0;i<=n;i++){
dp[i][0]=h[i];
}
for(int j=1;(1<<j)<=n;j++){
for(int i=0;i+(1<<j)-1<=n;i++){
dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
}
int rmq(int l,int r){
int k=0;
while((1<<(k+1))<=r-l+1){
k++;
}
return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
int solve(int l,int r){
if(l==r){
return n-sa[l];
}
if(l>r){
swap(l,r);
}
return rmq(l+1,r);
}
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
scanf("%s",str);
for(int i=0;i<n;i++){
s[i]=str[i]-'a'+1;
}
s[n]=0;
build_sa(n);
// debug();
ac.init();
tr[0]=ac.build(1,n);
for(int i=1;i<=n;i++){
tr[i]=ac.update(tr[i-1],1,n,sa[i]+1);
}
init();
while(m--){
scanf("%d%d%d",&l,&r,&k);
l--;
r--;
int len=r-l+1;
int R=rk[l];
int ql=R,qr=R;
int ll=1,rr=R;
while(ll<=rr){
int md=(ll+rr)/2;
if(solve(md,R)>=len){
ql=md;
rr=md-1;
}else{
ll=md+1;
}
}
ll=R,rr=n;
while(ll<=rr){
int md=(ll+rr)/2;
if(solve(R,md)>=len){
qr=md;
ll=md+1;
}else{
rr=md-1;
}
}
if(qr-ql+1<k){
printf("-1\n");
continue;
}
int ans=ac.query(tr[ql-1],tr[qr],1,n,k);
printf("%d\n",ans);
}
}
return 0;
}
hdu6705 path
题意
给一个有向图,求第\(k\)小的路径。
分析
- 这种题似乎都是优先队列来的...
- 首先把所有点出边排序,最小的放进优先队列里,然后每次取出一条边\((u,v)\),加入\(u\)的下一条边和\(v\)的第一条边(如果有的话)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+50;
struct Edge{
int v;
ll w;
bool operator <(const Edge &rhs)const{
return w<rhs.w;
}
};
vector<Edge> g[N];
int T,n,m,q,k,u,v,w;
struct node{
int u,id;
ll w;
bool operator <(const node &rhs)const{
return w>rhs.w;
}
};
int que[N];
ll ans[N];
priority_queue<node> pq;
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){
g[i].clear();
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
g[u].push_back(Edge{v,1ll*w});
}
int mx=0;
for(int i=1;i<=q;i++){
scanf("%d",&que[i]);
mx=max(mx,que[i]);
}
while(!pq.empty()){
pq.pop();
}
for(int i=1;i<=n;i++){
sort(g[i].begin(),g[i].end());
if(g[i].size()>0){
pq.push(node{i,0,g[i][0].w});
}
}
int kk=0;
//取一条加两条
while(!pq.empty()){
node tmp=pq.top();
pq.pop();
kk++;
ans[kk]=tmp.w;
/**
* if(kk==que[i].k){
* i++;
* if(i>q){
* break;
* }
* }
* 这种写法是错的,如果有多个相同的k,只会计算第一个的答案,后面死循环
*/
if(kk==mx){
break;
}
int u=tmp.u;
int id=tmp.id;
int v=g[u][id].v;
ll w=tmp.w;
if(g[u].size()>id+1){
pq.push(node{u,id+1,w-g[u][id].w+g[u][id+1].w});
}
if(g[v].size()>0){
pq.push(node{v,0,w+g[v][0].w});
}
}
for(int i=1;i<=q;i++){
printf("%lld\n",ans[que[i]]);
}
}
return 0;
}