51Nod比赛2总结

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

分析

将Floor那个条件化简,得到Floor(1/a[i]+1/a[j]),题目中给出的A数组元素范围在1倒10^9,容易发现,当两个a中一个a为1,另一个a不为一时,上式答案为1,两个a都为1时,上式答案为2,两个a都为2时,上式答案为1,其它情况上式均为0,所以统计一下1,2的个数,ans=sum1*(sum1-1)+sum1*(n-sum1)+sum2*(sum2-1)/2。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
int n,a,sum1,sum2;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        if(a==1) sum1++;
        if(a==2) sum2++;
    }
    printf("%d",sum1*(sum1-1)+sum1*(n-sum1)+sum2*(sum2-1)/2);
}

B - 线段的重叠 51Nod - 1091

题意

X轴上有N条线段,每条线段包括1个起点和终点。线段的重叠是这样来算的,(10,20)和(12,25)的重叠部分为(12,20)。(PS:就是闭区间)
给出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

分析

按左端点从小到大排序,从小到大扫,记录之前处理过的线段(左端点比它小的)的右端点的最大值,和当前线段的左端点作比较,ans与其取max,然后用这个线段的右端点继续维护右端点最大值,为下一次比较做准备。
明明很清晰的思路,考场上居然为应该左端点还是右端点排序纠结了很久!还是不熟这样的贪心啊。

代码

#include<cstdio>
#include<algorithm>
#define MAXN 50006
using namespace std;
int n,maxone,ans;
struct node
{
    int l,r;
}a[MAXN];
bool cmp(node a,node b)
{
    if(a.l==b.l) return a.r<b.r;
    return a.l<b.l;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&a[i].l,&a[i].r);
    sort(a+1,a+1+n,cmp);
    maxone=a[1].r;
    for(int i=2;i<=n;i++)
    {
        ans=max(ans,min(maxone-a[i].l,a[i].r-a[i].l));
        maxone=max(maxone,a[i].r);
    }
    printf("%d",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
样例输入1
5
1 1 2 3 3
Sample Output
样例输出1
2

分析

按贪心的思想,如果两个货物可以合并,就直接合并(如果不合并,等待其它机会,对答案并没有任何帮助),实现方法比较多,我选择直接记录一个sum[i]表示w为i的有多少个,从小到大扫一遍即可,但是注意要加一个小优化,记录一下w最大值,只枚举到那个最大值大一小点的地方,不然会玄学超时。

代码

#include<cstdio>
#include<algorithm>
#define MAXN 1000106
using namespace std;
int n,a,sum[MAXN],ans,maxone;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        sum[a]++;
        maxone=max(maxone,a);
    }
    for(int i=0;i<maxone+100;i++)
    {
        if(sum[i])
        {
            sum[i+1]+=sum[i]>>1;
            if(sum[i]%2==1) ans++;
        }
    }
    printf("%d",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
输入样例1
6 10
##########
#K#..#####
#.#..##.##
#..L.#…#
###D###A.#
##########
4
N 2
S 1
E 1
W 2
Sample Output
输出样例1
AD

分析

预处理一下每个点能最多向四个方向走多远,然后每个可能的字母O(K)走一边就可以了。
具体到如何预处理,比如处理向左走,就对每一行从左往右走一边,如果左边是陆地,它的向左走最大值就是左边那个点的值+1,如果是海,就不管它(默认是0),其它方向的同理。
这道题一开始想着将所有指令挨个走一遍,预处理出如果从(0,0)出发,会访问到那些点,然后再将每个字母坐标代入,在图上检查,发现T了……

代码

#include<cstdio>
#include<algorithm>
#define MAXN 1006
#define MAXK 100006
using namespace std;
bool map[MAXN][MAXN];
int n,m,way[4][2]={{-1,0},{1,0},{0,-1},{0,1}},x[30],y[30],k,op[MAXK],a[MAXK],maxd[MAXN][MAXN][4];
char c;
bool check(int p)
{
    int nowx=x[p];
    int nowy=y[p];
    for(int i=1;i<=k;i++)
    {
        if(a[i]>maxd[nowx][nowy][op[i]]) return 0;
        nowx+=a[i]*way[op[i]][0];
        nowy+=a[i]*way[op[i]][1];
    }
    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++)
        {
            scanf("%c",&c);
            if(c=='#') continue;
            map[i][j]=1;
            if(c!='.')
            {
                x[c-'A']=i;
                y[c-'A']=j;
            }
        }
    }
    for(int i=2;i<n;i++)
    {
        for(int j=2;j<m;j++)
            if(map[i][j-1]) maxd[i][j][2]=maxd[i][j-1][2]+1;
        for(int j=m-1;j>=2;j--)
            if(map[i][j+1]) maxd[i][j][3]=maxd[i][j+1][3]+1;
    }
    for(int j=2;j<m;j++)
    {
        for(int i=2;i<n;i++)
            if(map[i-1][j]) maxd[i][j][0]=maxd[i-1][j][0]+1;
        for(int i=n-1;i>=2;i--)
            if(map[i+1][j]) maxd[i][j][1]=maxd[i+1][j][1]+1;
    }
    scanf("%d",&k);
    for(int i=1;i<=k;i++)
    {
        scanf("\n%c %d",&c,&a[i]);
        if(c=='N') op[i]=0;
        if(c=='S') op[i]=1;
        if(c=='W') op[i]=2;
        if(c=='E') op[i]=3;
    }
    int ok=0;
    for(int i=0;i<26;i++)
    {
        if(x[i]==0&&y[i]==0) continue;
        if(check(i))
        {
            ok=1;
            printf("%c",i+'A');
        }
    }
    if(!ok) printf("no solution");
}

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

