NOI 2014 题解
来源:互联网 发布:php curl post请求 编辑:程序博客网 时间:2024/06/11 17:50
起床困难综合症
(传送门)
题意
在[0,m]中取一个整数,使得n次给定的位运算操作后(and,or,xor),答案最大(m<=10^9,n<=10^5)
分析
位运算水题,按位考虑就行,O(nlogm)随便搞搞就行
代码
#include <bits/stdc++.h> using namespace std;const int maxn=100000+10;int n,m;long long sum,ans;int op[maxn],dat[maxn];int f[101];int cal(int x) { for(int i=1;i<=n;i++) { if(op[i]==1) x=(x&dat[i]); else if(op[i]==2) x=(x|dat[i]); else if(op[i]==3) x=(x^dat[i]); } return x;}int main() { cin>>n>>m; for(int i=1;i<=n;i++) { char ch[5]; long long x; scanf("%s%d",ch,&x); dat[i]=x; if(ch[0]=='A') op[i]=1; else if(ch[0]=='O') op[i]=2; else if(ch[0]=='X') op[i]=3; } int t=cal(0); for(int i=0;i<=30;i++) f[i]=cal(1<<i)&(1<<i); for(int i=30;i>=0;i--) { int now=1<<i; if(t&now)ans+=now; else if(f[i] && sum+now<=m) { sum+=now; ans+=now; } } cout<<ans<<endl;}
魔法森林
(传送门)
题意
无向图每条边上有权值A,B,找出一条1->n的路径,使路径上所有边A的最大值与B的最大值之和尽量小。(n<=50000,m<=100000,Ai,Bi<=50000。)
分析1
官方正解是LCT。
n个点m条边,在LCT里,下标[1,n]是结点,下标[n+1,m+n]中的结点是边;边对应的结点才有权值(b)。
边按照a排序,并查集维护图的连通性。按a值从小到大不断地加边,维护并查集,加一条边时,要同时在LCT里连接这条边的两个点u和v(先连接u和边对应的结点,再把边对应的结点和v相连)。会连成环,看LCT中u到v路径b最大的边,删掉,加入这条边。
ans=min{a+LCT中起点到终点最大的b}。
代码1
/*n个点m条边,在lct里,下标[1,n]是结点,下标[n+1,m+n]中的结点是边;边对应的结点才有权值(b)。边按照a排序,并查集维护图的连通性。按a值从小到大不断地加边,维护并查集,加一条边时,要同时在LCT里连接这条边的两个点u和v(先连接u和边对应的结点,再把边对应的结点和v相连)会连成环,看LCT中u到v路径b最大的边 ,删掉,加入这条边。ans=min{a+LCT中起点到终点最大的b}。*/#include <bits/stdc++.h>using namespace std;const int MAXN=150000+10;const int MAXE=100000+10;const int INF=0x3f3f3f3f; struct Edge{ int u,v,a,b;}edges[MAXE];int n,m,nCount=0,ans=INF;int r[MAXN],ch[MAXN][2],fa[MAXN];//根,左右儿子,父节点int b[MAXN],maxv[MAXN];//该点值(b)、该点对应区间的最大值对应结点的下标 bool rev[MAXN],isRoot[MAXN];////翻转标记、根节点标记 int find(int x)//找x的根{ if(r[x]==x) return x; return r[x]=find(r[x]);} bool cmp(Edge x1,Edge x2){ return x1.a<x2.a;}/*....................Splay.....................*/void update(int x){if(!x)return;swap(ch[x][0],ch[x][1]);rev[x] ^= 1;}void pushdown(int x){if(rev[x]){update(ch[x][0]);update(ch[x][1]);rev[x]=0;}}void pushup(int x){int lc=ch[x][0],rc=ch[x][1];maxv[x]=x; if(b[maxv[lc]]>b[maxv[x]]) maxv[x]=maxv[lc]; if(b[maxv[rc]]>b[maxv[x]]) maxv[x]=maxv[rc]; }void rotate(int x){ int y=fa[x],tmp=ch[y][1]==x; ch[y][tmp]=ch[x][!tmp]; fa[ch[y][tmp]]=y; fa[x]=fa[y]; fa[y]=x; ch[x][!tmp]=y; if(isRoot[y]) { isRoot[y]=0; isRoot[x]=1; } else ch[fa[x]][ch[fa[x]][1]==y]=x; pushup(y); } void P(int x)//维护x和它的所有祖先{ if(!isRoot[x]) P(fa[x]); pushdown(x);} void splay(int x){ P(x); while(!isRoot[x]) { int f=fa[x],ff=fa[f]; if(isRoot[f]) rotate(x); else if((ch[ff][1]==f)==(ch[f][1]==x)) { rotate(f); rotate(x); } else { rotate(x); rotate(x); }} pushup(x); }/*...............Link Cut Tree..................*/int access(int x)//x到根节点的preferred path{int y=0;while(x){splay(x);isRoot[ch[x][1]]=1;ch[x][1]=y; isRoot[ch[x][1]]=0; pushup(x); y=x; x=fa[x];}return y;}void makeroot(int x)//使x成为所在树的根 { access(x); splay(x); update(x);}void link(int u,int v){makeroot(u);fa[u]=v;}void cut(int u,int v){makeroot(u);access(v);splay(v);fa[ch[v][0]]=fa[v];fa[v]=0;isRoot[ch[v][0]]=1;ch[v][0]=0;pushup(v);}/*................................................*/int query(int x,int y)//求点x到y之间路径上b的最大值(返回该边){ makeroot(x); access(y); splay(y); return maxv[y];} void solve(int i)//连成环,删{ int u=edges[i].u,v=edges[i].v,w=edges[i].b; int t=query(u,v);//找出b最大的边 if(w<b[t]) { cut(edges[t-n].u,t); cut(edges[t-n].v,t); link(u,i+n); link(v,i+n); } }int main(){cin>>n>>m;//初始化根为自身for(int i=1;i<=n+m;i++)//将边也看成一个单独的节点isRoot[i]=1;for(int i=1;i<=n;i++)r[i]=i;for(int i=1;i<=m;i++) scanf("%d%d%d%d",&edges[i].u,&edges[i].v,&edges[i].a,&edges[i].b); sort(edges+1,edges+m+1,cmp);//按a大小排序 for(int i=1;i<=m;i++) { b[n+i]=edges[i].b; maxv[n+i]=n+i; } for(int i=1;i<=m;i++)//维护连通,往LCT里面加边 { int u=edges[i].u,v=edges[i].v,w=edges[i].b; int rootu=find(u),rootv=find(v); if(rootu!=rootv)//未形成环,加边 { r[rootu]=rootv; link(u,n+i); link(v,n+i); } else//是环,处理 solve(i); if(find(1)==find(n))//起点终点连通,出现解 ans=min(ans,b[query(1,n)]+edges[i].a); } if(ans==INF) ans=-1; cout<<ans<<endl;return 0;}
分析2
还有人用三分的方法做到了满分,这里就不说了。
代码2
#include <bits/stdc++.h>using namespace std;const int maxn=50000+5;int cnt=0;int n,m,i,j,a,b,best=100001;struct nod{ int nex,a,b;};vector<nod> edges[maxn];int dp[maxn],q[maxn];bool vis[maxn];int ans[maxn];int cmp(nod x,nod y){ return x.a<y.a;}inline int spfa(int a){ if(ans[a]!=0) return ans[a]; memset(vis,0,sizeof(vis)); memset(dp,-1,sizeof(dp)); dp[1]=0; int head,tail; head=tail=1; q[1]=1; vis[1]=1; int now,xia,b; nod nex; while(head<=tail) { now=q[head%50003]; vis[now]=0; if(dp[n]!=-1 && dp[now]>=dp[n]) { head++; continue; } for(int j=0;j<edges[now].size();j++) { nex=edges[now][j]; xia=nex.nex; if(nex.a>a) break; b=max(dp[now],nex.b); if(dp[xia]==-1 || b<dp[xia]) { dp[xia]=b; if(vis[xia]==0) { vis[xia]=1; tail++; q[tail%50003]=xia; if(dp[q[(head+1)%50003]]>dp[q[tail%50003]]) { swap(q[(head+1)%50003],q[tail%50003]); } } } } head++; } if(dp[n]==-1) ans[a]=-1; else ans[a]=dp[n]+a; if(dp[n]==-1) return -1; return dp[n]+a;}int dfs(int l,int r){ cnt++; if(cnt>1000) return 0; if(l==r) { int temp=spfa(l); if(temp!=-1) best=min(best,temp); return 0; } if(l>r) return 0; int mid=(l+r)/2; int temp=spfa(mid); if(temp==-1) { dfs(mid+1,min(r,best)); return 0; } best=min(best,temp); int lef=best-(temp-mid); if(spfa(l)-l!=spfa(mid)-mid) dfs(l,min(lef,mid)); else { int temp=spfa(l); if(temp!=-1) best=min(best,temp); } if(spfa(r)-r!=spfa(mid)-mid) dfs(mid+1,min(r,best)); return 0;}int main(){ int zuida=0; memset(ans,0,sizeof(ans)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) edges[i].clear(); nod temp; for(int k=1;k<=m;k++) { scanf("%d%d%d%d",&i,&j,&b,&a); if(i==j) continue; zuida=max(zuida,a); temp.nex=j; temp.a=a; temp.b=b; edges[i].push_back(temp); temp.nex=i; edges[j].push_back(temp); } for(int i=1;i<=n;i++) { sort(edges[i].begin(),edges[i].end(),cmp); } if(spfa(50000)==-1) { cout<<-1<<endl; return 0; } dfs(1,zuida); cout<<best<<endl;}
动物园
(传送门)
题意
长度为N的字符串,Num[i]表示以i结尾的后缀与字符串前缀的最长公共长度,并且两个串不重叠。求(Num[i]+1)的乘积
分析
裸的KMP,求出next数组,顺便求出cnt数组(长度为i的前缀经过几次fix=next[fix]会得到0)
重新匹配一次,这次注意当fix*2>i的时候令fix=next[fix]即可
代码
#include <bits/stdc++.h>using namespace std;const int maxn=1000000+10;const int MOD=1000000007;char s[maxn];int next[maxn],cnt[maxn];long long ans;void getnext(){int fix=0;cnt[1]=1;for(int i=2;s[i];i++){while(fix && s[fix+1]!=s[i]) fix=next[fix];if(s[fix+1]==s[i]) fix++;next[i]=fix;cnt[i]=cnt[fix]+1;}}void kmp(){ans=1;int fix=0;for(int i=2;s[i];i++){while(fix && s[fix+1]!=s[i]) fix=next[fix];if(s[fix+1]==s[i]) fix++;while(fix*2>i) fix=next[fix];ans*=(cnt[fix]+1);ans%=MOD;}}int main(){int T;cin>>T;while(T--){scanf("%s",s+1);getnext();kmp();printf("%d\n",ans);}return 0;}
随机数生成器
(传送门)
题意
通过给定参数得到N*M的随机数矩阵(只包含数1-N*M),求从左上角到右下角得到数列升序排序后字典序最小的路径
分析
之所以能贪心,是因为有1的时候必选1,而2,3,4同理,所以从小到大开始加数,并暴力删除由于选这个数而不可选的其他数,并得到答案,因为每个数只会被删除一次,所以注意删除的方式可以大大降低复杂度。
代码
#include <bits/stdc++.h>using namespace std;const int MAXN=5000+5;long long seed,a,b,c,MOD;int arr[MAXN*MAXN],mp[MAXN][MAXN];bool vis[MAXN][MAXN];int m,n,Q;int getx() { return seed=(a*seed*seed%MOD+b*seed%MOD+c)%MOD; }int main(){ scanf("%lld%lld%lld%lld%lld%d%d%d",&seed,&a,&b,&c,&MOD,&m,&n,&Q); for(int i=1;i<=m*n;i++) { arr[i]=i; swap(arr[i],arr[getx()%i+1]); } while(Q--) { int x,y; scanf("%d%d",&x,&y); swap(arr[x],arr[y]); } for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) mp[i][j]=arr[(i-1)*n+j]; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) arr[mp[i][j]]=(i-1)*n+j; for(int i=1;i<=m*n;i++) { int x=arr[i]/n+1-(arr[i]%n==0),y=arr[i]-(x-1)*n; if(!vis[x][y]) { if(i!=1) printf(" "); printf("%d",i); for(int j=x+1;j<=m;j++) for(int k=y-1;k>0;k--) { if(vis[j][k]) break; vis[j][k]=1; } for(int j=x-1;j>0;j--) for(int k=y+1;k<=n;k++) { if(vis[j][k]) break; vis[j][k]=1; } } } printf("\n"); return 0;}
购票
(传送门)
题意
有根树(1为根),边有长度。每个点u有三个属性(len[u],p[u],q[u]),每次u可以转移到u的某个祖先节点v(v满足dist(u,v)<=len[u]),代价为p[u]*dist(u,v)+q[u]。求每个点都转移到1的代价。
分析
网上的解法很多,官方题解给的是树分治+CDQ分治,下面给出的是考虑用树链剖分+线段树+三分来做时间复杂度O(n(logn)^3),应该是比较好写的一个了。
代码
#include <bits/stdc++.h>using namespace std;#define lson (c<<1)#define rson (c<<1|1)#define mid ((l+r)>>1)const long long INF=999999999999999999LL;const int MAXN=200000+10;const int MAXM=MAXN<<2; struct point{ long long x,y; int num; point(){} point(long long _x,long long _y){ x=_x,y=_y;} friend point operator -(point a,point b){ return point(a.x-b.x,a.y-b.y);} friend double operator /(point a,point b){ return ((0.0+a.x)*b.y-(0.0+a.y)*b.x);}}p;//graphint tot,head[MAXN];struct Edge{ int u,next; long long v;} edges[MAXN];inline void addEdge(int x,int u,long long v){ edges[++tot]=(Edge){u,head[x],v}; head[x]=tot;}//segment treeint ch[MAXM][2];vector<point> hu[MAXM];//treeint n,F[MAXN],g[MAXN],H[MAXN],size[MAXN],son[MAXN],T,Que[MAXN],top[MAXN],ll,rr,xx;long long s[MAXN],P[MAXN],Q[MAXN],lim[MAXN],dis[MAXN],f[MAXN]; void change(int c,int l,int r,int x){ int s; while(((s=hu[c].size())>1) && ((p-hu[c][s-2])/(hu[c][s-1]-hu[c][s-2])>0)) hu[c].pop_back(); hu[c].push_back(p); if(l>=r) return; if(x<=mid) change(lson,l,mid,x); else change(rson,mid+1,r,x);} long long query(int c,int l,int r){ long long ans=INF; int lt=0,rt=hu[c].size()-1,m1,m2,i; long long lv,rv; if(ll<=l&&r<=rr) { for(lt=0,rt=hu[c].size()-1;rt-lt>3;) { m1=lt+(rt-lt)/3; m2=rt-(rt-lt)/3; lv=P[xx]*(dis[xx]-hu[c][m1].x)+hu[c][m1].y; rv=P[xx]*(dis[xx]-hu[c][m2].x)+hu[c][m2].y; if(lv<=rv) rt=m2; else lt=m1; } for(int i=lt;i<=rt;i++) ans=min(ans,P[xx]*(dis[xx]-hu[c][i].x)+Q[xx]+hu[c][i].y); return ans; } if(ll<=mid) ans=query(lson,l,mid); if(rr>mid) ans=min(ans,query(rson,mid+1,r)); return ans;} void dfs1(int c){ int t=0; size[c]=1; for(int i=head[c];i;i=edges[i].next) { dis[edges[i].u]=dis[c]+edges[i].v; dfs1(edges[i].u); size[c]+=size[edges[i].u]; if(size[edges[i].u]>t) { t=size[edges[i].u]; son[c]=edges[i].u; } }} void dfs2(int c,int tp){ g[c]=++T; H[T]=c; top[c]=tp; if(!son[c]) return; dfs2(son[c],tp); for(int i=head[c];i;i=edges[i].next) if(edges[i].u!=son[c]) dfs2(edges[i].u,edges[i].u);} void cal(int l,int r,int c){ long long ans=INF; for(xx=c;dis[top[r]]>dis[l];r=F[top[r]]) { long long=g[top[r]],rr=g[r]; ans=min(ans,query(1,1,n)); } ll=g[l],rr=g[r]; ans=min(ans,query(1,1,n)); f[c]=ans; p=point(dis[c],f[c]); p.num=c; change(1,1,n,g[c]);}int main(){ int h,t,c,l,r; long long far; scanf("%d%d",&n,&t); for(int i=2;i<=n;i++) scanf("%d%lld%lld%lld%lld",F+i,s+i,P+i,Q+i,lim+i),addEdge(F[i],i,s[i]); dfs1(1); dfs2(1,1); Que[h=t=1]=1; while(h<=t) { c=Que[h++]; for(int i=head[c];i;i=edges[i].next) Que[++t]=edges[i].u; } p=point(0,0); p.num=1; change(1,1,n,1); for(int i=2;i<=n;i++) { t=c=Que[i]; far=dis[c]-lim[c]; for(t=F[t];t>1&&dis[F[top[t]]]>far;t=F[top[t]]); if(t>1) { l=g[top[t]]; r=g[t]; while(l<=r) { if(dis[H[mid]]>=far) { t=H[mid]; r=mid-1; } else l=mid+1; } } else t=1; cal(t,F[c],c); } for(int i=2;i<=n;i++) printf("%lld\n",f[i]); return 0;}
- NOI 2014 题解
- NOI 2014简要题解
- NOI 2002 题解
- NOI 2003 题解
- NOI 2004 题解
- NOI 2005 题解
- NOI 2006 题解
- NOI 2007 题解
- NOI 2008 题解
- NOI 2009 题解
- NOI 2010 题解
- NOI 2011 题解
- NOI 2012 题解
- NOI 2013 题解
- NOI 2001 食物链 题解
- 【NOI 2014】【BZOJ 3670】动物园 题解&代码(C++)
- XJHS NOI训练题7 简要题解
- NOI 2015 软件包管理器 题解&代码
- Android的GPS的代码分析(二)
- Dynamics CRM 报表导出EXCEL 列合并问题的解决方法
- MVC4的缓存
- Intel VT-X
- Spring 之注解事务 @Transactional
- NOI 2014 题解
- DataSource 和 DataSet 区别
- 黑马程序员——Java练习笔记——反射
- spring ioc 笔记(Spring in action)
- jquery实现checkbox复选框的全选反选
- Android的GPS的代码分析(三)
- advinst实现Javaweb一键安装部署
- 由一段代码说开去系列
- MVC razor 中 RenderPartial, RenderAction , Partial , Action 的使用选择