[SinGuLaRiTy] NOIP2017 提高组

【SinGuLaRiTy-1048】 Copyright (c) SinGuLaRiTy 2018. All Rights Reserved.

NOIP2017过了这么久,现在2018了才找到寒假这么一个空挡来写题解。哎,没办法,谁叫学校抓的紧呢。

序 | Before

这是我的最后一次NOIP。

因为是最后一次的原因吧,考前压力就蛮大的,再加上各种模拟赛,模板练习的轮番轰炸,走进考场时整个人都是“飘飘欲仙”的感觉~

我的NOIP2017就在这种“飘飘欲仙”的氛围下开始了。

游记 | Blogs

写游记是老规矩。

Day 1

T1 略略一瞟题目,我的妈,感觉是一个牛逼哄哄的数论题耶( •̀ ω •́ )y。磨了半天,最开始打表找规律,然后再画画数轴——咦?好像搞出来了些东西:ans=a*b-a-b?

嗯,就是这个样子的。

T2 看起来就很费时间,最后做......(1 hour later)不就是字符串的傻逼模拟题吗?就是模拟太TM复杂。(话说Day 1的字符串题我怎么没练过?)

剩下的时间都喂给T2了,考完了也没把判格式的那段调出来,直接报零。

T3 嗯,考前的模拟赛好像做过类似的(兴奋)。果断拓扑排序判0环,再加上DP。

考完后对答案:

“大佬,T3是不是拓扑排序判零环?”

“是的。”(淡定的回答)

“太好了欧耶!”

......

“你清数组了吗?”

“啥?要menset?”

GG......手动滑稽

Day 2

T1 紧张地看完题目,再瞟一眼数据范围......我的妈,这不就是dfs吗。10分钟码完。(今年做的最顺的一道题)

考完:“我为什么要脑抽加手残用一个sqrt?!”

T2 看起来就蛮复杂的——T3看起来好欺负一点。

被T3打回来:这玩意儿似乎是状压DP?

T3 嗯,这题怎么这么水?!——我的妈,这数据范围怎么这么恶心?O(M*N)都不让过?好吧,还是做做T2吧。

最后30分钟,又想了想,算了,还是打一个40分的暴力吧。

 

NOIP2017炸的飞起。总分340(为什么重庆作为竞赛弱“市”,一等线那么高?早知就去贵州了......)

题解 | Solution

D1T1 小凯的疑惑

【问题描述】
小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在小凯无法准确支付的商品。
【输入格式】
输入文件名为 math.in。
输入数据仅一行,包含两个正整数 a 和 b,它们之间用一个空格隔开,表示小凯手中金币的面值。
【输出格式】
输出文件名为 math.out。
输出文件仅一行,一个正整数 N,表示不找零的情况下,小凯用手中的金币不能准确支付的最贵的物品的价值。
【数据规模与约定】
对于 30%的数据: 1 ≤ a,b ≤ 50。
对于 60%的数据: 1 ≤ a,b ≤ 10,000。
对于 100%的数据:1 ≤ a,b ≤ 1,000,000,000。

【题解】

找规律吧,没什么可以说的。

#include<cstdio>
#include<iostream>

using namespace std;

long long a,b;

int main()
{
    cin>>a>>b;
    cout<<a*b-a-b;
    return 0;
}

D1T2 时间复杂度

【问题描述】
小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。
A++语言的循环结构如下:

