NOI 2014简要题解

Day 1.Problem A. 起床困难综合症

100分做法:

把数字看成二进制数。对于初始攻击力,我们将其拆成32位,并求出每一位为0和1时经过所有防御门之后分别得到的数字。然后就是按位贪心了,我们尽量让初始攻击力的高位在经过所有防御门后变成1而不是0,根据这一贪心思想,剩下要做的就是个很简单的数位贪心问题了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 110000

using namespace std;

typedef long long int LL;

int n,m;
int op[MAXN]; //op[i]=1:AND 2:OR 3:XOR
int t[MAXN];

namespace Biaozhun
{
    int digit[100],len=0; //最低位在digit[1]
    int ans[100][2]; //ans[i][1]=从低到高第i位,初始取1,最后得到的答案
    int sol[100]; //sol保存的是最后得到的答案的最大值

    void getDigit(int x)
    {
        while(x)
        {
            digit[++len]=x&1;
            x>>=1;
        }
    }

    void solve()
    {
        getDigit(m);
        for(int Dig=0;Dig<32;Dig++)
        {
            int num=0; //初始为0
            for(int i=1;i<=n;i++)
            {
                if(op[i]==1) //AND
                    num=num&((t[i]&(1<<Dig))?1:0);
                else if(op[i]==2) //OR
                    num=num|((t[i]&(1<<Dig))?1:0);
                else //XOR
                    num=num^((t[i]&(1<<Dig))?1:0);
            }
            ans[Dig+1][0]=num;

            num=1; //初始为1
            for(int i=1;i<=n;i++)
            {
                if(op[i]==1) //AND
                    num=num&((t[i]&(1<<Dig))?1:0);
                else if(op[i]==2) //OR
                    num=num|((t[i]&(1<<Dig))?1:0);
                else //XOR
                    num=num^((t[i]&(1<<Dig))?1:0);
            }
            ans[Dig+1][1]=num;
        }

        bool flag=true; //flag=true表示当前前缀还是和m一样大的
        for(int i=32;i>len;i--) sol[i]=ans[i][0];
        for(int i=len;i>=1;i--)
        {
            if(!flag) //初始数字的第i位可以随便填
            {
                if(ans[i][0]) sol[i]=1;
                else if(ans[i][1]) sol[i]=1;
                else sol[i]=0;
            }
            else
            {
                if(digit[i]==1)
                {
                    if(ans[i][0])
                    {
                        flag=false;
                        sol[i]=1;
                    }
                    else if(ans[i][1]) sol[i]=1;
                    else
                    {
                        flag=false;
                        sol[i]=0;
                    }
                }
                else
                {
                    if(ans[i][0])
                        sol[i]=1;
                    else
                        sol[i]=0;
                }
            }
        }
        long long int sum=0;
        for(int i=32;i>=1;i--)
        {
            sum<<=1LL;
            sum|=(LL)sol[i];
        }
        printf("%lld\n",sum);
    }
}

int main()
{
    //freopen("in","r",stdin);
    //freopen("out_ceshi","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        char cmd[10];
        scanf("%s",cmd);
        if(cmd[0]==‘A‘) op[i]=1;
        else if(cmd[0]==‘O‘) op[i]=2;
        else if(cmd[0]==‘X‘) op[i]=3;
        scanf("%d",&t[i]);
    }
    using namespace Biaozhun;
    solve();
    return 0;
}

Day 1.Problem B. 魔法森林

15分做法

直接做DFS暴力就行了

50分做法

考虑到一些点里a的范围比较小,因此我们可以枚举初始时携带的A精灵个数limitA,然后二分初始时携带的B精灵个数limitB,BFS来判断是否能走到终点。由于边上的ai的值最多只可能有边数m个,因此只需要枚举每种不同的ai来作为limitA就行了,时间复杂度O(mlog(maxbi)n)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>

using namespace std;