分析

恶心的实现题!
此题最可怕的点在于日期处理,我们先写一个恶心的日期换算函数,将年月日换算成天数,再处理会方便很多。
题意中每次询问涉及到的职务不超过200个,只有10000个询问,于是我们离线处理,按职务开始时间排序,询问按照时间从前到后排序,然后滑窗即可。
注意处理小于一个月的情况。

代码

留坑待填(反正都是实现题自己实现你说是吧?…)

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

分析

似乎很多人都是用DP做的。
然而我考场上没想出DP,然后自己瞎搞出一个方法过的…
我是这样想的:设一个状态整数now,如果遇到‘(’,now++,遇到‘)’,now–,然后用一个数组sum[i]表示状态为i的有多少。
具体操作时,遇到‘(’,就改状态,并且sum[now]++,如果遇到‘)’,就要先将上一个状态(即now–前)的sum清0,因为此时后面已经无法与上一个状态下的括号串产生联系。具体来说就是会呈现类似于()(()))(这样的情况,到了倒数第2个’)’时,上一个状态已经和以后的隔开了。然后更改状态,让答案加上sum[now],sum[now]还++,这样的话下次遇到同一状态答案就会包括这一个。
要记得优化清0。
(PS:交上去时都没抱希望,居然过了,具体你们还是看看代码吧…)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1100006
using namespace std;
int sum[MAXN*2],T,ma,mi,n;
long long ans;
char s[MAXN];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s);
        n=strlen(s);
        int now=0;
        sum[MAXN]=1;
        ma=mi=0;
        ans=0;
        for(int i=0;i<n;i++)
        {
            if(s[i]=='(') 
            {
                now++;
                sum[now+MAXN]++;
            }
            if(s[i]==')')
            {
                sum[now+MAXN]=0;
                now--;
                ans+=1ll*sum[now+MAXN];
                sum[now+MAXN]++;
            }
            ma=max(ma,now);
            mi=min(mi,now);
        }
        for(int i=mi;i<=ma;i++)
            sum[i+MAXN]=0;
        printf("%lld\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)

分析

贪心的想,对于一个根,应该尽量把左边的点和右边的配对,把右边的和左边配对,所以根连下去的边,都会被最多用min(子树大小,n-子树大小)次。我们找树的重心,如果找到,就会发现它每一个子树大小都小于等于n/2,所以每条边都可以被用上述次,就不用找重心了,直接DFS就可以了。

代码

#include<cstdio>
#include<vector>
#include<algorithm>
#define MAXN 100006
using namespace std;
int n,a,b;
long long ans,c;
struct node
{
    int to;
    long long len;
};
vector<node> road[MAXN];
node makenode(int to,long long len)
{
    node add;
    add.to=to;
    add.len=len;
    return add;
}
int DFS(int x,int fa)
{
    int re=1;
    for(int i=0;i<(int)road[x].size();i++)
    {
        int next=road[x][i].to;
        if(next==fa) continue;
        int son=DFS(next,x);
        ans+=1ll*min(son,n-son)*road[x][i].len;
        re+=son;
    }
    return re;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%lld",&a,&b,&c);
        road[a].push_back(makenode(b,c));
        road[b].push_back(makenode(a,c));
    }
    DFS(1,0);
    printf("%lld",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

分析

膜拜大佬 http://blog.csdn.net/clx55555/article/details/78435407
这道题可以将每个点拆成两个,i和i’,分列两边,形成一个二分图,如果连边i->j,就在二分图上连接i和j’,这样的话,二分图的每一个完全匹配都对应了一种方案,因为假如从一点i开始,匹配到了j’,此时原图上实际是i和j连边,再看j的匹配k’,表示了j和k的连边,这样一直进行下去,就形成一个环,整个二分图的一个完全匹配就是很多个环,这样的话直接计算有多少个完全匹配就好了。
这里可以用状压DP的方式实现,d[i][j]表示有i个点匹配,j用2进制表示哪些点匹配上了。
具体还是看他的博客吧…
我能学到的就是这种用二分图表示环的方法了…

代码

不存在的(待填)

猜你喜欢

转载自blog.csdn.net/Nuclear_fusion/article/details/79327317