[Codeforces Gym]2015年ACM-ICPC越南国赛第二场简要题解

A. Stock Market

题目大意

你手上有 w 元钱,并且你已经预测出了近 n 天的某股票行情(这个股票每股价格),你只能选择一天买入股票,问你最多能在这 n 天中赚多少钱。

思路

显然股票肯定也是同一天卖出的(在第 i 天买入的话,肯定选择某个第 j 天卖出, j>ij 天的股票价格是在 [i+1,n] 天中是最大的),因此打暴力就行了,本场比赛最水的题。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1000

using namespace std;

int p[MAXN],n,w;

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&w);
        for(int i=1;i<=n;i++) scanf("%d",&p[i]);
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int num=w/p[i]; //第i天买入num份股票
            for(int j=i+1;j<=n;j++)
                ans=max(ans,num*p[j]-num*p[i]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

B. Sum

题目大意

ni=1[ni],n<=1012

思路

如果直接for循环一遍的话是 O(n) 的,显然TLE
但是发现向下取整的很有趣的性质,即 [nx] 在连续的一段 i 答案是相同的,并且 1<=x<=n 的情况下, [nx] 最多有 n 种不同的值,这是很显然的。
因此我们可以枚举 [ni] 的取值,并能维护两个指针 i,j 表示当前的 [nx] 的取值是对应于 x[i,j] 的,这样复杂度就降到了 O(n)
这种优化方法即分块优化思想(不是区间查询问题里的那个分块),在很多类似问题里都很常见。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MOD 1000000

using namespace std;

typedef long long int LL;

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        LL n,ans=0;
        scanf("%I64d",&n);
        LL i=1;
        while(i<=n)
        {
            LL j=n/(n/i);
            ans=(ans+((j-i+1)*(n/i))%MOD)%MOD;
            i=j+1;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

C. ATM withdrawal

题目大意

你有
1000 2000 3000 5000 1000101 2000101 3000101 5000101 …… 500010c 的钞票,每种面值的钞票若干张,然后要用这样纸币凑齐 W 元钱,求最少要多少张纸币,以及最小方案的方案数。

题解

可以想到一个非常显然的思路:
首先把 W 除以1000(越南猴子的钞票通货膨胀很严重啊233),这样钞票就变成了1、2、3、5、10、20、30、50……这样的面值比较好看,然后把现在的 W 按照位数分解,对于个位到第 c 位,每一位均是一个个位数,对这 c 个个位数求出每一位需要凑齐的纸币最少要多少张,我们可以打表得到 need[i]=i(i<10) 需要最少多少张纸币才能凑齐, ways[i]=i 用纸币凑齐的最小方案有多少种,然后对于 c+1 到最高位,只能用最大的面值( 510c )来凑,用上述方法求和即为最小方案,乘法计数即可得到最小方案的个数。这样贪心很显然是可以得到最少要多少张纸币的,但是无法正确地求出最小方案的种数,比如下面这个反例: W=110 ,用上面的贪心可以得到最小方案有一种: 50+50+10 ,但是实际上还有一种最小方案: 50+30+30
究其原因就是因为我们僵化了想法, W 太大的部分不一定非得要用 510c 凑齐,可以留一个 510c ,即留下 W mod 510c+510c
因此我们先用 [(W510c)510c] 510c 面值的纸币,对剩下的 W=W[(W510c)510c](510c) 元,再用上面的贪心做法做一遍即可。

D. Treasure Box

题目大意

对数字 N 进行 K 次变换,每次变换如下:
N=N+N mod 100
求经过 K 次变换后 N 最终变成什么数字

题解

如果 O(K) 打暴力的话,单组数据在2s的时限内玩玩常数是有可能AC的,但是由于题目最多500组数据,因此还是会TLE。

最终的答案实际上可以看成是 XK=XK1+AK ,其中 Xt 是做了 t 次变换后得到的答案。 At= 在做第 t 次变换时给答案增加上去的那个值。

At 可以看成是 (N+...(N+(Nmod100))...mod100) 这样的形式,因为一直是 mod 100 ,因此可以改写成 At=(2t1N) mod 100 。也可以写成递推形式, At=2At1 mod 100A1=N

可以发现,在 t>2 时, At=(2t3N) mod 25=At2 ,于是出现了循环节,当然也可以通过打表找循环规律。那么我们没有必要挨个挨个加 K 次答案,只需要把循环节的那部分乘上循环节的出现次数就可以了,剩下的不是循环节的部分也不是很长,因此这样做可以AC掉。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1000

using namespace std;

typedef long long int LL;

LL N,K;
LL hash[110];
LL step[110]; //step[i]= X mod 100=i时移动的步长

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(hash,-1,sizeof(hash));
        memset(step,0,sizeof(step));
        scanf("%lld%lld",&N,&K);
        LL X=N%100;
        LL i;
        LL addv=0;
        for(i=1;i<=K;i++)
        {
            addv+=X;
            if(hash[X]!=-1)
            {
                addv+=((K-i)/(i-step[X]))*(addv-hash[X]);
                i+=(i-step[X])*((K-i)/(i-step[X]));
                X=(X*2)%100;
                break;
            }
            else
            {
                hash[X]=addv;
                step[X]=i;
                X=(X*2)%100;
            }
        }
        for(;i<K;i++)
        {
            addv+=X;
            X=(X*2)%100;
        }
        printf("%lld\n",N+addv);
    }
    return 0;
}