int n,m;
inline int read()
{
    int tmp=0;
    char ch=getchar();
    while(ch==‘ ‘||ch==‘\n‘) ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘) tmp=tmp*10+ch-‘0‘,ch=getchar();
    return tmp;
}

namespace BFSSearch
{
    const int MAXV=51000;
    const int MAXE=210000;

    struct edge
    {
        int u,v,next;
        int a,b;
    }edges[MAXE];

    int head[MAXV],nCount=0;

    void AddEdge(int U,int V,int A,int B)
    {
        edges[++nCount].u=U;
        edges[nCount].v=V;
        edges[nCount].a=A;
        edges[nCount].b=B;
        edges[nCount].next=head[U];
        head[U]=nCount;
    }

    int q[MAXE*2],h=0,t=0,S,T;
    int vis[MAXV],total=0;

    inline bool BFS(int limitA,int limitB)
    {
        total++;
        h=0,t=0;
        //memset(vis,false,sizeof(vis));
        //cout<<S<<‘ ‘<<T<<endl;
        q[t++]=1;
        vis[1]=total;
        while(h<t)
        {
            int u=q[h++];
            if(u==n)
                return true;
            for(int p=head[u];p!=-1;p=edges[p].next)
            {
                int v=edges[p].v;
                if(vis[v]==total) continue;
                if(edges[p].a<=limitA&&edges[p].b<=limitB)
                {
                    q[t++]=v;
                    vis[v]=total;
                }
            }
        }
        return false;
    }

    int sta[MAXE],top=0;
    int minans=1000000000;

    void solve()
    {
        memset(head,-1,sizeof(head));
        S=1,T=n;
        int maxB=0;
        int maxA=0;
        for(int i=1;i<=m;i++)
        {
            int U,V,A,B;
            U=read(),V=read(),A=read(),B=read();
            AddEdge(U,V,A,B);
            AddEdge(V,U,A,B);
            sta[++top]=A;
            maxA=max(maxA,A); maxB=max(maxB,B);
        }
        if(n>5000&&m>10000&&maxA>30)
        {
            printf("-1\n");
            return;
        }
        sort(sta+1,sta+top+1);
        top=unique(sta+1,sta+top+1)-sta;
        for(int i=1;i<=top;i++)
        {
            bool flag=false;
            int costA=sta[i];
            if(!BFS(costA,maxB)) continue;
            int lowerBound=0,upperBound=maxB,costB=0;
            while(lowerBound<=upperBound)
            {
                int mid=(lowerBound+upperBound)>>1;
                if(BFS(costA,mid))
                {
                    flag=true;
                    costB=mid;
                    upperBound=mid-1;
                }
                else lowerBound=mid+1;
            }
            if(flag)
                minans=min(minans,costA+costB);
        }
        if(minans==1000000000) minans=-1;
        printf("%d\n",minans);
    }
}

int main()
{
    //freopen("in","r",stdin);
    //freopen("out_ceshi","w",stdout);
    scanf("%d%d",&n,&m);
    using namespace BFSSearch;
    solve();
    return 0;
}

70分做法

我们可以和50分做法一样枚举ai最大值limitA,在所有的ai≤limitA的边里,维护一棵bi之和最小的MST,若在MST加入某条边i之后,点1和点n连通,那么显然此时limitB最小可以取bi,因为可以预处理对所有边进行排序,所以这种做法时间复杂度是O(nm),卡卡常数就能得到70分

//70分做法:Kruscal
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 210000

using namespace std;

struct edge
{
    int u,v,a,b;
}edges[MAXN];

int f[MAXN];

int findSet(int x)
{
    if(f[x]==x) return f[x];
    return f[x]=findSet(f[x]);
}

int read()
{
    int ans=0;
    char ch=getchar();
    while(ch==‘ ‘||ch==‘\n‘) ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘) ans=ans*10+ch-‘0‘,ch=getchar();
    return ans;
}

int n,m;
int sta[MAXN],top=0;
int minans=1000000000;

bool cmp(edge a,edge b)
{
    return a.b<b.b;
}

