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

G题没时间做了,之前想了个要炸的O(NQ),后来想了个没想好的O(nlogn+Qlogn);

A - n^n的末位数字 51Nod - 1004

给出一个整数N,输出N^N(N的N次方)的十进制表示的末位数字。
Input
一个数N(1 <= N <= 10^9)
Output
输出N^N的末位数字
Sample Input
13
Sample Output
3

思路

一上来就是道水题,快速幂模10不就完了吗。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
int ksm(int x,int y,int MOD)
{
    int k=1;
    x%=MOD;
    while(y>0)
    {
        if(y&1)
            k=(k*x)%MOD;
        x=(x*x)%MOD;
        y>>=1;
    }
    return k;
}
int main()
{
    int n;
    scanf("%d",&n);
    printf("%d\n",ksm(n,n,10));
}

B - 蜥蜴和地下室 51Nod - 1489

哈利喜欢玩角色扮演的电脑游戏《蜥蜴和地下室》。此时,他正在扮演一个魔术师。在最后一关,他必须和一排的弓箭手战斗。他唯一能消灭他们的办法是一个火球咒语。如果哈利用他的火球咒语攻击第i个弓箭手(他们从左到右标记),这个弓箭手会失去a点生命值。同时,这个咒语使与第i个弓箭手左右相邻的弓箭手(如果存在)分别失去b(1 ≤ b < a ≤ 10)点生命值。

因为两个端点的弓箭手(即标记为1和n的弓箭手)与你相隔较远,所以火球不能直接攻击他们。但是哈利能用他的火球攻击其他任何弓箭手。

每个弓箭手的生命值都已知。当一个弓箭手的生命值小于0时,这个弓箭手会死亡。请求出哈利杀死所有的敌人所需使用的最少的火球数。

如果弓箭手已经死亡,哈利仍旧可以将他的火球扔向这个弓箭手。

Input
第一行包含3个整数 n, a, b (3 ≤ n ≤ 10; 1 ≤ b < a ≤ 10),第二行包含n个整数――h1,h2,…,hn (1 ≤ hi ≤ 15), hi 是第i个弓箭手所拥有的生命力。
Output
以一行输出t――所需要的最少的火球数。
Sample Input
3 2 1
2 2 2
Sample Output
3

思路

开始以为是贪心,因为边边上的只能有一种打死方式,于是写了个贪心,理所应当的wa了。这才发现中间的可以有几种方式打死,再一看数据范围,写了个十分暴力的dfs,枚举当前是否被打死,如果没有,就枚举两种打死方式。结果wa了,其实这个dfs和上面的贪心都有一个bug。没有考虑两种被打方式共存的情况。于是写了个更加暴力的dfs。枚举打的次数。从刚好能把前面的打死到能把自己打死。没有卡时间就过了。(两种打死指的是用a和b)(dfs前先把两边打死)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=15;
int h[MAXN],n,a,b,ans=0,ee=0;
void dfs(int x,int jans)
{
    if(x>=n)
    {
        ans=min(ans,jans);
        return ;
    }
    if(h[x-1]<0)
    {
        dfs(x+1,jans);
    }
    int c=0;
    if(h[x-1]>0)
    {
        c=h[x-1]/b+1;
        h[x-1]-=b*c;
        h[x]-=a*c;
        h[x+1]-=b*c;
        dfs(x+1,jans+c);
        h[x-1]+=b*c;
        h[x]+=a*c;
        h[x+1]+=b*c;
    }
    int c2=h[x]/a+1;
    if(h[x]>=0)
    {
        for(int i=c+1;i<=c2;i++)
        {
            h[x-1]-=b*i;
            h[x]-=a*i;
            h[x+1]-=b*i;
            dfs(x+1,jans+i);
            h[x-1]+=b*i;
            h[x]+=a*i;
            h[x+1]+=b*i;
        }
    }
}
int main()
{
    scanf("%d %d %d",&n,&a,&b);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&h[i]);
    }
    if(h[1]>=0)
    {
        int c=h[1]/b+1;
        ee+=c;
        h[1]-=b*c;
        h[2]-=a*c;
        h[3]-=b*c;
    }
    if(h[n]>=0)
    {
        int c=h[n]/b+1;
        ee+=c;
        h[n-2]-=b*c;
        h[n-1]-=a*c;
        h[n]-=b*c;
    }
    ans=1000000;
    dfs(2,ee);
    printf("%d\n",ans);
}