H. Pencil Game

题目大意

给定一个 NM 大小的棋盘,棋盘上的点 (i,j),(0<=i<=N,0<=j<=M) 上的数字为 iM+j ,要从中找一个面积最小的子矩形,使得该子矩形的元素之和为 L ,求该子矩形的最小面积,若无解输出-1

题解

不妨设这个子矩形最左边的列号是 x1 ,最右边的列号是 x2 ,最上边的行号是 y1 ,最下边的行号是 y2 。很容易推出这个子矩形的元素之和S与其关系为:
2S=(x2x1+1)(y2y1+1)[x1+x2+M(y1+y2)]
X=(x2x1+1) Y=(y2y1+1) ,显然 XY 就是最终答案
再把上式改写
2S=XY[x1+x2+M(y1+y2)]
那么我们最终可以爆枚 2L 的约数 XY ,然后判断爆枚出的 XY 是否合法,并维护最小的 XY 值,由于 L<=1012 ,而且一个数的约数个数非常少,爆枚复杂度为 O(2L) ,因此可以通过此题。

I. Space Tour

题目大意

给一个 NM 大小的棋盘,棋盘上某些格子有障碍物。一个火星车要空降到棋盘的某一点,然后从这一点开始,每次从下列两种方法循环依次选择一种方法来移动:
1、向右拐弯,前进一格
2、向左拐弯,前进一格

这个火星车不能越过棋盘的边界,也不能经过某些障碍格子,问这个火星车最多能遍历多少格子。

思路

可以发现这个火星车从降落点开始的行走路线如下图一样
[Codeforces Gym]2015年ACM-ICPC越南国赛第二场简要题解_第1张图片
这个路线是放射状的,并且开始的方向一定是像下图这样:
[Codeforces Gym]2015年ACM-ICPC越南国赛第二场简要题解_第2张图片

可以将这四条路线分别标记为从左上、左下、右上、右下出发而来的路线,由于这四条路线的形态是固定的,因此我们可以对每条路线,从路线的终点倒过来dp, f[i][j]= 某条路线走到 (i,j) ,沿途遍历过的格子个数。然后注意一定要在路线上每隔一个格子转移一次状态,即如下面的伪代码一样(伪代码来自本次比赛的tutorial):
[Codeforces Gym]2015年ACM-ICPC越南国赛第二场简要题解_第3张图片

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100

using namespace std;

char s[MAXN];
int map[MAXN][MAXN];
int fzs[MAXN][MAXN],fzx[MAXN][MAXN],fys[MAXN][MAXN],fyx[MAXN][MAXN]; //左上、左下、右上、右下为起点到(i,j)的方案数
int n,m;

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(fzs,0,sizeof(fzs));
        memset(fzx,0,sizeof(fzx));
        memset(fys,0,sizeof(fys));
        memset(fyx,0,sizeof(fyx));
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
        {
            scanf("%s",s+1);
            for(int j=1; j<=m; j++)
                map[i][j]=s[j]=='1';
        }
        //左上角开始的路线的DP
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                if(!map[i][j])
                    fzs[i][j]=0;
                else if(!map[i][j-1]) fzs[i][j]=1;
                else fzs[i][j]=fzs[i-1][j-1]+2;
        //左下角路线开始的DP
        for(int i=n; i>=1; i--)
            for(int j=1; j<=m; j++)
                if(!map[i][j])
                    fzx[i][j]=0;
                else if(!map[i+1][j]) fzx[i][j]=1;
                else fzx[i][j]=fzx[i+1][j-1]+2;
        //右上角路线开始的DP
        for(int i=1; i<=n; i++)
            for(int j=m; j>=1; j--)
                if(!map[i][j])
                    fys[i][j]=0;
                else if(!map[i-1][j]) fys[i][j]=1;
                else fys[i][j]=fys[i-1][j+1]+2;
        //右下角路线开始的DP
        for(int i=n; i>=1; i--)
            for(int j=m; j>=1; j--)
                if(!map[i][j])
                    fyx[i][j]=0;
                else if(!map[i][j+1]) fyx[i][j]=1;
                else fyx[i][j]=fyx[i+1][j+1]+2;
        int maxans=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                maxans=max(maxans,fzx[i][j]+fys[i][j]+fzs[i][j]+fyx[i][j]-3);
        printf("%d\n",maxans);
    }
    return 0;
}

你可能感兴趣的:([Codeforces Gym]2015年ACM-ICPC越南国赛第二场简要题解)