int Kruscal(int limitA)
{
    int tot=0;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
    {
        if(edges[i].a>limitA) continue;
        int rootu=findSet(edges[i].u),rootv=findSet(edges[i].v);
        if(rootu==rootv) continue;
        f[rootu]=rootv;
        tot++;
        if(findSet(1)==findSet(n)) return edges[i].b;
    }
    return -1;
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
        edges[i].u=read(),edges[i].v=read(),edges[i].a=read(),edges[i].b=read(),sta[++top]=edges[i].a;
    sort(sta+1,sta+top+1);
    sort(edges+1,edges+m+1,cmp);
    top=unique(sta+1,sta+top+1)-sta;
    for(int i=1;i<=top;i++)
    {
        int tmp=Kruscal(sta[i]);
        if(tmp!=-1) minans=min(minans,sta[i]+tmp);
    }
    if(minans==1000000000) minans=-1;
    printf("%d\n",minans);
    return 0;
}

100分做法:

我们可以用link-cut tree来维护此图的一个生成树。初始时,我们对所有边按ai升序排序,往MST里不断放入边,若当前边连接的是两个不同的联通块,则可以直接加入进去。否则若当前边的bi比MST中这条边的两端点连接的路径中bi最大的那条边的bi小的话,则可以加入这条边,并替换掉MST中这条边的两端点连接的路径中bi最大的那条边。若当前点1和点n连通的话,用当前路径上的max{ai}+max{bi}去更新答案

Day 1.Problem C. 消除游戏

第一个测试点

目测可以发现,第一个测试点的矩阵里是一条回文串+一条大质数构成的,手打输出数据就能得到10分

Day 2.Problem A. 动物园

15分做法

对于每个前缀,暴力枚举它的每个前缀是否能满足题目的要求。时间复杂度O(|S|3)

25分做法

与15分做法类似,对于每个前缀,暴力枚举它的每个前缀是否能满足题目的要求。不同的是使用Hash而不是用暴力来匹配字符串。时间复杂度O(|S|2)

50分做法

定义cnt[i]=前缀i里,既是前缀又是后缀(注意没了长度要小于i的限制,也就是说前缀i也可以满足这一条件)的子串个数。cnt[i]=1,假设前缀p是前缀i里最大的,既是前缀又是后缀,长度又≤i2的子串,那么num[i]=cnt[p](前缀p里既是前缀又是后缀的子串个数=[1,p]里是前缀、[i-p+1,i]里是后缀的子串个数)

考虑next指针。next[i]=前缀i里最大的既是前缀又是后缀、且长度<i的子串。我们可以从小到大枚举i,对于每个i,从next[i]开始沿着next指针走,找出最大的既是前缀又是后缀、且长度≤i2的子串(前缀p)。但是构造一组全部是一种字母的(如aaaa)的字符串,就能使该算法退化到O(|S|2),UOJ中实测50分。

100分做法

与50分做法基本上差不多,但是对于每个前缀i,不是暴力找p,可以发现p指针可以均摊O(1)求出,具体看代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>

#define MAXN 1100000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

char s[MAXN];
int next[MAXN],n;
int cnt[MAXN];

void getnext()
{
    next[1]=0;
    int p=0;
    cnt[1]=1;
    for(int i=2;i<=n;i++)
    {
        while(p&&s[p+1]!=s[i]) p=next[p];
        if(s[p+1]==s[i]) p++;
        next[i]=p;
        cnt[i]=cnt[p]+1;
    }
}

LL f[MAXN];

void calc()
{
    int p=0;
    for(int i=2;i<=n;i++)
    {
        while(p&&s[p+1]!=s[i]) p=next[p];
        if(s[p+1]==s[i]) p++;
        while(p&&p*2>i) p=next[p];
        f[i]=cnt[p];
    }
}

