NOI 2014 题解

起床困难综合症

(传送门)

题意

在[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;
//graph
int 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 tree
int ch[MAXM][2];
vector<point> hu[MAXM];
//tree
int 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;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值