寒假训练2

此次比赛虽然题做起来比较顺,但是最后两道题却没有做出。经查网上的题解后,最后两道题都是思维题,没有实现难度,可以看出我思维的欠缺。另外这套题中的E题是道实现细节题,所以可以看出我的实现能力相对以前较好了。

Pairwise Sum and Divide(51Nod - 1305)

题目描述

有这样一段程序,fun会对整数数组A进行求值,其中Floor表示向下取整:

fun(A)
    sum = 0
    for i = 1 to A.length
        for j = i+1 to A.length
            sum = sum + Floor((A[i]+A[j])/(A[i]*A[j])) 
    return sum

给出数组A,由你来计算fun(A)的结果。例如:A = {1, 4, 1},fun(A) = 5/45/4 + 2/12/1 + 5/45/4 = 1 + 2 + 1 = 4。
Input
第1行:1个数N,表示数组A的长度(1 <= N <= 100000)。
第2 - N + 1行:每行1个数Aii(1 <= Aii <= 10^9)。
Output
输出fun(A)的计算结果。

思路与小结

经过去年NOIP的第一题经历后,对于这种看上去像是数学题的东西,我选择打表找规律。但是打表并不是乱来的,通过观察我们发现程序中的Floor((A[i]+A[j])/(A[i]*A[j]))在A[i]和A[j]增大到一定程度时,两个数的乘积远远大于两个数的和,经过向下取整后变为0。也就是说后面的函数值大多都是零。
然后我打了1~10的表及中间的结果,发现确实如我想的一样。所以先排序,然后我们只需要在原来的函数中加一行小剪枝,当现在我们计算出来的值已经为0时,后面的数都是为零,可以省略了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

#define MAXN 100005

long long n;
long long a[MAXN];

int main()
{
    scanf("%lld",&n);
    for(long long i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    sort(a+1,a+n+1);
    long long ans=0;
    for(long long i=1;i<=n;i++)
        for(long long j=i+1;j<=n;j++)
        {
            long long t=(a[i]+a[j])/(a[i]*a[j]);
            //printf("%lld\n",t);
            if(!t)
                break;
            ans+=t;
        }
    printf("%lld",ans);
}

线段的重叠(51Nod - 1091)

题意

X轴上有N条线段,每条线段包括1个起点和终点。线段的重叠是这样来算的,10201020和12251225的重叠部分为12201220。
给出N条线段的起点和终点,从中选出2条线段,这两条线段的重叠部分是最长的。输出这个最长的距离。如果没有重叠,输出0。
Input
第1行:线段的数量N(2 <= N <= 50000)。
第2 - N + 1行:每行2个数,线段的起点和终点。(0 <= s , e <= 10^9)
Output
输出最长重复区间的长度。

思路与小结

这道题我初看只有一点想法是贪心,并不是很确定,所以选择了先跳过。将C题过来再来。将线段按照起点升序,终点降序的方式排序。然后从头到尾扫一遍,我们维护一个最远终点的信息,对于每个线段,我们只需要算它的起点到,它的终点和记录的最远终点中最小值就可以了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

#define MAXN 50050

int n,ans;
pair <int,int> a[MAXN];

bool cmp(pair<int,int> x,pair<int,int> y)
{
    return x.first<y.first||(x.first==y.first&&x.second>y.second);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&a[i].first,&a[i].second);
    sort(a+1,a+n+1,cmp);
    int last=a[1].second;
    for(int i=2;i<=n;i++)
    {
        ans=max(ans,min(last,a[i].second)-a[i].first);
        last=max(last,a[i].second);
    }
    printf("%d",ans);
}

搬货物 (51Nod - 1596)

题意

现在有n个货物,第i个货物的重量是 2^wi 。每次搬的时候要求货物重量的总和是一个2的幂。问最少要搬几次能把所有的货物搬完。
Input
单组测试数据。
第一行有一个整数n (1≤n≤10^6),表示有几个货物。
第二行有n个整数 w1,w2,…,wn,(0≤wi≤10^6)。
Output
输出最少的运货次数。