int main()
{
    //freopen("in","r",stdin);
    //freopen("out_ceshi","w",stdout);
    int T;
    scanf("%d",&T);
    //cout<<"dd"<<endl;
    while(T--)
    {
        memset(f,0,sizeof(f));
        scanf("%s",s+1);
        n=strlen(s+1);
        getnext();
        calc();
        //for(int i=1;i<=n;i++) cout<<f[i]<<" ";
        //cout<<endl;
        LL ans=1;
        for(int i=1;i<=n;i++)
            ans=(ans*(f[i]+1))%MOD;
        printf("%lld\n",ans);
    }
    return 0;
}

Day 2.Problem B. 随机数生成器

10分做法

DFS乱搞

60分做法

问题可以转述为,给棋盘中n+m-1个格子涂色,使得左上角的格子和右下角的格子四连通。

观察发现,要想让得到的序列的字典序尽量小,就应该从1到nm,从小到大填入数字,尽量把小数字都填色。每次给一个格子填色后,会使得该格子左下角和右上角的区域不能填色了。

因此我们可以用二维树状数组来维护每个格子是否能填色。与一般的二维树状数组不同的是,这里需要支持区间更新单点查询,因此可以使用差分约束系统,若对左上角为(x1,y1),右下角为(x2,y2)的矩阵区间+1,就相当于在BIT中对(x1,y1)单点+1,(x2+1,y1)单点-1,(x2+1,y2+1)单点+1,(x1,y2+1)单点-1,如下图:

时间复杂度:O(nmlognlogm)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>

#define calcpos(x,y) (((x)-1)*m+y)
#define lowbit(x) ((x)&(-(x)))

using namespace std;

typedef long long int LL;

LL lastx,a,b,c,d;
int n,m,Q;

int randnum()
{
    LL x=(((a*lastx)%d)*lastx%d+(b*lastx)%d+c)%d;
    lastx=x;
    return (int)x;
}

namespace Greedy
{
    const int MAXN=2100;
    int seq[MAXN*MAXN],pos[MAXN*MAXN]; //pos[i]=数字i在随机序列里的下标
    int ansseq[MAXN*3],top=0;
    bool used[MAXN*MAXN],vislayer[MAXN*3]; //used[(i,j)]=true表示(i,j)这个格子要被经过,vislayer[i]=true表示步数i所走的格子已经确定了
    int bit[MAXN][MAXN];

    void getRandomSeq(int len)
    {
        for(int i=1;i<=len;i++) seq[i]=i;
        for(int i=1;i<=len;i++)
            swap(seq[i],seq[(randnum()%i)+1]);
        for(int i=1;i<=Q;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            swap(seq[u],seq[v]);
        }
        for(int i=1;i<=len;i++) pos[seq[i]]=i;
    }

    inline bool inMap(int x,int y)
    {
        if(x<1||x>n||y<1||y>m) return false;
        return true;
    }

    inline void update(int x,int y,int val)
    {
        while(x<=n)
        {
            int tmpy=y;
            while(tmpy<=m)
            {
                bit[x][tmpy]+=val;
                tmpy+=lowbit(tmpy);
            }
            x+=lowbit(x);
        }
    }

    int query(int x,int y) //false表示(x,y)可以用
    {
        int ans=0;
        while(x)
        {
            int tmpy=y;
            while(tmpy)
            {
                ans+=bit[x][tmpy];
                tmpy-=lowbit(tmpy);
            }
            x-=lowbit(x);
        }
        return ans;
    }

    void Update(int zsx,int zsy,int yxx,int yxy)
    {
        yxx++,yxy++;
        update(yxx,yxy,-1);
        update(zsx,zsy,-1);
        update(yxx,zsy,1);
        update(zsx,yxy,1);
    }

    int Query(int zsx,int zsy,int yxx,int yxy)
    {
        zsx++,zsy++,yxx++,yxy++;
        return query(yxx,yxy)+query(zsx-1,zsy-1)-query(yxx,zsy-1)-query(zsx-1,yxy);
    }

