[3.16校内训练赛]
Posted FallDream
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[3.16校内训练赛]相关的知识,希望对你有一定的参考价值。
这次一个学长出题....结果我把dij写成了大顶的,就说复杂度那么科学怎么T了.........真的丢人
-------------------------------
A.给定一个长度为n的序列,你要求出从那个位置开始连续数n个数,得到的序列最大(先比第一位,再第二位..)。n<=2000000
题解:第一眼想到的是可以把每个数拆开来计数排序+dc3后缀数组,应该可过。
但是此题还有一个非常妙的解法。
假设目前最优的开头是i,你要判断开头为j的是否更优,那么你可以找到第一位不同的位k,即s[i+k]!=s[j+k],
这时候,如果s[j+k]<[i+k],那么以i开头的比j优,i+1的也会比j+1的优.....i+k的同样比j+k更优,所以我们可以直接让j=j+k+1,继续计算即可。复杂度O(n)
#include<iostream> #include<cstdio> #include<cstring> #define MAXN 2000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();} return x*f; } int n,s[2*MAXN+5]; int main() { freopen("minecraft.in","r",stdin); freopen("minecraft.out","w",stdout); n=read(); for(int i=1;i<=n;i++) s[i]=s[i+n]=read(); int k,i=1,j=2; for(;j<=n;) { for(k=0;s[j+k]==s[i+k]&&k<n;k++); if(s[j+k]>s[i+k])i=(s[j+k]>s[j])?j+k:j; j=j+k+1; } for(int j=0;j<n;j++)printf("%d ",s[j+i]); return 0; }
B.有n个数s1..sn,你要从中删除一些数,使得最后满足si=i(第i个数是i)的数尽量多。n<=100000
做法:考虑一个n^2的dp,即f[i]=max(f[j])+1,但必须满足 1)i>j 2)si-sj<=i<j 3)si>sj
但是我们可以发现,如果满足了2和3两个条件,那么第一个条件一定满足。
又因为si-sj<=i-j -> si-i<=sj-j 所以我们把si-i和si其中的一个排序,并且把另一个离散之后存到线段树里加速dp就好啦。
复杂度nlogn
#include<iostream> #include<cstdio> #include<algorithm> #define N 131072 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();} return x*f; } int n; int s[100005],f[100005],l[100005],p[100005],T[N*2+5],l2[100005]; void renew(int x,int ad) { x+=N;T[x]=max(T[x],ad); for(x>>=1;x;x>>=1)T[x]=max(T[x<<1],T[x<<1|1]); } int query(int l,int r) { int sum=0; for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) { if(~l&1)sum=max(sum,T[l+1]); if( r&1)sum=max(sum,T[r-1]); } return sum; } bool cmp(int x,int y) { return s[x]<s[y]||(s[x]==s[y]&&p[x]<p[y]); } int main() { freopen("fivethree.in","r",stdin); freopen("fivethree.out","w",stdout); n=read(); for(int i=1;i<=n;i++)s[i]=read(); for(int i=1;i<=n;i++)l2[i]=p[i]=s[i]-i; sort(l2+1,l2+n+1);int j=1; for(int i=2;i<=n;i++)if(l2[i]!=l2[i-1]) l2[++j]=l2[i]; for(int i=1;i<=n;i++)p[i]=j-(lower_bound(l2+1,l2+j+1,p[i])-l2)+1; int K=0; for(int i=1;i<=n;i++) if(s[i]-i<=0) l[++K]=i; sort(l+1,l+K+1,cmp); s[0]=-2000000000; for(int ii=1;ii<=K;ii++) { int i=l[ii]; f[i]=query(1,p[i])+1; if(s[i]!=s[l[ii+1]]) for(int j=ii;j&&s[l[j]]==s[l[ii]];j--) renew(p[l[j]],f[l[j]]); } printf("%d\n",query(1,n)); return 0; }
C.有一个n个点m条边的图,有边权,你要从第1个点到第n个点,但是你必须先到2...k+1这些点去搞事情。特殊地,有一些限制(a,b),表示必须在a搞完事情之后才能在b搞事情,求搞完所有事情到达第n个点至少要走的距离。n<=20000,m<=200000,k<=20
题解:很容易发现其实和图没什么关系,而且k的范围比较小,考虑壮压dp。
先用dij预处理出两两之间和每个点到起点终点的距离
然后用f[i][j]表示最后走到第i个点,状态是j的最小距离,转移即可。状态数最多2^20*20≈20000000,转移复杂度20,但实际并没有这么多状态,并且题目有2s,所以还是比较科学的。
交了一个大顶堆,真的丢人
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #define INF 2100000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();} return x*f; } int n,m,K,cnt=0; int p[25],l[25],to,v[25],rk[1000005],num[1000005]; int dis[25][25],d[20005],head[20005]; int f[22][1100000]; bool mark[20005]; struct edge{ int to,next,w; }e[400005]; struct node{int x,f;}; class cmp{public:bool operator()(node a,node b){return a.f>b.f;}}; priority_queue<node,vector<node>,cmp>q; void ins(int f,int t,int w) { e[++cnt].next=head[f];head[f]=cnt; e[cnt].to=t;e[cnt].w=w; } void work(int num,int from) { while(!q.empty())q.pop(); memset(d,0x3f3f3f,sizeof(d)); memset(mark,0,sizeof(mark)); d[from]=0;q.push((node){from,0}); while(!q.empty()) { int u=q.top().x;q.pop();if(mark[u])continue;mark[u]=1; for(int i=head[u];i;i=e[i].next) if(d[u]+e[i].w<d[e[i].to]) { d[e[i].to]=d[u]+e[i].w; q.push((node){e[i].to,d[e[i].to]}); } } for(int i=1;i<=K;i++)dis[num][i]=d[i+1]; dis[num][K+1]=d[n]; } int main() { freopen("revenge.in","r",stdin); freopen("revenge.out","w",stdout); n=read();m=read();K=read();to=1<<K; p[1]=1;for(int i=2;i<=K;i++)p[i]=p[i-1]<<1; for(int i=1;i<=m;i++) { int u=read(),v=read(),w=read(); ins(u,v,w);ins(v,u,w); } m=read(); for(int i=1;i<=m;i++) {int u=read(),v=read();l[v-1]|=p[u-1];} work(0,1);if(K==0)return 0*printf("%d",dis[0][K+1]); for(int i=1;i<=K;i++) work(i,i+1); for(int i=1;i<=K;i++) for(int j=0;j<to;j++) f[i][j]=INF; for(int i=1;i<=K;i++) if(!l[i]) f[i][p[i]]=dis[0][i]; for(int i=1;i<to;i++) { for(int j=1;j<=K;j++) if(f[j][i]<INF) for(int k=1;k<=K;k++) if((l[k]&i)==l[k]&&(!(i&p[k]))) { int into=i|p[k]; f[k][into]=min(f[k][into],f[j][i]+dis[j][k]); } } int ans=INF; for(int i=1;i<=K;i++)ans=min(ans,f[i][to-1]+dis[i][K+1]); cout<<ans; return 0; }
D.有n个数,你可以任选一个区间,并且用其中的任意一个数与这个区间的次大值异或起来,求最大异或值。n<=50000
题解:这道题是最好想出来的.....
对于每个数,往两边二分到第一个比它大的数,再向两边二分到第二个比它大的数。
假设序列是1 3 4 2 6 1 8,那么对于数2,它二分到的四个点分别是3,4,6,8,假设称为l2,l1,r1,r2
那么在区间(l1,r2)和(l2,r1)内,这个数显然都是次大值。然后我们对于每个查询到的区间都在可持久化TRIE树上查询一个和它异或最大的值,更新一下答案。
二分时候可以用线段树来check,在可持久化TRIE上查询是log,所以复杂度是nlog^2n
#include<iostream> #include<cstdio> #include<cstring> #define MAXN 2000005 #define N 65536 #define INF 2100000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();} return x*f; } int s[50005]; struct TRIE{ int son[2]; int size; }T[MAXN]; int t[N*2+5],c[35],rt[50005],cnt=0,n,ans=0; void renew(int x,int ad) { t[x+=N]=ad; for(x>>=1;x;x>>=1)t[x]=max(t[x<<1],t[x<<1|1]); } int query(int l,int r) { int sum=0; for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) { if(~l&1)sum=max(sum,t[l+1]); if( r&1)sum=max(sum,t[r-1]); } return sum; } void ins(int num) { memset(c,0,sizeof(c));rt[num]=++cnt; for(int x=s[num],len=0;x;c[++len]=(x&1),x>>=1); for(int i=30,x=rt[num-1],nx=rt[num];i;i--) { T[nx].son[c[i]]=++cnt;T[nx].son[!c[i]]=T[x].son[!c[i]]; x=T[x].son[c[i]];nx=T[nx].son[c[i]]; T[nx].size=T[x].size+1; } } void calc(int l,int r,int xx) { if(l>r)return; memset(c,0,sizeof(c));for(int x=xx,len=0;x;c[++len]=(x&1),x>>=1); int sum=0; for(int i=rt[l-1],j=rt[r],k=30,l=1<<29;k;k--,l>>=1) { c[k]=!c[k]; int to=(T[T[j].son[c[k]]].size-T[T[i].son[c[k]]].size>0)?c[k]:1-c[k]; if(to)sum|=l;j=T[j].son[to];i=T[i].son[to]; // cout<<i<<" "<<j<<" "<<to<<" "<<sum<<endl; } ans=max(ans,sum^xx); } int get(int rr,int xx,int spec) { int l=1,mid,r=--rr,q=rr+1; if(!rr)return spec; while(l<=r) { int mid=(l+r)>>1; if(query(mid,rr)<xx)q=mid,r=mid-1; else l=mid+1; } return q; } int get2(int ll,int xx,int what) { int l=++ll,r=n,mid,q=ll-1; if(ll>n)return what; while(l<=r) { int mid=(l+r)>>1; if(query(ll,mid)<xx)q=mid,l=mid+1; else r=mid-1; } return q; } int main() { freopen("lock.in","r",stdin); freopen("lock.out","w",stdout); n=read();int mx=0; for(int i=1;i<=n;i++) {s[i]=read();ins(i);renew(i,s[i]);mx=max(mx,s[i]);} for(int i=1;i<=n;i++) { if(s[i]==mx)continue; int l1=get(i,s[i],2)-1,l2=get(l1,s[i],l1); int r1=get2(i,s[i],n-1)+1,r2=get2(r1,s[i],r1); // cout<<i<<" "<<l1<<" "<<l2<<" "<<r1<<" "<<r2<<"!!!!!!!"<<endl; calc(l2,min(r1+1,n),s[i]);calc(max(0,l1-1),r2,s[i]); } cout<<ans; return 0; }
以上是关于[3.16校内训练赛]的主要内容,如果未能解决你的问题,请参考以下文章