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

如果不会LCT,可以想到SPFA的思路,即枚举a权值spfa(b)更新ans得到答案。
这样显然会超时,考虑一步步优化。
1.不对dist数组进行更新,这样可以保证单调性。
2.随a权值递增而加边,同时在函数外让点入队。
3.对a权值排序然后进行枚举。这样做可以做到满分,而且比正解LCT更快。

还有人用三分的方法做到了满分,这里就不说了。

代码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;}


0 0