高2020级寒假训练2【总结】

那个E题细节好恶心,时间都花在上面了

A - 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((Aii+Ajj)/(Aii*Ajj))
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)的计算结果。
Sample Input
3
1 4 1
Sample Output
4

思路

这道题一看数据范围就知道要在O(n)左右解决,那么应该是算每个Ai的贡献,(a+b)/(a*b)=1/a+1/b,所以如果不向下取整的话直接就算了,但是有向下取整其实不难发现更简单,首先,如果是1,那么应该是(1+a)/a<=2,所以1的贡献应该为n-1(a==1时1+1=2),1算完了,把所有的1删除后,2就只能和2有贡献了,所以2的总贡献应为n *(n-1)/2,之后就再没有贡献了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
int n,a,ans,a1,a2;
int main()
{
    ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        if(a==1)
        {
            ans+=n-1;
        }
        if(a==2)
            a2++;
    }
    ans+=(a2-1)*a2/2;
    printf("%d\n",ans);
}

B - 线段的重叠 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
输出最长重复区间的长度。
Sample Input
5
1 5
2 4
2 8
3 7
7 9
Sample Output
4

思路

这个估计也是O(n)左右吧,于是开始想贪心,先按左边排序,从开始的算如果下一个的末尾在当前的之前,那么直接统计答案,如果在后面,就跟新末尾。这样后面的前端点一定是覆盖了的,我们要让后面能覆盖的尽可能的长,这样就看后面的,如果后面那个的后面段点在更后面就更新答案和末端点,如果再前面就只更新答案。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=50005;
int n;
struct xd
{
    int s,e;
}a[MAXN];
bool cmp(xd a,xd b)
{
    if(a.s==b.s)
        return a.e>b.e;
    return a.s<b.s;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&a[i].s,&a[i].e);
    }
    sort(a+1,a+n+1,cmp);
    int e=1;
    int ans=0;
    for(int i=2;i<=n;i++)
    {
        if(a[i].e<=a[e].e)
        {
            ans=max(ans,a[i].e-a[i].s);
        }
        else
        {
            ans=max(ans,a[e].e-a[i].s);
            e=i;
        }
    }
    printf("%d\n",ans);
}

C - 搬货物 51Nod - 1596

现在有n个货物,第i个货物的重量是 2wi2wi 。每次搬的时候要求货物重量的总和是一个2的幂。问最少要搬几次能把所有的货物搬完。

样例解释:

1,1,2作为一组。

3,3作为一组。

Input
单组测试数据。
第一行有一个整数n (1≤n≤10^6),表示有几个货物。
第二行有n个整数 w1,w2,…,wn,(0≤wi≤10^6)。
Output
输出最少的运货次数。
Sample Input
5
1 1 2 3 3
Sample Output
2

思路

这个,就是传说中的1+1=2,2+2=3,3+3=4!,两个相同的我们可以把他当成一个,最后如果还剩一个就加1,一直推,不过这道题不写读入优化会卡常,我没写,用了>>1代替/2,&1代替%2,再刷了几次才过的。

代码

#include<cstdio>
int a[1000100],n;
int main()
{
    scanf("%d",&n);
    int ans=0,b=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b);
        a[b]++;
    }
    for(int i=0;i<=1000050;i++)
    {
        a[i+1]+=a[i]>>1;
        ans+=a[i]&1;
    }
    printf("%d\n",ans);
}

D - 宝岛地图 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”(没有引号)。
Sample Input
6 10
##########
#K#..#####
#.#..##.##
#..L.#…#
###D###A.#
##########
4
N 2
S 1
E 1
W 2
Sample Output
AD

思路