思路与小结

这道题读完题就秒了,因为要求每次都是2的幂,所以每两个相同的货物可以组成一起搬,然后把它们看作整体,组成了一个更大的物体。这个物体可以也可以和它大小相同的物体继续这样。
所以我们只需要对后一种物体加上当前物体个数除以2,答案加上当前物体个数模2就可以了。
但莫名这道题只有十的六次方大,却会T,我也很奇怪,希望有人知道能告知一下。我加了读入优化才过的。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

#define MAXN 1000500

int n,ans;
int a[MAXN];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9')   {ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return x;
}

int main()
{
    int maxn=0;
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x;
        x=read();
        a[x]++;
        maxn=max(maxn,x);
    }
    maxn+=100;
    for(int i=0;i<=maxn&&i<=MAXN-100;i++)
    {
        a[i+1]+=(a[i]>>1);
        a[i]%=2;
        ans+=a[i];
    }
    printf("%d",ans);
}

宝岛地图 (51Nod - 1572)

题意

勇敢的水手们到达了一个小岛,在这个小岛上,曾经有海盗在这里埋下了一些宝藏。然而,我们的船快抛锚了,与此同时,船长发现藏宝图的一角被老鼠咬掉了一块。
藏宝图可以用一个n×m大小的矩形表示。矩形中的每一小块表示小岛中的一小块陆地(方块的边长为1米)。有一些方块表示的是海,这些块人是不能通过的。除了海不能走,其它的小方块都是可以行走的。在可行走区域里有一些小方块表示一些已知的地点。
另外,在地图上有k条指令。每条指令的格式表示如下:
“向y方向走n米”。
这里的方向有四种:“北”,“南”,“东”,“西”。如果你正确的跟着这些指令行走,并且完整的执行完所有指令,你就可以找到宝藏所在的地点。
但是,很不幸,由于地图中好多地方都缺失了,船长也不知道从哪些地方开始走。但是船长依然清楚地记得一些已知的地点。另外,船长也知道所有可行走区域。
现在船长想知道从哪些已知地点出发,按照指令,可能找到宝藏所在地。
Input
单组测试数据
第一行包含两整数n和m(3≤n,m≤1000)。
接下来的n行每行有m个字符,表示整个地图。
“#”代表海。在地图矩形中,矩形的四周一圈一定是海。
“.”代表可行走区域,未知地点。大写字母“A”到“Z”表示可行走区域,已知地点。
所有大写字母不一定都被用到。每个字母在地图中最多出现一次。所有已知地点用不同的大写字母表示。
接下来一行有一个整数k(1≤k≤10^5),接下来有k行。
每行表示一条指令。
指令格式为“dir len”,“dir”表示朝哪个方向走,“len”表示走几步。
“dir”有四种取值“N”,“S”,“E”,“W”,对应题目中的“北”,“南”,“东”,“西”
在地图中,北是在顶部,南是在底部,西是在左边,东是在右边。“len”是一个整数,范围在1,10001,1000。
Output
共一行,按字典序升序打印出所有可以完整执行地图中指令的已知区域的字母,如果没有满足要求的已知区域,则打印“no solution”(没有引号)。

思路与小结

这道题一开始我的想法是对于每一个字母当作起点按照指令跑一遍,似乎看上去时间复杂度并不高,但是我忽略了一个细节。我只判断了移动完了的位置,并没有判断移动过程中的位置是否合法。这个问题直到我快写完的时候才意识到,如果不是代码之后的思路差别不大的话,可能会极大的浪费我比赛的时间。所以动手前应该考虑好每一个细节。
之后我改成先预处理出当前位置向各个方向能够行动的最大距离是多少,用O(n*m)的时间处理出来。然后在运行指令的时候,将当前要走的值与这个位置这个方向能够移动的距离相比较,如果大于了则是非法的,可以直接返回false了。只要依次判断就好了,时间复杂度每次判断是O(k)的,所以总的时间复杂度是O(n*m)的。而如果直接处理出每一个经过的点,判断是否合法的方法最坏的时间复杂度可以被卡到O(n^2*m^2)。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;