C - 前缀后缀集合 51Nod - 1280

一个数组包含N个正整数,其中有些是重复的。一个前缀后缀集是满足这样条件的下标对(P,S), 0<= P,S < N 满足数组元素A0..P0..P的值也在AS..N−1S..N−1的值中出现,并且AS..N−1S..N−1中的值也再A0..P0..P中出现。换句话说前缀的集合A0..P0..P与后缀集合AS..N−1S..N−1包含完全相同的值。求这样的前缀后缀集合的数量。

例如:3 5 7 3 3 5,共有14个集合符合条件:(1, 4), (1, 3), (2, 2), (2, 1), (2, 0), (3, 2), (3, 1), (3, 0), (4, 2), (4, 1), (4, 0), (5, 2), (5, 1), (5, 0)
本题由 @javaman 翻译。

Input
第1行:一个数N, 表示数组的长度(1 <= N <= 50000)。
第2 - N + 1行:每行1个数,对应数组中的元素Ai。(1 <= Ai <= 10^9)
Output
输出符合条件的集合数量。
Sample Input
6
3
5
7
3
3
5
Sample Output
14

思路

开始看到这道题一头雾水O(n^2)(就是暴力枚举S,P)过不了啊,。于是去做后面的题。等到再回来看时,便想把暴力的O(n^2)进行优化,首先离散化,这样就可以用vis打标记了(其实map也行,不过我喜欢离散化),然后再看,如果枚举到某P时枚举S时S枚举着枚举着,咔嚓,S有P没有的了,那么就可以break了,但是这不够,P每次只是加了一个元数(而且还可能没有加,如果没有加就直接加上次的答案咯),上次的vis如果清零就太可惜了,应该可以利用一下,很明显,能给答案有贡献的应该是一段连续的区间,当上次成功有贡献后,上次的区间明显和这次要找到只差了一个元素,我们可以从上次失败的地方继续找,这样后面的时间复杂的就成功的变成了O(n)加上离散化,为O(nlogn);

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=50005;
int n,a[MAXN],b[MAXN];
bool vis[MAXN],vis2[MAXN];
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b,b+n);
    int tot=unique(b,b+n)-b;
    for(int i=0;i<n;i++)
    {
        a[i]=lower_bound(b,b+tot,a[i])-b;
        //printf("%d ",a[i]);
    }
    int x=0,y=0,ans=0,jans=0,s=n-1;
    for(int i=0;i<n;i++)
    {
        if(!vis[a[i]])
        {
            vis[a[i]]=1;
            x++;
        }
        else
        {
            ans+=jans;
            continue;
        }
        jans=0;
        for(int j=s;j>=0;j--)
        {
            if(!vis[a[j]])
                break;
            if(!vis2[a[j]])
            {
                vis2[a[j]]=1;
                y++;
            }
            if(x==y)
            {
                jans++;
                s=j-1;
            }
        }
        ans+=jans;
    }
    printf("%d\n",ans);
}

D - 山峰和旗子 51Nod - 1281

用一个长度为N的整数数组A,描述山峰和山谷的高度。山峰需要满足如下条件, 0 < P < N - 1 且 AP−1P−1 < APP > AP+1P+1。

现在要在山峰上插上K个旗子,并且每个旗子之间的距离 >= K,问最多能插上多少个旗子(即求K的最大值)。两个山峰之间的距离为|P - Q|。
以上图为例,高度为:1 5 3 4 3 4 1 2 3 4 6 2。其中可以作为山峰的点为:1 3 5 10。