暴力,没什么好说的,暴力模拟走路,于是T了,后来想了想,其实可以判断哪些地方走了,这样一个格子就只走一次,如果有那种走来走去的数据就很浪费时间,但是代码已经写了,所以索性加了个小剪支先碰运气,就是一个地方如果成功就记录一下,有一点记忆化搜索的感觉,结果奇迹过了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=1005;
int map[MAXN][MAXN];
int hhh[MAXN][MAXN][4];
int n,m,k,rw[30][2],fx[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
struct wf
{
    int o,jl;
}a[100005];
bool check2(int x,int y,int d)
{
    if(hhh[x][y][a[d].o]>=a[d].jl)
        return 1;
    for(int i=1;i<=a[d].jl;i++)
    {
        x+=fx[a[d].o][0];
        y+=fx[a[d].o][1];
        if(map[x][y]==1)
            return 0;
    }
    return 1;
}
bool check(int d)
{
    int x=rw[d][0];
    int y=rw[d][1];
    for(int i=1;i<=k;i++)
    {
        if(check2(x,y,i))
        {
            hhh[x][y][a[i].o]=a[i].jl;
            x+=fx[a[i].o][0]*a[i].jl;
            y+=fx[a[i].o][1]*a[i].jl;
        }
        else
            return 0;
    }
    return 1;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("\n");
        for(int j=1;j<=m;j++)
        {
            char c;
            scanf("%c",&c);
            if(c=='#')
                map[i][j]=1;
            else
                map[i][j]=0;
            if('A'<=c&&c<='Z')
            {
                rw[c-'A'][0]=i;
                rw[c-'A'][1]=j;
            }
        }
    }
    scanf("%d",&k);
    for(int i=1;i<=k;i++)
    {
        char c;
        scanf("\n%c %d",&c,&a[i].jl);
        if(c=='N')
            a[i].o=0;
        if(c=='S')
            a[i].o=1;
        if(c=='W')
            a[i].o=2;
        if(c=='E')
            a[i].o=3;
    }
    int key=0;
    for(int i=0;i<26;i++)
    {
        if(rw[i][0]==0)
            continue;
        if(check(i))
        {
            printf("%c",i+'A');
            key=1;
        }
    }
    if(key==0)
        printf("no solution");
    printf("\n");
}

E - 小鲨鱼在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个整数,代表小鲨鱼担任的职务。职务列表保持升序。
Sample Input
4
2000 01 01 2000 01 01 111
2000 01 02 2001 02 02 222
2000 01 28 2000 02 29 333
2000 01 29 2000 02 28 444
4
2000 01 01
2000 01 02
2000 01 28
2000 02 29
Sample Output
0
1 222
2 222 333
2 222 333

思路

这道题纠结了半天是T还是MLE,结果,一写暴力,wa了!这就是实现题啊,蒟蒻调了半天也没调出来。然后,就没有然后了。判断看代码,真的是恶心

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=100005;
struct date
{
    int s,e,b;
    date(){}
    date(int ss,int ee,int bb)
    {
        s=ss,e=ee,b=bb;
    }
}a[MAXN];
bool cmp(date a,date b)
{
    return a.s<b.s;
}
int n,q;
int main()
{
    vector<date>a;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int y0,m0,d0,y1,m1,d1,x;
        scanf("%d %d %d %d %d %d %d",&y0,&m0,&d0,&y1,&m1,&d1,&x);
         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.push_back(date(y0*10000+m0*100+d0,y1*10000+m1*100+d1,x));
        }
    }
    sort(a.begin(),a.end(),cmp);
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        int y,m,d;
        scanf("%d %d %d",&y,&m,&d);
        int u=y*10000+m*100+d;
        vector<int>ta;
        for(int j=0;j<int(a.size());j++)
        {
            if (u<a[j].s)  
                break;  
            if (u>=a[j].s&&u<=a[j].e)  
            {  
                ta.push_back(a[j].b);  
            }
        }
        sort(ta.begin(),ta.end());
        printf("%d",ta.size());
        for(int j=0;j<int(ta.size());j++)
            printf(" %d",ta[j]);
        printf("\n");
    }
}

F - 合法括号子段 51Nod - 1791

有一个括号序列,现在要计算一下它有多少非空子段是合法括号序列。

合法括号序列的定义是:

1.空序列是合法括号序列。

2.如果S是合法括号序列,那么(S)是合法括号序列。
3.如果A和B都是合法括号序列,那么AB是合法括号序列。