#define MAXN 1050

int n,m,ans,ncnt,sum;
int name[30],out[30],d[MAXN][MAXN][4];
char a[MAXN][MAXN];
vector <pair<int,int> > st;
vector <pair<int,int> > com;
map <int,int> idx;
int f[4][2]={{-1,0},{0,-1},{1,0},{0,1}};

bool check(int x,int y)
{
    for(int i=0;i<(int)com.size();i++)
    {
        //printf("####%d %d\n",x,y);
        int how=com[i].first,len=com[i].second;
        int nx=x+f[how][0]*len;
        int ny=y+f[how][1]*len;
        if(len>d[x][y][how])
            return 0;
        x=nx,y=ny;
    }
    return 1;
}

void Init()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(a[i][j]=='#')
                d[i][j][0]=d[i][j][1]=-1;
            else
            {
                d[i][j][0]=d[i-1][j][0]+1;
                d[i][j][1]=d[i][j-1][1]+1;
            }
        }
    for(int i=n;i>=1;i--)
        for(int j=m;j>=1;j--)
        {
            if(a[i][j]=='#')
                d[i][j][2]=d[i][j][3]=-1;
            else
            {
                d[i][j][2]=d[i+1][j][2]+1;
                d[i][j][3]=d[i][j+1][3]+1;
            }
        }
    /*for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            printf("%d,%d:%d %d %d %d\n",i,j,d[i][j][0],d[i][j][1],d[i][j][2],d[i][j][3]);*/
}

int main()
{
    idx['N']=0,idx['S']=2,idx['E']=3,idx['W']=1;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",a[i]+1);
        for(int j=1;j<=m;j++)
            if(a[i][j]>='A'&&a[i][j]<='Z')
                st.push_back(make_pair(i,j)),name[++ncnt]=a[i][j]-'A';
    }
    Init();
    char s[5];
    int t,len;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s%d",s,&len);
        com.push_back(make_pair(idx[s[0]],len));
    }
    for(int i=0;i<(int)st.size();i++)
    {
        if(check(st[i].first,st[i].second))
            ans++,out[++sum]=name[i+1];
        //printf("\n\n");
    }
    if(!ans)
        printf("no solution");
    else
    {
        sort(out+1,out+sum+1);
        for(int i=1;i<=sum;i++)
            printf("%c",out[i]+'A');
    }
}

小鲨鱼在51nod小学 (51Nod - 1631)

题意

鲨鱼巨巨2.0(以下简称小鲨鱼)以优异的成绩考入了51nod小学。并依靠算法方面的特长,在班里担任了许多职务。
每一个职务都有一个起始时间A和结束时间B,意为小鲨鱼在A,BA,B时间内,担任了某职务(inclusively)。
现在给定小鲨鱼的职务履历表,你可以高效的给出小鲨鱼在某天担任了哪些职务吗?
p.s. 由于小鲨鱼担任的职务太多,所有任期小于一个自然月的职务都忽略不计。(如1月1日~2月1日为一个自然月,即月份加1)
p.p.s. 输入数据保证小鲨鱼同时不担任超过200种职务。(牛!)
p.p.p.s 输入的日期均为合法日期,范围在2000年01月01日~2999年12月31日。
p.p.p.p.s 巨大的输入输出,推荐使用scanf/printf,编译器推荐使用Virtual C++
Input
第一行为一个整数n,代表小鲨鱼担任过N种职务。(1 <= n <= 10^5)
接下来的n行,每一行为七个整数,y0, m0, d0, y1, m1, d1, x。意为在 到 时间内,小鲨鱼担任了职务x。(1 <= x <= 10^9)
给定的时间皆合法,且起始日期小于或等于截止日期。职务x是唯一的。
接下来是一个整数q,代表q次查询。(1 <= q <= 10^4)
接下来的q行,每一行为三个整数 ,代表查询的日期。时间皆合法。
Output
每一次查询输出一行结果。
首先输出一个整数n,代表此时小鲨鱼担任的职务数。(n可以为0)
接下来是n个整数,代表小鲨鱼担任的职务。职务列表保持升序。