其中“F i x y”表示新建变量 (i 变量 i 不可与未被销毁的变量重名)并初始化为 x,然后判断 i 和 y 的大小关系,若 i 小于等于 y 则进入循环,否则不进入。每次循环结束后i都会被修改成i +1,一旦i 大于 y 终止循环。
x和 y 可以是正整数(x 和 y 的大小关系不定)或变量 n。n 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100。
“E”表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。
注:本题中为了书写方便,在描述复杂度时,使用大写英文字母“O”表示通常意义下“Θ”的概念。
【输入格式】
输入文件名为 complexity.in。
输入文件第一行一个正整数 t,表示有 t(t ≤ 10)个程序需要计算时间复杂度。每个程序我们只需抽取其中 “F i x y”和“E”即可计算时间复杂度。注意:循环结构允许嵌套。
接下来每个程序的第一行包含一个正整数 L 和一个字符串,L 代表程序行数,字符串表示这个程序的复杂度,“O(1)”表示常数复杂度,“O(n^w)”表示复杂度为n^w,其中 w 是一个小于 100 的正整数(输入中不包含引号),输入保证复杂度只有 O(1)和 O(n^w) 两种类型。
接下来 L 行代表程序中循环结构中的“F i x y”或者 “E”。
程序行若以“F”开头,表示进入一个循环,之后有空格分离的三个字符(串)i x y,其中 i 是一个小写字母(保证不为“n”),表示新建的变量名,x 和 y 可能是正整数或 n ,已知若为正整数则一定小于 100。
程序行若以“E”开头,则表示循环体结束。
【输出格式】
输出文件名为 complexity.out。
输出文件共 t 行,对应输入的 t 个程序,每行输出“Yes”或“No”或者“ERR”(输出中不包含引号),若程序实际复杂度与输入给出的复杂度一致则输出“Yes”,不一致则输出“No”,若程序有语法错误(其中语法错误只有: F 和E 不匹配;新建的变量与已经存在但未被销毁的变量重复两种情况),则输出“ERR”。
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出 “ERR”。
【数据规模与约定】
对于 30%的数据:不存在语法错误,数据保证小明给出的每个程序的前 L/2 行一定为以 F 开头的语句,第L/2+1 行至第 L 行一定为以 E 开头的语句,L<=10,若 x、y 均为整数,x 一定小于 y,且只有 y 有可能为 n。
对于50%的数据:不存在语法错误,L<=100,且若x、y均为整数,x 一定小于 y,且只有 y 有可能为 n。
对于 70%的数据:不存在语法错误,L<=100。
对于 100%的数据:L<=100。

 【题解】

用STL stack进行模拟,话说这代码量在历届NOIP的模拟中居然还算短的?!

#include<cstring>
#include<stack>
#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>

using namespace std;

int t,hang,zhishu,wuxiao=0,ans,qzhishu;

struct chuan
{
    char bianliang;
    int zhishu_changshu;//0无效 1指数 2常数
} aaa;

stack<chuan>q;
bool changshu,shiyong[10000],flag;
char fuzadu[100],kaishi[100],jieshu[100];



void xvnhuan()
{
    cin>>aaa.bianliang>>kaishi>>jieshu;
    if(!shiyong[int(aaa.bianliang)])shiyong[int(aaa.bianliang)]=1;//是否销毁
    else
    {
        if(!flag)printf("ERR\n");
        flag=1;
    }
    if(kaishi[0]==jieshu[0]&&jieshu[0]=='n')
    {
        q.push((chuan)
        {
            aaa.bianliang,2
        });
        return ;
    }
    int lena=strlen(kaishi),shua=0;
    int lenb=strlen(jieshu),shub=0;
    for(int i=0; i<lena; i++) //第一个串
    {
        if(kaishi[i]!='n')
        {
            shua*=10;
            shua+=(int(kaishi[i])-48);
        }
        else
        {
            q.push((chuan)
            {
                aaa.bianliang,0
            });
            wuxiao++;//无效。
            return ;
        }
    }
    for(int i=0; i<lenb; i++) //第二个串
    {
        if(jieshu[i]!='n')
        {
            shub*=10;
            shub+=(int(jieshu[i])-48);
        }
        else if(jieshu[i]=='n')
        {
            q.push((chuan)
            {
                aaa.bianliang,1
            });
            if(!wuxiao)
            {
                qzhishu++;
                ans=max(ans,qzhishu);
            }
            return;
        }
    }
    if(shua<=shub)q.push((chuan)
    {
        aaa.bianliang,2
    });
    else
    {
        q.push((chuan)
        {
            aaa.bianliang,0
        });
        wuxiao++;
    }//无效
}

void mem()
{
    qzhishu=0;
    ans=0;
    wuxiao=0;
    flag=0;
    changshu=0;
    zhishu=0;
    memset(fuzadu,0,sizeof(fuzadu));
    memset(shiyong,0,sizeof(shiyong));
    while(!q.empty())q.pop();
}