Input
多组测试数据。
第一行有一个整数T(1<=T<=1100000),表示测试数据的数量。
接下来T行,每一行都有一个括号序列,是一个由’(‘和’)’组成的非空串。
所有输入的括号序列的总长度不超过1100000。
Output
输出T行,每一行对应一个测试数据的答案。
Sample Input
5
(
()
()()
(()
(())
Sample Output
0
1
3
1
2

思路

这个就是递推,因为要看前面的括号匹配的前面所以用d[i]表示i位置结束(i位置一定是右括号)这个括号与前面的AAA…B式结合的方案。再用个栈存一下,就是记录左括号,O(n)刷一遍就完了(注意longlong!)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=1100005;
long long tot=0,st[MAXN],ans=0,d[MAXN];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ans=0;
        tot=0;
        char s[MAXN];
        scanf("%s",s);
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            d[i]=0;
            if(s[i]=='(')
                st[++tot]=i;
            else
            {
                if(!tot)
                    continue;
                int k=st[tot--];
                if(k)
                    d[i]=d[k-1]+1;
                else
                    d[i]=1;
                ans+=d[i];
            }
        }
        printf("%I64d\n",ans);
    }
}

G - 配对 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
一个数表示答案
Sample Input
6
1 2 1
1 3 1
1 4 1
3 5 1
4 6 1
Sample Output
7
//配对方案为(1,2)(3,4)(5,6)

思路

考试时做E去了,没看。
考后才看了题解,觉得这个思路有毒,贪心,让每个边的系数最大就行了,感觉就是菊花树端点连来连去的。(ans要用longlong)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const long long MAXN=100005;
long long ans;
int n;
struct edge
{
    int v,c,nxt;
    edge(){}
    edge(int vv,int cc,int nn)
    {
        v=vv,c=cc,nxt=nn;
    }
}E[MAXN*2];
int w[MAXN],ncnt,son[MAXN];
void addedge(int u,int v,int c)
{
    E[++ncnt]=edge(v,c,w[u]);
    w[u]=ncnt;
    E[++ncnt]=edge(u,c,w[v]);
    w[v]=ncnt;
}
void dfs(int u,int fa)
{
    son[u]=1;
    for(int i=w[u];i;i=E[i].nxt)
    {
        int v=E[i].v,c=E[i].c;
        if(v!=fa)
        {
            dfs(v,u);
            son[u]+=son[v];
            ans+=1LL*min(son[v],n-son[v])*c;
        }
    }
}
int main()
{
    ans=0;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v,c;
        scanf("%d %d %d",&u,&v,&c);
        addedge(u,v,c);
    }
    dfs(1,-1);
    printf("%I64d\n",ans);
}

H - 环 51Nod - 1833

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

思路

和上道题一样,考时没有做。看了题解,太神了。
这个不相交环其实就是把原图造成二分图的一个完全匹配。
比如如果有一个i->j的边我们就在i->j’的二分图中建一个边。
那么二分图的完全匹配就是不相交环覆盖的方案数。
那么可以用DP,dp[i][j]表示第i位已经匹配了i个数,这i个数可以用二进制j表示,n个数每个数都匹配一个不是自身的数,就是全集(1 << n)-1(11111…..11(n个1)),开始dp[0][0] =1。
我还学到了一个函数__builtin_popcount(j)求j的2进制有多少个1.

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MOD=998244353;
int n,m;
vector<int>w[25];
int d[25][(1<<20)+5];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        w[u].push_back(v);
    }
    d[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=(1<<n)-1;j++)
        {
            if(__builtin_popcount(j)==i)
            {
                for(int k=0;k<int(w[i].size());k++)
                {
                    int v=w[i][k];
                    if((j>>(v-1))&1)
                        d[i][j]=(d[i][j]+d[i-1][j^(1<<(v-1))])%MOD;
                }
            }
        }
    }
    printf("%d\n",d[n][(1<<n)-1]);
}

猜你喜欢

转载自blog.csdn.net/qq_35713030/article/details/79326086
今日推荐