放2面旗子, 可以放在1 和 5。
放3面旗子, 可以放在1 5 和 10。
放4面旗子, 可以放在1 5 和 10,之后就放不下了。
所以最多可以放3面旗子。
Input
第1行:一个数N,表示数组的长度(1 <= N <= 50000)。
第2 - N + 1行:每行1个数Ai(1 <= Ai <= 10^9)。
Output
输出最多能插上多少面旗子(即求K的最大值)。
Sample Input
12
1
5
3
4
3
4
1
2
3
4
6
2
Sample Output
3

思路

这个先处理一下变成只有山峰,存的是山峰的距离。然后发现这道题和原来做的某道奶牛跳石头有惊人的相似,于是便想到用二分。二分答案,然后check;过了;

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=50005;
int a[MAXN],b[MAXN],tot=0,n;
bool check(int x)
{
    int k=0,la=-1000;
    for(int i=1;i<=tot;i++)
    {
        if(b[i]-la>=x)
        {
            la=b[i];
            k++;
        }
    }
    return k>=x;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=2;i<n;i++)
    {
        if(a[i-1]<a[i]&&a[i]>a[i+1])
        {
            b[++tot]=i;
        }
    }
    int l=0,r=n;
    while(r-l>1)
    {
        int mid=(l+r)>>1;
        if(check(mid))
            l=mid;
        else
            r=mid;
    }
    printf("%d\n",l);
}

E - 最长等差数列 51Nod - 1055

N个不同的正整数,找出由这些数组成的最长的等差数列。

例如:1 3 5 6 8 9 10 12 13 14
等差子数列包括(仅包括两项的不列举)
1 3 5
1 5 9 13
3 6 9 12
3 8 13
5 9 13
6 8 10 12 14

其中6 8 10 12 14最长,长度为5。
Input
第1行:N,N为正整数的数量(3 <= N <= 10000)。
第2 - N+1行:N个正整数。(2<= Aii <= 10^9)
Output
最长等差数列的长度。
Sample Input
10
1
3
5
6
8
9
10
12
13
14
Sample Output
5

思路

开始读题读半天,没太懂,以为是要找一个等差的子串,一脸懵逼。便去做F题。倒回来做时才发现是“由这些数组成的最长的等差数列”,不用讲究顺序。这样肯定先排序呀。然后想贪心,贪心不对,那应该是DP,先想dp[i][j] 表示第i个数为末尾,数列差为j的最大等差长度,但是很明显。存不下啊。于是变成dp[i][j]表示第i个数为末尾上一个数为j的最大等差长度,这样空间小了很多,也可以知道差了。但是每次要找i,j和另外一个数的话就是O(n^3)了,假设i是中间的那个数,那么前面的数l和后面的r必须和i构成等差数列才能计算,如果每次确定l后都找一遍r的话,就算是logn也O(n^2logn)太慢了,受c题影响,我发现完全没有这个必要,如果l从i-1开始向前走,那么由于数列是排了序的,l要找的数一定在上次找到的数的后面,如果相等,那么看那边移动的越少移哪边,这样就只有O(n^2)了。时间开了2s,10000应该能勉强卡过吧。但是————MLE!哇,内存超了一倍怎么办!无可奈何,我向李兽骏东(化名)询问有没有坑,李兽骏东说:“反正我用short int过了”;short int!!!,我**,这个东西说实话,我以前没有怎么用过,现在我还真的差点忘了有这个东西,而且ans还不会炸。真是个好东西,我把dp改成了short int 过了。。。。。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=10005;
short int dp[MAXN][MAXN];
int a[MAXN];
int n,ans=0;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        fill(dp[i],dp[i]+MAXN,2);
    }
    ans=2;
    sort(a+1,a+n+1);
    for(int i=2;i<n;i++)
    {
        int l=i-1,r=i+1;
        while(l>=1&&r<=n)
        {
            if(a[i]-a[l]==a[r]-a[i])
            {
                dp[r][i]=dp[r][i]<dp[i][l]+1?dp[i][l]+1:dp[r][i];
                ans=ans<dp[r][i]?dp[r][i]:ans;
                if(l==1||a[i]-a[l-1]>a[r+1]-a[i])
                    r++;
                else
                    l--;
            }
            if(a[i]-a[l]>a[r]-a[i])
                r++;
            if(a[i]-a[l]<a[r]-a[i])
                l--;
        }
    }
    printf("%d\n",ans);
}