void Print()
{
    if(!flag)
    {
        if(!q.empty())
        {
            printf("ERR\n");
            return;
        }
        if(changshu)
        {
            if(!ans)printf("Yes\n");
            else printf("No\n");
            return ;
        }

        else if(!changshu)
        {
            if(ans==zhishu)printf("Yes\n");
            else printf("No\n");
            return ;
        }
    }
}

int main()
{
    scanf("%d",&t);
    for(int ll=1; ll<=t; ll++)
    {
        mem();
        scanf("%d%s",&hang,fuzadu);
        int changdu=strlen(fuzadu);
        if(fuzadu[2]=='1')
        {
            changshu=1;
        }
        else
        {
            for(int i=4; i<changdu-1; i++)
            {
                zhishu*=10;
                zhishu=zhishu+int(fuzadu[i])-48;
            }
        }
        for(int i=1; i<=hang; i++)
        {
            char leixing;
            scanf("%s",&leixing);
            if(leixing=='F')xvnhuan();
            else
            {
                if(q.empty())
                {
                    if(!flag)printf("ERR\n");
                    flag=1;
                }
                else
                {
                    chuan aaaa=q.top();
                    q.pop();
                    shiyong[int(aaaa.bianliang)]=0;
                    if(aaaa.zhishu_changshu==0)wuxiao--;
                    if(aaaa.zhishu_changshu==1&&wuxiao==0)qzhishu--;
                }
            }
        }
        Print();
    }
    return 0;
}

D1T3 逛公园

【问题描述】
策策同学特别喜欢逛公园。公园可以看成一张N个点M条边构成的有向图,且没有自环和重边。其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值,代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从1号点进去,从N号点出来。
策策喜欢新鲜的事物,他不希望有两天逛公园的路线完全一样,同时策策还是一个特别热爱学习的好孩子,他不希望每天在逛公园这件事上花费太多的时间。如果1号点到N号点的最短路长为d,那么策策只会喜欢长度不超过d+K的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮他吗?
为避免输出过大,答案对P取模。
如果有无穷多条合法的路线,请输出−1。
【输入格式】
输入文件名为 park.in。
第一行包含一个整数T, 代表数据组数。
接下来T组数据,对于每组数据:
第一行包含四个整数N, M, K, P,每两个整数之间用一个空格隔开。
接下来M行,每行三个整数ai , bi , ci,代表编号为ai, bi的点之间有一条权值为ci的有向边,每两个整数之间用一个空格隔开。
【输出格式】
输出文件名为 park.out。
输出文件包含T行,每行一个整数代表答案。
【数据规模与约定】
对于不同的测试点,我们约定各种参数的规模不会超过如下:

对于100%的数据, 1≤P≤109,1≤ai,bi≤𝑁,0≤ci ≤ 1000。
数据保证:至少存在一条合法的路线。

【题解】

最短路+拓扑排序+DP

先跑最短路。发现K只有50,所以一定是要从K入手。所以考虑DP,令f[i][j]表示走到i,多走的长度是j的方案数。(多走指的是比最短路多的部分的长度)。但是发现这个DP方程是存在环的,因为最短路径图上的边以及零边都是可以同行转移的。将最短路径图上的边以及零边都拿出来跑拓扑排序,然后让这些边在转移时必须沿着拓扑序转移即可。特别地,如果一个零环位于一条从1到n长度<=d+K的路径上,则输出-1即可。

本人再次郑重提醒:一定一定一定要memset!!!

#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#include<queue>
#include<deque>

using namespace std;

const int MAXN=1e5+5;

int n,m,k,p;

struct edge
{
    int to,next,w;
} e[MAXN<<1],fe[MAXN<<1],ling[MAXN<<1];

int head[MAXN],fhead[MAXN],linghead[MAXN],cnt=0,fcnt=0,lingcnt=0;
bool flag=0;

