寒假训练2

寒假训练2


A - Pairwise Sum and Divide

有这样一段程序,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。

分析

A[i]>2&&A[j]>2,Floor((Aii+Ajj)/(Aii*Ajj))=0;
A[i]==1&&A[j]==1,Floor=2
A[i]==2&&A[j]==2,Floor=1
A[i]==1&&A[j]>1,Floor=1,
因此,只需统计A中1与2的个数

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 100000
int a[MAXN+5];
long long cnt[2];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    sort(a,a+n);
    for(int i=0;i<n;i++)
        if(a[i]==1||a[i]==2)
            cnt[a[i]]++;
        else break;
    long long ans=0;
    ans+=cnt[1]*(n-cnt[1]);
    ans+=cnt[1]*(cnt[1]-1);
    ans+=cnt[2]*(cnt[2]-1)/2;
    printf("%ld\n",ans);
}

B - 线段的重叠

X轴上有N条线段,每条线段包括1个起点和终点。线段的重叠是这样来算的,1020和1225的重叠部分为1220。给出N条线段的起点和终点,从中选出2条线段,这两条线段的重叠部分是最长的。输出这个最长的距离。如果没有重叠,输出0。

分析

按起始时间排序。枚举时,当前线段与排在它之前的线段的最大重叠的右端点要么为当前线段的右端点,要么为之前线段的最右端点。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 50000
struct node
{
    int s,t;
}a[MAXN+5];
bool cmp(node a,node b)
{
    return a.s<b.s;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d%d",&a[i].s,&a[i].t);
    sort(a,a+n,cmp);
    int ans=0,r=a[0].t;
    for(int i=1;i<n;i++)
    {
        ans=max(ans,min(r,a[i].t)-a[i].s);
        r=max(r,a[i].t);
    }
    printf("%d\n",ans);
}

C - 搬货物

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

分析

将当前货物除以2加到后一种货物上,将除以2的余数加到答案中。
不知道为什么TLE了,据说要写读入优化。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 1000000
int n,cnt[MAXN+100];
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()
{
    scanf("%d",&n);
    int x,r=-1;
    for(int i=0;i<n;i++)
    {
        int x;
        x=read();
        cnt[x]++;
        r=max(r,x);
    }
    int ans=0;
    for(int i=0;i<r+60;i++)
    {
        cnt[i+1]+=(cnt[i]>>1);
        ans+=(cnt[i]&1);
    }
    printf("%d\n",ans);
}

D - 宝岛地图

勇敢的水手们到达了一个小岛,在这个小岛上,曾经有海盗在这里埋下了一些宝藏。然而,我们的船快抛锚了,与此同时,船长发现藏宝图的一角被老鼠咬掉了一块。
藏宝图可以用一个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”(没有引号)。

分析

直观的想法从每一个起点走一遍,但如果一格一格去判断会TLE,所以要预处理出每个位置可向四个方位移动的最大距离是多少,然后再枚举每一个起点,用O(k)的时间判断该起点是否可行。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 1000
#define MAXQ 100000
char p[MAXN+5][MAXN+5];
struct question
{
    int d,len;
}q[MAXQ+5];
struct node
{
    int dir[4];
}a[MAXN+5][MAXN+5];
int n,m,nq;
int s=0;
char ans[30];
int dx[4]={-1,0,1,0};
int dy[4]={0,-1,0,1};
bool DFS(int i,int j,int p)
{
    if(p==nq) return 1;
    int r=q[p].d;
    if(a[i][j].dir[r]<=q[p].len) return 0;
    i=i+dx[r]*q[p].len,j=j+dy[r]*q[p].len;
    return DFS(i,j,p+1);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
        scanf("%s",p[i]);
    scanf("%d",&nq);
    char di[2];
    for(int i=0;i<nq;i++)
    {
        scanf("%s%d",di,&q[i].len);
        if(di[0]=='N')  q[i].d=0;
        else if(di[0]=='W') q[i].d=1;
        else if(di[0]=='S') q[i].d=2;
        else q[i].d=3;
    }
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            if(p[i][j]=='#')
                a[i][j].dir[0]=a[i][j].dir[1]=0;
            else
            {
                if(i!=0)
                    a[i][j].dir[0]+=a[i-1][j].dir[0];
                if(j!=0)
                    a[i][j].dir[1]+=a[i][j-1].dir[1];
                a[i][j].dir[0]++,a[i][j].dir[1]++;
            }
    for(int i=n-1;i>=0;i--)
        for(int j=m-1;j>=0;j--)
            if(p[i][j]=='#')
                a[i][j].dir[2]=a[i][j].dir[3]=0;
            else
            {
                if(i!=n-1)
                    a[i][j].dir[2]+=a[i+1][j].dir[2];
                if(j!=0)
                    a[i][j].dir[3]+=a[i][j+1].dir[3];
                a[i][j].dir[2]++,a[i][j].dir[3]++;
            }
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            if('A'<=p[i][j]&&p[i][j]<='Z')
                if(DFS(i,j,0))
                    ans[s++]=p[i][j];
    if(s==0) printf("no solution\n");
    else
    {
        sort(ans,ans+s);
        printf("%s\n",ans);
    }
}