F - 零树 51Nod - 1424

有一棵以1为根的树,他有n个结点,用1到n编号。第i号点有一个值vi。

现在可以对树进行如下操作:

步骤1:在树中选一个连通块,这个连通块必须包含1这个结点。

步骤2:然后对这个连通块中所有结点的值加1或者减1。

问最少要经过几次操作才能把树中所有结点都变成0。

注意:步骤1与步骤2合在一起为一次操作。

Input
单组测试数据。
第一行有一个整数n(1 ≤ n ≤ 10^5)
接下来n-1行,每行给出 ai 和 bi (1 ≤ ai, bi ≤ n; ai ≠ bi),表示ai和bi之间有一条边,输入保证是一棵树。
最后一行有n个以空格分开的整数,表示n个结点的值v1, v2, …, vn (|vi| ≤ 10^9)。
Output
输出一个整数表示最少的操作步数。.
Sample Input
3
1 2
1 3
1 -1 1
Sample Output
3

思路

这个题开始还在想用拓扑序做的,后来码到一半发现完全没必要,直接dfs记录儿子的正负使用情况(就是加了多少次,减了多少次),然后这个节点的情况就是所有儿子的最大值再加上自己(自己要先加上改变的情况)。交上去,wa。看数据范围,要用long long,正式的比赛要是出了这个问题那真是太严重了,每道题还是看看数据范围。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const long long MAXN=100005;
long long add[MAXN],ua[MAXN],val[MAXN],n;
vector<int>w[MAXN];
void dfs(long long u,long long fa)
{
    for(long long i=0;i<int(w[u].size());i++)
    {
        long long v=w[u][i];
        if(v!=fa)
        {
            dfs(v,u);
            add[u]=max(add[u],add[v]);
            ua[u]=max(ua[u],ua[v]);
        }
    }
    val[u]=val[u]+add[u]-ua[u];
    if(val[u]>0)
        ua[u]+=val[u];
    if(val[u]<0)
        add[u]-=val[u];
}
int main()
{
    scanf("%I64d",&n);
    for(long long i=1;i<n;i++)
    {
        long long u,v;
        scanf("%I64d %I64d",&u,&v);
        w[u].push_back(v);
        w[v].push_back(u);
    }
    for(long long i=1;i<=n;i++)
        scanf("%I64d",&val[i]);
    dfs(1,-1);
    printf("%I64d\n",add[1]+ua[1]);
}

G - Jabby’s segment tree 51Nod - 1792

线段树是一种经典的数据结构,一颗1,n1,n的线段树他的根是1,n1,n,当一个线段树的结点是l,rl,r时,设mid=(l+r)>>1,则这个结点的左儿子右儿子分别是l,midl,mid,mid+1,rmid+1,r
当我们在线段树上跑x,yx,y询问时,一般是从根节点开始计算的,设现在所在结点是l,rl,r,有以下几种分支:
1.若x,yx,y包含l,rl,r,计算结束
2.否则,若左儿子和x,yx,y有交,计算左儿子,若右儿子和x,yx,y有交,计算右儿子
定义询问x,yx,y的费用是询问时计算了几个结点
给定Q次询问,每次给定l,r,求满足l<=x<=y<=r的(x,y)的费用之和
你需要将答案对1000000007取模
Input
第一行两个正整数n,Q(1<=n,Q<=100000)
接下来Q行每行两个正整数l,r,保证l<=r
Output
输出Q行,每行一个非负整数表示答案
Sample Input
2 1
1 2
Sample Output
5

思路

本次考试没有做出来的题,看了题解,打算明天再码。

代码

waiting
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值