inline void add(int u,int v,int w)
{
    e[++cnt]=(edge) {v,head[u],w},head[u]=cnt;
}
inline void addf(int u,int v,int w)
{
    fe[++fcnt]=(edge) {v,fhead[u],w},fhead[u]=fcnt;
}
inline void addling(int u,int v)
{
    ling[++lingcnt]=(edge) {v,linghead[u],0},linghead[u]=lingcnt;
}

struct hnd
{
    int u,d;
    bool operator<(const hnd&rhs)const
    {
        return d>rhs.d;
    }
};

int dis[MAXN],dis2[MAXN];
priority_queue<hnd>q;

void dij(int n)
{
    dis[n]=0;
    q.push((hnd)
    {
        n,dis[n]
    });
    while(q.size())
    {
        hnd x=q.top();
        q.pop();
        int u=x.u;
        if(x.d!=dis[u])continue;
        for(int i=head[u]; i; i=e[i].next)
        {
            int v=e[i].to,w=e[i].w;
            if(dis[u]+w<dis[v])
            {
                dis[v]=dis[u]+w;
                q.push((hnd)
                {
                    v,dis[v]
                });
            }
        }
    }
}

void dij2(int n)
{
    dis2[n]=0;
    q.push((hnd)
    {
        n,dis2[n]
    });
    while(q.size())
    {
        hnd x=q.top();
        q.pop();
        int u=x.u;
        if(x.d!=dis2[u])continue;
        for(int i=fhead[u]; i; i=fe[i].next)
        {
            int v=fe[i].to,w=fe[i].w;
            if(dis2[u]+w<dis2[v])
            {
                dis2[v]=dis2[u]+w;
                q.push((hnd)
                {
                    v,dis2[v]
                });
            }
        }
    }
}

int que[MAXN],num=0,in[MAXN];

void topsort()
{
    for(int i=1; i<=n; i++)
    {
        for(int j=head[i]; j; j=e[j].next)
        {
            int v=e[j].to,w=e[j].w;
            if(!w)
            {
                addling(i,v);
                in[v]++;
            }
        }
    }
    for(int i=1; i<=n; i++)
    {
        if(!in[i])que[++num]=i;
    }
    for(int i=1; i<=num; i++)
    {
        int u=que[i];
        for(int j=linghead[u]; j; j=ling[j].next)
        {
            int v=ling[j].to;
            in[v]--;
            if(!in[v])que[++num]=v;
        }
    }
    for(int i=1; i<=n; i++)
    {
        if(in[i]&&dis[i]+dis2[i]<=dis[n]+k)
        {
            printf("-1\n");
            flag=1;
            return;
        }
    }
}

int f[60][MAXN];

int dp()
{
    memset(ling,0,sizeof(ling));
    memset(linghead,0,sizeof(linghead));
    memset(que,0,sizeof(que));
    lingcnt=0,num=0;
    for(int i=1; i<=n; i++)
        for(int j=head[i]; j; j=e[j].next)
        {
            int v=e[j].to,w=e[j].w;
            if(w+dis[i]==dis[v])
            {
                in[v]++;
                addling(i,v);
            }
        }
    for(int i=1; i<=n; i++)
    {
        if(!in[i])
        {
            que[++num]=i;
        }
    }
    for(int i=1; i<=num; i++)
    {
        int u=que[i];
        for(int j=linghead[u]; j; j=ling[j].next)
        {
            int v=ling[j].to;
            in[v]--;
            if(!in[v])que[++num]=v;
        }
    }
    f[0][1]=1;
    for(int i=0; i<=k; i++)
    {
        for(int x=1; x<=num; x++)
        {
            int u=que[x];
            for(int z=head[u]; z; z=e[z].next)
            {
                int v=e[z].to,w=e[z].w;
                if(i+dis[u]+w-dis[v]<=k) (f[i+dis[u]+w-dis[v]][v]+=f[i][u])%=p;
            }
        }
    }
    int ans=0;
    for(int i=0; i<=k; i++) (ans+=f[i][n])%=p;
    return ans;
}