E - 小鲨鱼在51nod小学

鲨鱼巨巨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

分析

按时间排序,给起始时间,终止时间,询问时间分别打上标记为0,2,1。排序后为0就放入set中,为2就从set中删去,为1就将set中的元素存入答案。

发现Wa在审题上

由于小鲨鱼担任的职务太多,所有任期小于一个自然月的职务都忽略不计。

代码

#include<cstdio>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
#define MAXN 300000
#define MAXQ 10000
struct node
{
    int y,m,d,x,f;
}a[MAXN];
set<int>Q;
vector<int>ans[MAXQ+5];
int al[MAXQ+5];
bool cmp(node a,node b)
{
    if(a.y==b.y)
    {
        if(a.m==b.m)
        {
            if(a.d==b.d)
                return a.f<b.f;
            return a.d<b.d;
        }
        return a.m<b.m;
    }
    return a.y<b.y;
}
int main()
{
    int n,q,cnt=0;
    int y1,y2,m1,m2,d1,d2,x;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%d%d%d%d",&y1,&m1,&d1,&y2,&m2,&d2,&x);
        if((y1+1==y2&&m1==12&&m2==1&&d1>d2)||(y1==y2&&(m1==m2||(m2==m1+1&&d1>d2)))) continue;
        a[++cnt].x=x;
        a[cnt].y=y1,a[cnt].m=m1,a[cnt].d=d1,a[cnt].f=0;
        a[++cnt].x=x;
        a[cnt].y=y2,a[cnt].m=m2,a[cnt].d=d2,a[cnt].f=2;
    }
    scanf("%d",&q);
    for(int i=0;i<q;i++)
    {
        scanf("%d%d%d",&y1,&m1,&d1);
        a[++cnt].x=i;
        a[cnt].y=y1,a[cnt].m=m1,a[cnt].d=d1,a[cnt].f=1;
    }
    sort(a+1,a+1+cnt,cmp);
    for(int i=1;i<=cnt;i++)
    {
        if(a[i].f==0) Q.insert(a[i].x);
        else if(a[i].f==2) Q.erase(a[i].x);
        else
        {
            int p=a[i].x;
            int len=Q.size();
            al[p]=len;
            set<int>::iterator it;
            for(it=Q.begin();it!=Q.end();it++)
                ans[p].push_back(*it);
        }
    }
    for(int i=0;i<q;i++)
    {
        printf("%d",al[i]);
        for(int j=0;j<al[i];j++)
            printf(" %d",ans[i][j]);
        printf("\n");
    }
}

F - 合法括号子段

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

分析

DP,先预处理出每个左括号对应的右括号的位置,dp[i]表示以i为起点的子序列中有多少个类似于AB结构的合法括号。从后向前枚举dp[i]=dp[nxt[i]+1]+1;

代码

#include<cstdio>
#include<stack>
#include<cstring>
using namespace std;
#define MAXN 1200000
char s[MAXN];
long long nxt[MAXN],d[MAXN];
void Solve()
{
    stack<int>Q;
    int n=strlen(s);
    for(int i=0;i<n;i++)
        d[i]=0,nxt[i]=-1;
    for(int i=0;i<n;i++)
    {
        if(s[i]=='(') Q.push(i);
        else
        {
            if(Q.empty()) continue;
            int x=Q.top(); Q.pop();
            nxt[x]=i;
        }
    }
    long long ret=0LL;
    for(int i=n-1;i>=0;i--)
    {
        if(nxt[i]!=-1)
        {
            d[i]=d[nxt[i]+1]+1;
            ret+=d[i];
        }
    }
    printf("%lld\n",ret);
}
int main()
{
    int t;
    scanf("%d",&t);
    for(int i=0;i<t;i++)
    {
        scanf("%s",s);
        Solve();
    }
}

G - 配对

给出一棵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)

分析

这道题没有想出来,题解如下:
由于权值在边上,所以我们按边算贡献!
显然,每条边最多只能做出它的权值乘以去掉它所形成的两个连通块中点数较小的那一个的点数那么大的贡献。那么, 我们如果能够构造出一种配对方法,使得所有边的贡献值达到最大,最终答案不就得到了吗?
其实呢,只要我们保证连接匹配点的路径经过重心,即可保证所有边的贡献值达到最大
因为此边就成为了两段连通的必经之地,就达到贡献最大了
其实计算的时候没必要求出重心

代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 100000
struct node
{
    int v,w;
};
vector<node>G[MAXN+5];
int sz[MAXN+5];
long long ans;
int n;
void DFS(int u,int fa)
{
    sz[u]=1;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i].v,w=G[u][i].w;
        if(v!=fa)
        {
            DFS(v,u);
            sz[u]+=sz[v];
            ans+=1LL*min(sz[v],n-sz[v])*w;
        }
    }
}
int main()
{
    scanf("%d",&n);
    int u;
    node e;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&e.v,&e.w);
        G[u].push_back(e);
        swap(u,e.v);
        G[u].push_back(e);
    }
    DFS(1,-1);
    printf("%lld\n",ans);
}

H - 环

有一个有向图。这张图有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

分析

没有思路(未完待续)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值