思路与小结

一开始,看到这种问题我想的是线段树,但我觉得这道题写线段树的代码会比较复杂,容易干扰我后面的时间。所以尝试想一下其他方法,并没有什么收获。所以我选择暂且跳过,先做了原题F。当我回来时,大概只剩下了一个半小时。这个时候如果写线段树的话加上调试的时间可能只有这一道题了。我再读了一遍题,发现暴力的时间复杂度是O(n*q)的,如果题目中非法的职务比较多的话说不定可以卡过去。
于是尝试写暴力,事实证明我写的暴力似乎成功的卡了过去时间700+ms。应该比较好吧。这应该算是运气了,毕竟ACM制的比赛没有骗分,万一没有过就是没有过。
后来分析了一下,觉得这真的可能只是一道实现题,在于各种约束条件的判断上。所以大概在判断的时候,各种非法的情况省去的比较多了,导致最后的时间复杂度降的比较多。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<stack>
using namespace std;

#define MAXN 100005

int n,m,ncnt;
int out[MAXN];
struct node
{
    int y0,m0,d0;
    int y1,m1,d1;
    int name;
    bool operator < (const node &a) const
    {
        return name<a.name;
    }
}a[MAXN];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int y0,m0,d0,y1,m1,d1,name;
        scanf("%d%d%d%d%d%d%d",&y0,&m0,&d0,&y1,&m1,&d1,&name);
        if(y1-y0>=2||(y1==y0&&m1-m0>=2)||(y1==y0&&m1-m0==1&&d1>=d0)||(y1-y0==1&&m1>=2)||
          (y1-y0==1&&m0==12&&d1>=d0)||(y1-y0==1&&m0<=11))
        {
            a[++ncnt].y0=y0; a[ncnt].m0=m0; a[ncnt].d0=d0;
            a[ncnt].y1=y1; a[ncnt].m1=m1; a[ncnt].d1=d1;
            a[ncnt].name=name;
        }
    }
    sort(a+1,a+ncnt+1);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int ans=0;
        int y,m,d;
        scanf("%d%d%d",&y,&m,&d);
        for(int j=1;j<=ncnt;j++)
        {
            if(y<a[j].y0||y>a[j].y1)
                continue;
            if(y==a[j].y0)
            {
                if(m<a[j].m0)
                    continue;
                else if(m==a[j].m0&&d<a[j].d0)
                    continue;
            }
            if(y==a[j].y1)
            {
                if(m>a[j].m1)
                    continue;
                else if(m==a[j].m1&&d>a[j].d1)
                    continue;
            }
            out[++ans]=a[j].name;
        }
        printf("%d",ans);
        for(int j=1;j<=ans;j++)
            printf(" %d",out[j]);
        printf("\n");
    }
}

合法括号子段(51Nod - 1791)

题意

有一个括号序列,现在要计算一下它有多少非空子段是合法括号序列。
合法括号序列的定义是:
1.空序列是合法括号序列。
2.如果S是合法括号序列,那么(S)是合法括号序列。
3.如果A和B都是合法括号序列,那么AB是合法括号序列。
Input
多组测试数据。
第一行有一个整数T(1<=T<=1100000),表示测试数据的数量。
接下来T行,每一行都有一个括号序列,是一个由’(‘和’)’组成的非空串。
所有输入的括号序列的总长度不超过1100000。
Output
输出T行,每一行对应一个测试数据的答案。