void mem()
{
    memset(e,0,sizeof(e));
    memset(fe,0,sizeof(fe));
    memset(dis,0x3f,sizeof(dis));
    memset(dis2,0x3f,sizeof(dis2));
    memset(f,0,sizeof(f));
    memset(head,0,sizeof(head));
    memset(fhead,0,sizeof(fhead));
    memset(ling,0,sizeof(ling));
    memset(linghead,0,sizeof(linghead));
    memset(que,0,sizeof(que));
    memset(in,0,sizeof(in));
    lingcnt=0,cnt=0,fcnt=0,num=0;
    flag=0;
}

void work()
{
    mem();
    scanf("%d%d%d%d",&n,&m,&k,&p);
    for(int i=1; i<=m; i++)
    {
        int tem1,tem2,tem3;
        scanf("%d%d%d",&tem1,&tem2,&tem3);
        add(tem1,tem2,tem3);
        addf(tem2,tem1,tem3);
    }
    dij(1);
    dij2(n);
    topsort();
    if(!flag)printf("%d\n",dp());
}

int t;
int main()
{
    scanf("%d",&t);
    while(t--)work();
    return 0;
}

D2T1 奶酪

【问题描述】
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为z = 0,奶酪的上表面为z = h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?
空间内两点P(x ,y ,z )、P(x ,y ,z )的距离公式如下:

【输入格式】
输入文件名为 cheese.in。
每个输入文件包含多组数据。
输入文件的第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。
接下来是 T 组数据,每组数据的格式如下:
第一行包含三个正整数 n,h 和 r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的 n 行,每行包含三个整数 x、y、z,两个数之间以一个空格分开,表示空洞球心坐标为(x, y, z)。
【输出格式】
输出文件名为 cheese.out。
输出文件包含 T 行,分别对应 T 组数据的答案,如果在第 i 组数据中,Jerry 能从下表面跑到上表面,则输出“Yes”,如果不能,则输出“No”(均不包含引号)。
【数据规模与约定】
对于 20%的数据,n = 1,1 ≤ h , r ≤ 10,000,坐标的绝对值不超过 10,000。
对于 40%的数据,1 ≤ n ≤ 8, 1 ≤ h , r ≤ 10,000,坐标的绝对值不超过 10,000。
对于 80%的数据,1 ≤ n ≤ 1,000,1 ≤ h , r ≤ 10,000,坐标的绝对值不超过 10,000。
对于 100%的数据,1 ≤ n ≤ 1,000,1 ≤ h , r ≤ 1,000,000,000,T ≤ 20,坐标的绝对值不超过 1,000,000,000。

【题解】

把每个圆看成一个节点,如果两点距离小于其半径之和,那么就连一条边。最后DFS。

是的,int本身不会被卡,但是千万不能用sqrt,要直接算平方之和来判断两圆的位置关系。

#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<iostream>

using namespace std;

const int MAXN=1e4+5;

long long a,b,ccfgao,ccfbanjing;

int ccffa[MAXN],ccfrk[MAXN],t,ccfdian;

struct ccfpoint
{
    long long chang,kuan,gao;
} ppp[MAXN];

inline long long ccfcalc(long long ccfx,long long ccfy ,long long ccfz,long long ccfxx,long long ccfyy,long long ccfzz)
{
    long long ccftem1=abs(ccfx-ccfxx)*abs(ccfx-ccfxx);
    long long ccftem2=abs(ccfy-ccfyy)*abs(ccfy-ccfyy);
    long long ccftem3=abs(ccfz-ccfzz)*abs(ccfz-ccfzz);
    return ccftem1+ccftem2+ccftem3;
}

void ccfmakeuu(int n)
{
    for(int i=0; i<=n; i++)ccffa[i]=i;
    ccffa[4000]=4000;
    ccffa[5000]=5000;
}

int ccffind(int x)
{
    return (x==ccffa[x])?x:ccffa[x]=ccffind(ccffa[x]);
}

void ccfunset(int x,int y)
{
    if((x=ccffind(x))==(y=ccffind(y)))return;
    if(ccfrk[x]>ccfrk[y])ccffa[y]=x;
    else
    {
        ccffa[x]=y;
        if(ccfrk[x]==ccfrk[y])ccfrk[y]++;
    }
}