    void solve()
    {
        getRandomSeq(n*m);
        int R=n*m;
        ansseq[++top]=seq[1];
        if(n!=1&&m!=1) ansseq[++top]=seq[R];
        int nowX,nowY;
        nowX=1,nowY=1;
        Update(nowX+1,1,n,nowY-1);
        Update(1,nowY+1,nowX-1,m);
        nowX=n,nowY=m;
        Update(nowX+1,1,n,nowY-1);
        Update(1,nowY+1,nowX-1,m);
        for(int i=1;i<=R;i++)
        {

            if(i==seq[1]||i==seq[R]) continue;
            int nowpos=pos[i];
            if(nowpos%m)
            {
                nowX=nowpos/m+1;
                nowY=nowpos%m;
            }
            else
            {
                nowX=nowpos/m;
                nowY=m;
            }
            //cout<<nowX<<" "<<nowY<<endl;
            //cout<<Query(nowX,nowY,nowX,nowY)<<" dd: "<<i<<endl;
            if(query(nowX,nowY)) continue;

            ansseq[++top]=i;
            if(top==n+m-1) break;
            Update(nowX+1,1,n,nowY-1);
            Update(1,nowY+1,nowX-1,m);
        }
        sort(ansseq+1,ansseq+top+1);
        for(int i=1;i<=top;i++)
            printf("%d ",ansseq[i]);
        printf("\n");
    }
}

int main()
{
    scanf("%lld%lld%lld%lld%lld",&lastx,&a,&b,&c,&d);
    scanf("%d%d%d",&n,&m,&Q);
    using namespace Greedy;
    solve();
    return 0;
}

100分做法

非常搞笑的是,此题满分做法比70分做法好写很多!

思路基本上和70分做法差不多,只不过维护每个点是否可以染色,从二维树状数组维护,变成了暴力维护。由于每行可以染色的区域永远是一个连续的区间,因此我们只需要记录每一行i所对应的可行染色区域[Li,Ri]即可,那么这样的话,每次查询某个格子是否可以染色是O(1)的,而更新L[],R[]数组则是O(n)的,此题里,查询次数是O(nm),更新次数则是O(n+m),因此非常神奇的一幕发生了:这种看上去非常sb的暴力,复杂度仅有O(n(n+m))

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>

#define calcpos(x,y) (((x)-1)*m+y)
#define lowbit(x) ((x)&(-(x)))

using namespace std;

typedef long long int LL;

LL lastx,a,b,c,d;
int n,m,Q;

int randnum()
{
    LL x=((a*lastx+b)*lastx+c)%d;
    lastx=x;
    return (int)x;
}

namespace Greedy
{
    const int MAXN=5100;
    int seq[MAXN*MAXN],pos[MAXN*MAXN]; //pos[i]=数字i在随机序列里的下标
    int ansseq[MAXN*3],top=0;
    int L[MAXN],R[MAXN];

    void getRandomSeq(int len)
    {
        for(int i=1;i<=len;i++) seq[i]=i;
        for(int i=1;i<=len;i++)
            swap(seq[i],seq[(randnum()%i)+1]);
        for(int i=1;i<=Q;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            swap(seq[u],seq[v]);
        }
        for(int i=1;i<=len;i++) pos[seq[i]]=i;
    }

    void solve()
    {
        getRandomSeq(n*m);
        int End=n*m;
        ansseq[++top]=seq[1];
        if(n!=1&&m!=1) ansseq[++top]=seq[End];
        for(int i=1;i<=n;i++) L[i]=1,R[i]=m;
        for(int i=1;i<=End;i++)
        {
            int nowX,nowY;
            if(i==seq[1]||i==seq[End]) continue;
            int nowpos=pos[i];
            if(nowpos%m)
            {
                nowX=nowpos/m+1;
                nowY=nowpos%m;
            }
            else
            {
                nowX=nowpos/m;
                nowY=m;
            }
            //cout<<"nowX: "<<nowX<<" nowY: "<<nowY<<" L: "<<L[nowX]<<" R: "<<R[nowX]<<endl;
            if(nowY<L[nowX]||nowY>R[nowX]) continue;
            ansseq[++top]=i;
            if(top==n+m-1) break;
            for(int i=1;i<=nowX-1;i++)
                R[i]=min(R[i],nowY);
            for(int i=nowX+1;i<=n;i++)
                L[i]=max(L[i],nowY);
        }
        sort(ansseq+1,ansseq+top+1);
        for(int i=1;i<=top;i++)
            printf("%d ",ansseq[i]);
        printf("\n");
    }
}