思路与小结

这道题应该是原来做过的原题,比较简单的DP。首先我们需要预处理出每一个左括号与之匹配的右括号的位置pos,这是我们Dp的关键。然后定义状态d[i]表示以第i个字符为起点的子序列中有多少个合法的情况
d[i]=d[pos[i]+1]+1 d [ i ] = d [ p o s [ i ] + 1 ] + 1
从后往前推就好了,最后的答案是所有d的和。
注意数据范围,需要longlong

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<stack>
using namespace std;

#define MAXLEN 1200000

int n;
int nex[MAXLEN];
long long ans;
long long d[MAXLEN];

int main()
{
    stack <int> last;
    int T;
    char s[MAXLEN];
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s+1);
        n=strlen(s+1);

        ans=0LL;
        for(int i=1;i<=n;i++)
            d[i]=0,nex[i]=-1;
        while(!last.empty())
            last.pop();

        for(int i=1;i<=n;i++)
        {
            if(s[i]=='(')
                last.push(i);
            else
            {
                if(last.empty())
                    continue;
                int x=last.top(); last.pop();
                nex[x]=i;
            }
        }
        for(int i=n;i>0;i--)
        {
            if(nex[i]==-1)
                continue;
            d[i]=d[nex[i]+1]+1LL;
            ans+=d[i];
        }
        printf("%lld\n",ans);
    }
}

配对(51Nod - 1737)

题意

给出一棵n个点的树,将这n个点两两配对,求所有可行的方案中配对两点间的距离的总和最大为多少。
Input
一个数n(1<=n<=100,000,n保证为偶数)
接下来n-1行每行三个数x,y,z表示有一条长度为z的边连接x和y(0<=z<=1,000,000,000)
Output
一个数表示答案

思路与小结

这道题我起初的思路是靠近了树形DP的,但是我没有想到重心这个东西。忽略了这个性质之后,导致我怎么也没有想到方法将当前子树和它的父亲配对这种情况想到。
正确的解法应该是找出树的重心,即保证了最优解的配对中路径是经过重心的。所以这样配对所经过的路径是最长的。
思维题啊!!!

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

#define MAXN 100005

struct node
{
    int v,w;
};
int n;
long long ans;
vector <node> E[MAXN];

int dfs(int u,int fa)
{
    int res=1;
    for (int i=0;i<E[u].size();i++)
    {
        int v=E[u][i].v,w=E[u][i].w;
        if (v==fa)
            continue;
        int sum=dfs(v,u);
        ans+=(long long)min(sum,n - sum) * w;
        res+=sum;
    }
    return res;
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        E[u].push_back((node){v,w});
        E[v].push_back((node){u,w});
    }

    ans=0;
    dfs(1,-1);
    printf("%lld\n",ans);
}

环(51Nod - 1833)

题意

有一个有向图。这张图有n个点和m条有向边。
他很好奇不相交的环(简单环)来覆盖所有点的方案数(数字可能很大请模998,244,353)。 经计算,这又是一个大质数。
Input
第一行有n和m。(1<=n<=20,1<=m<=n*(n-1))
后面m行描述着m条边。
输入保证没有重边自环。
Output
输出方案数。

思路与小结

这道题读完后可以说产生了畏惧心理吗?之前并没有见类似的题目,有点慌,加上是最后一道题,所以并没有花太多的时间在上面,而是把大多数时间放在了倒数第二题上。
网上看的正解地址:传送门
巧妙地转为了一个二分图完美匹配的问题,然后利用Dp求解。尤其是Dp的部分,运用的方法感觉特别巧妙。觉得像我这种蒟蒻永远也写不出来...
但是我似乎也只看懂了这点,其他的我并没有就看懂。网上似乎就一个题解…等待大佬的讲解,所以此题又要成为一个未填的坑了。(待续)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值