int main()
{
    scanf("%d",&t);
    for(int l=1; l<=t; l++)
    {
        memset(ppp,0,sizeof(ppp));
        memset(ccfrk,0,sizeof(ccfrk));
        memset(ccffa,0,sizeof(ccffa));
        scanf("%d%lld%lld",&ccfdian,&ccfgao,&ccfbanjing);
        ccfmakeuu(ccfdian);
        for(int i=1; i<=ccfdian; i++)
        {
            scanf("%lld%lld%lld",&ppp[i].chang,&ppp[i].kuan,&ppp[i].gao);
            if(ppp[i].gao<=ccfbanjing)
            {
                ccfunset(i,4000);
            }
            if(ccfgao-ppp[i].gao<=ccfbanjing)
            {
                ccfunset(i,5000);
            }
        }
        long long ccfbanjing2=(ccfbanjing*ccfbanjing)*4;
        for(int i=1; i<ccfdian; i++)
            for(int j=i+1; j<=ccfdian; j++)
            {
                if(i==j)continue;
                if(ccfcalc(ppp[i].chang,ppp[i].kuan,ppp[i].gao,ppp[j].chang,ppp[j].kuan,ppp[j].gao)<=ccfbanjing2)
                    ccfunset(i,j);
            }
        if(ccffind(4000)==ccffind(5000))printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

D2T2 宝藏

【问题描述】
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋,也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度 × 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
【输入格式】
输入文件名为 treasure.in。
第一行两个用空格分离的正整数 n 和 m,代表宝藏屋的个数和道路数。
接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为 1~n),和这条道路的长度 v。
【输出格式】
输出文件名为 treasure.out。
输出共一行,一个正整数,表示最小的总代价。
【数据规模与约定】
对于 20%的数据:
保证输入是一棵树,1≤n≤8,v≤5000 且所有的 v 都相等。
对于 40%的数据:
1≤n≤8,0≤m≤1000,v≤5000 且所有的 v 都相等。
对于 70%的数据:
1≤n≤8,0≤m≤1000,v≤ 5000
对于 100%的数据:
1≤n≤12,0≤m≤1000,v≤ 500000

【题解】

状压DP

一看n<=12,明显是状压DP的数据范围。于是令f[i][S]表示当前与根连通的点的状态为S,并且最深的点的深度为i的最小代价。转移时,我们枚举所有不在S中的点,处理出每个点与S中的某个点连通所需要的最小代价。然后枚举这些点构成的所有集合S',用S'中所有点的代价+f[i][S]去更新f[i+1][S|S']即可。时间复杂度:O(n3n)。

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cmath>

using namespace std;

const int MAXN=13;
const int INF=1<<29;

long long n,m,w[MAXN][1<<MAXN],f[MAXN][1<<MAXN];
long long dis[MAXN][MAXN],tem1,tem2,tem3;

int main()
{
    memset(dis,0x3f,sizeof(dis));
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&tem1,&tem2,&tem3);
        dis[tem1][tem2]=min(dis[tem1][tem2],tem3);
        dis[tem2][tem1]=min(dis[tem2][tem1],tem3);
    }
    for(int i=1; i<=n; i++)
        for(int j=1; j<(1<<n); j++)
        {
            w[i][j]=INF;
            for(int k=1; k<=n; k++)
            {
                if(1<<(k-1)&j)
                    w[i][j]=min(w[i][j],dis[k][i]);
            }
        }
    for(int i=1; i<(1<<n); i++)
    {
        if(i&(i-1))
            for(int ll=0; ll<=n; ll++)
                f[ll][i]=INF;
        for(int j=i&(i-1); j; j=i&(j-1))
        {
            long long diss=0;
            for(int k=1; k<=n; k++)
                if((1<<(k-1))&j)
                {
                    diss+=w[k][i-j];
                }
            for(int k=1; k<=n; k++)
                f[k][i]=min(f[k][i],f[k-1][i-j]+diss*k);
        }
    }
    long long ans=0x7fffffffffffffff;
    for(int i=1; i<=n; i++)
        ans=min(ans,f[i][(1<<n)-1]);
    printf("%lld",ans);
    return 0;
}