int main()
{
    scanf("%lld%lld%lld%lld%lld",&lastx,&a,&b,&c,&d);
    scanf("%d%d%d",&n,&m,&Q);
    using namespace Greedy;
    solve();
    return 0;
}

Day 2.Problem C. 购票

30分做法

暴力地求出树中每一层的结点是哪些。然后我们按照树的层数为阶段做DP,f[i]=点i到根节点所需最少费用。那么f[i]可以转移到f[j],当且仅当j在i的子树里。我们在dp f[i]时,直接沿着父亲指针往上爬来枚举j即可

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>

#define MAXN 210000

using namespace std;

typedef long long int LL;

vector<int>listOfDep[MAXN];

int n,T;
pair<int,int>q[MAXN*2];
LL P[MAXN],Q[MAXN],dis[MAXN];

struct edge
{
    int u,v,next;
    LL w;
}edges[MAXN];

int head[MAXN],nCount=0;

void AddEdge(int U,int V,LL W)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].w=W;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

int fa[MAXN];
int maxDep=0;
LL Depth[MAXN]; //Depth[i]=i到根节点的距离
LL f[MAXN];

void BFS()
{
    int h=0,t=1;
    q[h]=make_pair(1,1);
    listOfDep[1].push_back(1);
    Depth[1]=0;
    while(h<t)
    {
        int u=q[h].first,dep=q[h++].second;
        maxDep=max(maxDep,dep);
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            q[t++]=make_pair(v,dep+1);
            listOfDep[dep+1].push_back(v);
            Depth[v]=Depth[u]+edges[p].w;
        }
    }
}

void DP()
{
    f[1]=0;
    for(int depth=2;depth<=maxDep;depth++)
    {
        for(int id=0;id<listOfDep[depth].size();id++)
        {
            int i=listOfDep[depth][id];
            for(int j=fa[i];j;j=fa[j])
            {
                if(Depth[i]-Depth[j]>dis[i]) break;
                f[i]=min(f[i],f[j]+P[i]*(Depth[i]-Depth[j])+Q[i]);
            }
        }
    }
}

void solve()
{
    memset(head,-1,sizeof(head));
    memset(f,0x3f,sizeof(f));
    for(int i=2;i<=n;i++)
    {
        LL w;
        scanf("%d%lld%lld%lld%lld",&fa[i],&w,&P[i],&Q[i],&dis[i]);
        AddEdge(fa[i],i,w);
    }
    BFS();
    DP();
    for(int i=2;i<=n;i++) printf("%lld\n",f[i]);
}