D2T3 队列

【问题描述】
Sylvia是一个热爱学习的女孩子。
前段时间,Sylvia 参加了学校的军训。众所周知,军训的时候需要站方阵。Sylvia 所在的方阵中有n × m名学生,方阵的行数为 n,列数为 m。
为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中的学生从 1 到 n × m 编上了号码(参见后面的样例)。即:初始时,第 i 行第 j 列的学生的编号是(i−1)×m+j。
然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天中,一共发生了q件这样的离队事件。每一次离队事件可以用数对(x,y) (1≤x≤n, 1≤y≤m)描述,表示第 x 行第 y 列的学生离队。
在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达这样的两条指令:
1.向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条指令之后,空位在第 x 行第 m列。
2.向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条指令之后,空位在第 n 行第 m列。
教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后,下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 n 行第 m 列一个空位,这时这个学生会自然地填补到这个位置。
因为站方阵真的很无聊,所以 Sylvia 想要计算每一次离队事件中,离队的同学的编号是多少。
注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后方阵中同学的编号可能是乱序的。
【输入格式】
输入文件名为 phalanx.in。
输入共 q+1 行。
第 1 行包含 3 个用空格分隔的正整数 n, m, q,表示方阵大小是n行 m 列,一共发生了 q 次事件。
接下来 q 行按照事件发生顺序描述了 q 件事件。每一行是两个整数 x, y,用一个空格分隔,表示这个离队事件中离队的学生当时排在第 x 行第 y 列。
【输出格式】
输出文件名为 phalanx.out。
按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学生的编号。
【数据规模与约定】

数据保证每一个事件满足 1≤x≤n,1≤y≤m。

【题解】

离线+平衡树

由于操作是可逆的,所以将所有操作反过来做,将人的移动改成询问的反向移动,那么只需要维护这些询问点的移动即可,不难想到用数据结构。将操作顺序反过来,那么一次操作(x,y)可以看成:
1.(n,m)的学生出队;
2.第n列中,第x行及以下的所有学生下移一格;
3.第x行中,第y列及右面的所有学生右移一格;
4.将当前询问的学生放到(x,y)处。
鉴于这些操作,可以选择平衡树,那么1操作就是单点删除,2,3操作就是区间加法,4操作就是单点加入。对每一行,以及最后一列都开一棵SBT,维护其中的所有询问即可。并且注意:每一行y的范围是[1,m),即如果一行的最右面的元素位于最后一列,要将其从这一行删除,并加入到最后一列中。还有一点,如果(n,m)处有询问,说明那个询问与当前询问是重合的,所以记录一个类似于pre的东西,将那个询问的pre指向当前询问,并将那个询问删除。输出答案的时候直接令该询问的答案等于其pre的答案即可。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=300010;
typedef long long ll;
struct node
{
    int ch[2],siz,val,org,tag;
} s[maxn<<1];
int n,m,q,tot;
int rx,ry[maxn];
int bel[maxn],x[maxn],y[maxn];
ll ans[maxn];
inline int rd()
{
    int ret=0;
        char gc=getchar();
    while(gc<'0'||gc>'9')
        gc=getchar();
    while(gc>='0'&&gc<='9')
        ret=ret*10+gc-'0',gc=getchar();
    return ret;
}
inline void pushdown(int x)
{
    if(s[x].tag)
    {
        if(s[x].ch[0])
            s[s[x].ch[0]].val+=s[x].tag,s[s[x].ch[0]].tag+=s[x].tag;
        if(s[x].ch[1])
            s[s[x].ch[1]].val+=s[x].tag,s[s[x].ch[1]].tag+=s[x].tag;
        s[x].tag=0;
    }
}
inline void pushup(int x)
{
    s[x].siz=s[s[x].ch[0]].siz+s[s[x].ch[1]].siz+1;
}
inline void rotate(int &x,int d)
{
    int y=s[x].ch[d];
    pushdown(x),pushdown(y);
    s[x].ch[d]=s[y].ch[d^1],s[y].ch[d^1]=x;
    pushup(x),pushup(y);
    x=y;
}
inline void maintain(int &x,int d)
{
    if(s[s[s[x].ch[d]].ch[d]].siz>s[s[x].ch[d^1]].siz)
        rotate(x,d);
    else if(s[s[s[x].ch[d]].ch[d^1]].siz>s[s[x].ch[d^1]].siz)rotate(s[x].ch[d],d^1),rotate(x,d);
    else return ;
    maintain(s[x].ch[d],d),maintain(s[x].ch[d^1],d^1);
    maintain(x,d),maintain(x,d^1);
}
void insert(int &x,int y,int z)
{
    if(!x)
    {
        x=++tot,s[x].siz=1,s[x].ch[0]=s[x].ch[1]=s[x].tag=0,s[x].val=y,s[x].org=z;
        return ;
    }
    pushdown(x);
    int d=(y>s[x].val);
    insert(s[x].ch[d],y,z),pushup(x);
    maintain(x,d);
}
void del(int &x,int y)
{
    s[x].siz--,pushdown(x);
    if(s[y].val>s[x].val)
        del(s[x].ch[1],y);
    else if(s[y].val<s[x].val)
        del(s[x].ch[0],y);
    else
    {
        if(!s[x].ch[0]||!s[x].ch[1])
        {
            x=s[x].ch[0]^s[x].ch[1];
            return ;
        }
        int u=s[x].ch[1];
            pushdown(u);
        while(s[u].ch[0])
            u=s[u].ch[0],pushdown(u);
        s[x].org=s[u].org,s[x].val=s[u].val;
        del(s[x].ch[1],u);
    }
}
void updata(int x,int y)
{
    if(!x)
        return ;
    pushdown(x);
    if(s[x].val>=y)
    {
        s[x].val++;
        if(s[x].ch[1])
            s[s[x].ch[1]].val++,s[s[x].ch[1]].tag++;
        updata(s[x].ch[0],y);
    }
    else
        updata(s[x].ch[1],y);
}
inline int findmax(int x)
{
    pushdown(x);
    while(s[x].ch[1])
        x=s[x].ch[1],pushdown(x);
    return x;
}
inline ll point(int a,int b)
{
    return ll(a-1)*m+b;
}
void dfs(int x,int t)
{
    if(!x)
        return ;
    pushdown(x);
    if(!t)
        ans[s[x].org]=point(s[x].val,m);
    else
        ans[s[x].org]=point(t,s[x].val);
    dfs(s[x].ch[0],t),dfs(s[x].ch[1],t);
}
int find(int x,int y)
{
    if(!x)
        return 0;
    pushdown(x);
    if(y>s[x].val)
        return find(s[x].ch[1],y);
    if(y<s[x].val)
        return find(s[x].ch[0],y);
    return x;
}
int main()
{
    n=rd(),m=rd(),q=rd();
    int i;
    for(i=1; i<=q; i++)
    {
        bel[i]=i;
        x[i]=rd(),y[i]=rd();
    }
    for(i=q; i>=1; i--)
    {
        int t=findmax(rx);
        if(s[t].val==n)
        {
            bel[s[t].org]=i,del(rx,t);
        }
        updata(rx,x[i]);
        updata(ry[x[i]],y[i]);
        t=findmax(ry[x[i]]);
        if(s[t].val==m)
        {
            del(ry[x[i]],t),insert(rx,x[i],s[t].org);
        }
        if(y[i]<m)
            insert(ry[x[i]],y[i],i);
        else
            insert(rx,x[i],i);
    }
    dfs(rx,0);
    for(i=1; i<=n; i++)
        dfs(ry[i],i);
    for(i=1; i<=q; i++)
        printf("%lld\n",ans[i]=ans[bel[i]]);
    return 0;
}

结束 | After

从某种意义上说,高中竞赛到此就结束了。

不过,对于这个博客我还是舍不得呀。所以我还是想,不定时地做一做题,写一写题解——毕竟大学还是可以继续搞编程的嘛。当然,我也会发一些杂七杂八的东西啦~

See you later!

 

Time: 2018-01-28

posted @ 2018-01-28 13:47  SinGuLaRiTy2001  阅读(472)  评论(0编辑  收藏  举报