int main()
{
    scanf("%d%d",&n,&T);
    solve();
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-24 16:17:22

NOI 2014简要题解的相关文章

Codechef July Challenge 2014部分题解

Dish Owner(并查集) 链接:http://www.codechef.com/JULY14/problems/DISHOWN/ 题意分析:本题主要操作就是给出0 x y时,拥有第x道菜的厨师与拥有第y道菜的厨师pk,谁拥有的所有菜的其中一道菜(不一定是x或y)的分值比较高谁就获胜,并赢得loser的所有菜.即比较的是每个人分值最高的菜,所以对于非loser的人来说,他的分值最高的菜是不变的.综合题意用并查集易解. #include <cstdio> const int Maxn=100

[BZOJ 3669][NOI 2014]魔法森林(Link-Cut Tree)

题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3669 记得四个月之前的NOI同步赛,我还只会玩脚丫子.... 记得我当时看到这个题整个人就吓傻了,完全不知道怎么做,然后NOI同步赛就这样爆零了... 如今我学了LCT这个神器,再看这个题,感觉不再那么难了. 其实这个题的标准解法是SPFA,改得完全认不出来的SPFA. orz太神了,完全没见识过这么神犇的SPFA,NOIP 2014考了SPFA,NOI 2014也考了SPFA,

AGC025简要题解

AGC025简要题解 B RGB Coloring 一道简单题,枚举即可. C Interval Game 考虑可以进行的操作只有两种,即左拉和右拉,连续进行两次相同的操作是没有用的. 左拉时肯定会选择右端点尽量小的,右拉选择左端点尽量大的,所以排序之后贪心即可. D Choosing Points 首先证明对于所有\(d\),假设让两个不能同时选的点之间连一条边,那么结果是一张二分图. \(d\)是奇数可以黑白染色,\(d\)是偶数的时候,显然连边的两点在同一个颜色内.那么我们可以只考虑这个颜

月考简要题解

模拟赛简要题解 一下题目均可在loj上找到 10178. 「一本通 5.5 例 4」旅行问题 简单题,将n扩大到2 * n,单调队列即可,注意正反向. #include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=2000

JXOI2018简要题解

JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法描述大致如下: 假设我们要对一个大小为 \(n\) 的数列 \(a\) 排序. 等概率随机生成一个大小为 \(n\) 的排列 \(p\) . 构造一个大小为 \(n\) 的数列 \(b\) 满足 \(b_i=a_{p_i}\) ,检查 \(b\) 是否有序,如果 \(b\) 已经有序了就结束算法,并

BJOI2018简要题解

BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生成了一个长为 \(n\) 的二进制串,希望你对于这个二进制串的一个子区间,能求出其有多少位置不同的连续子串,满足在重新排列后(可包含前导 \(0\))是一个 \(3\) 的倍数.两个位置不同的子区间指开始位置不同或结束位置不同. 由于他想尝试尽量多的情况,他有时会修改串中的一个位置,并且会进行多次询

杂题记录及简要题解(三)

以下是大概 5 月初开始做的一些题.以前的简要题解都是骗人的.这次真的是简要题解了(大雾 相对之前改良了一下题目名称的格式. 2017 计蒜之道 初赛 - 腾讯狼人杀 二分答案 \(x\) 后原问题变为检验是否存在选取方案 \((V, E)(|V| = k)\) 使得 \(\sum_\limits{e \in E} w_e - xk \cdot (2n- k)\).式子可以写成 \(\sum_\limits{e \in E} w_e + \frac{k(k - 1)}{2} \cdot 2x -

【简要题解】Hihocoder 重复旋律1-8简要题解

[简要题解]Hihocoder 重复旋律1-8简要题解 编号 名称标签 难度 1403 后缀数组一·重复旋律 Lv.4 1407 后缀数组二·重复旋律2 Lv.4 1415 后缀数组三·重复旋律3 Lv.4 1419 后缀数组四·重复旋律4 Lv.4 1445 后缀自动机二·重复旋律5 Lv.4 1449 后缀自动机三·重复旋律6 Lv.4 1457 后缀自动机四·重复旋律7 Lv.1 1465 后缀自动机五·重复旋律8 Lv.1 1466 后缀自动机六·重复旋律9 Lv.1 后缀数组 思路简单

《信奥一本通》提高版—简要题解

<信奥一本通>提高版-简要题解 贪心 活动安排: 按右端点排序,因为越早结束越好. 然后从1扫到n,每次比较当前位置线段的左端点是否大于上一个选的线段的右端点.如果大于,那么ans++,将上一个选的线段的右端点更新为当前线段的右端点:如果小于,那什么都不用做.因为选上一条一定比选当前这一条更优(结束时间更早). 种树 按右端点排序,对于每个区间的需求,从右端往左端扫,只要没种到树的就种,ans++. 因为要使前面的需求尽量与后面的需求重叠,从而使树的数量最少 喷水装置 观察+画图发现对于一个圆