10.28 test solution.

Round 1

1.立方数(cubic)

Time Limit:

1000ms

Memory Limit:

128MB

题目描述

LYK定义了一个数叫“立方数”,若一个数可以被写作是一个正整数的 3 次方,则这个数就是立方数,例如 1,8,27 就是最小的 3 个立方数。
现在给定一个数 P,LYK想要知道这个数是不是立方数。
当然你有可能随机输出一些莫名其妙的东西来骗分,因此LYK有 T 次询问~

输入格式(cubic.in)

第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行一个数 P

输出格式(cubic.out)

输出 T 行,对于每个数如果是立方数,输出“YES”,否则输出“NO”。

输入样例

3
8
27
28

输出样例

YES
YES
NO

数据范围

对于 30% 的数据 p100
对于 60% 的数据 p106
对于 100% 的数据 p1018,T100


solution

  • 10183=106 ,直接枚举 1106 就行啦,也可以二分

code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

#define MAXN 1000010
ll a[MAXN];

int main() {
    freopen("cubic.in","r",stdin);
    freopen("cubic.out","w",stdout);
    int t;
    input(t);
    for(ll i=1;i<=1000000;i++)
        a[i]=i*i*i;
    while(t--) {
        ll x;
        input(x);
        int l=1,r=1000000,mid;
        while(l<=r) {
            mid=l+r>>1;
            if(a[mid]<=x) l=mid+1;
            else r=mid-1;
        }
        puts(a[l-1]==x?"YES":"NO");
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

2.立方数2(cubicp)

Time Limit:

1000ms

Memory Limit:

128MB

题目描述

LYK定义了一个数叫“立方数”,若一个数可以被写作是一个正整数的 3 次方,则这个数就是立方数,例如 1,8,27 就是最小的 3 个立方数。
LYK还定义了一个数叫“立方差数”,若一个数可以被写作是两个立方数的差,则这个数就是“立方差数”,例如 7(81),26(271),19(278) 都是立方差数。
现在给定一个数 P ,LYK想要知道这个数是不是立方差数。
当然你有可能随机输出一些莫名其妙的东西,因此LYK有 T 次询问~
这个问题可能太难了…… 因此LYK规定 P 是个质数!

输入格式(cubicp.in)

第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行一个数 P

输出格式(cubicp.out)

输出 T 行,对于每个数如果是立方差数,输出“YES”,否则输出“NO”。

输入样例

5
2
3
5
7
11

输出样例

NO
NO
NO
YES
NO

数据范围

对于 30% 的数据 p100
对于 60% 的数据 p106
对于 100% 的数据 p1012,T100


solution

  • 考虑立方差公式 a3b3=(ab)(a2+ab+b2)

  • 假设 p 是立方差数,那么p=(ab)(a2+ab+b2)

  • 又因为 p 是素数,所以 ab=1,否则 p 就不是素数

  • 所以p=a2+ab+b2

  • ab=1b=a1 ,把 b 代入就可以得到

  • p=a2+a(a1)+(a1)2=3a23a+1

  • 然后 a p=106 级别的,直接枚举/二分就好了

code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

ll calc(ll x) {
    return 3*x*(x-1)+1;
}

int main() {
    freopen("cubicp.in","r",stdin);
    freopen("cubicp.out","w",stdout);
    int t;
    input(t);
    while(t--) {
        ll x;
        input(x);
        ll l=1,r=1000000,mid,ans;
        while(l<=r) {
            mid=l+r>>1;
            if((calc(mid))<=x) ans=mid,l=mid+1;
            else r=mid-1;
        }
        puts(calc(ans)==x?"YES":"NO");
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

3.猜数字(number)

Time Limit:

2000ms

Memory Limit:

128MB

题目描述

LYK在玩猜数字游戏。

总共有 n 个互不相同的正整数,LYK每次猜一段区间的最小值。形如[li,ri]这段区间的数字的最小值一定等于 xi

我们总能构造出一种方案使得LYK满意。直到…… LYK自己猜的就是矛盾的!

例如LYK猜 [1,3] 的最小值是 2 [1,4]的最小值是 3 ,这显然就是矛盾的。

你需要告诉LYK,它第几次猜数字开始就已经矛盾了。

输入格式(number.in)

第一行两个数 n T ,表示有 n 个数字,LYK猜了 T 次。
接下来 T 行,每行三个数分别表示 li,ri xi

输出格式(number.out)

输出一个数表示第几次开始出现矛盾,如果一直没出现矛盾输出 T+1

输入样例

20 4
1 10 7
5 19 7
3 12 8
1 20 1

输出样例

3

数据范围

对于 50% 的数据 n8T10
对于 80% 的数据 n1000T1000
对于 100% 的数据 1n,T1000000,1lirin,1xin (但并不保证一开始的所有数都是 1n 的)。

Hint

建议使用读入优化

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    for(; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}

2000ms 是临时改的,想挑战大自然的可以试一下1000ms怎么做


solution

  • 这道题目可以二分,因为它满足

    • 如果 [1,mid] 可行,那么 [1,mid1] 肯定也可行

    • 如果 [1,mid] 不可行,那么 [1,mid+1] 肯定也不可行

  • 二分之后就是 Judge 函数怎么写的问题

  • 我们按 xi 降序排序,那么如果一个区间 [li,ri] 之前被大于 xi 的更大的区间覆盖过,那么就不可行(这里“更大的区间”的意思是“完全包含 [li,ri] 的区间”)

  • 但其实上面的算法有BUG,我们按照上面的方法分析一下样例

  • 排序之后变成

l[i] r[i] x[i]
3     12  8
1     10  7
5     19  7
1     20  1
  • 首先覆盖 [3,12] ,然后覆盖 [1,10] ,因为这个时候 [1,2] 还没被覆盖,所以是合法的,然后再覆盖 [5,19] ,这时 [13,19] 还没被覆盖,所以这个也是合法的,最后覆盖 [1,20] ,这时 [20,20] 还没被覆盖,也是合法的,最后输出的应该是 5 ,很明显,WA

  • 考虑哪里出了问题。题目中有这样一句话“总共有 n 个互不相同的正整数”

  • 根据我们上面对样例的模拟,[1,2]的最小值应该是 7 [13,19] 的最小值也是 7 ,出现了两个 7,这就是上面算法的BUG

  • 怎么把BUG干掉呢,我们发现如果要让 [1,10] [5,19] 的最小值都是 7 ,而且 7 只出现 1 次,那么只能让这两个区间的交集[5,10] 的最小值是 7

  • 然后我们就可以得到改进后的算法

  • 首先对于 xi 相同的区间求一个区间交,然后再按 xi 降序排序,那么如果一个区间 [li,ri] 之前被大于 xi 的更大的区间覆盖过,那么就不可行

  • 按照上面的方法求一下,发现我们能过样例了,贼jb开心

  • 然后问题就变成了,怎样知道一个区间是否被覆盖和怎样覆盖一个区间

  • nlog2n 的做法:先二分答案,然后建立一棵线段树,每个节点存区间最小值,一开始全为 0 ,查询区间是否覆盖可以查区间最小值是否为 1,覆盖一个区间就把这个区间的最小值全部设为 1

  • nlognα(n)的做法:先二分答案,每次 Judge 用并查集快速维护数组 f[i] f[i] 表示在 i 右边离 i 最近的没被覆盖的点是哪一个,初始化 f[i]=i ,查询区间是否覆盖可以直接查 find(l) find 函数的用途和并查集的差不多,可以理解成 find(i) 是找到在 i 右边离 i 最近的没被覆盖的点是哪一个)是否大于 r ,如果不理解请反复体会f[i] 的意义,然后覆盖一个区间的话,可以用以下代码来解决

for(int i=find(l);i<=r;i++)
    f[find(i)]=find(r+1);
  • 不理解的话,再读一次吧,只要理解了 f[i] find(i) 这个东西超级简单的

  • 最后一个注意的地方,覆盖一个区间的时候,是覆盖区间的并还是区间的交呢?

  • 这是一个值得思考的地方,我们简单修改一下样例来看一下这个问题

  • 造这么一组数据

Input

5
1 10 7
5 19 7
3 12 7
1 2 6
1 20 1

Output

4
  • 按照上面的做法模拟两次,一次覆盖区间的时候覆盖区间的并,一次覆盖区间的时候覆盖区间的交,强烈建议自己动手模拟自己体会,然后你会发现覆盖区间的并才是正解,你要问为什么?请看加粗的那句话

  • 线段树那个好像是不能过的(80分好像是),并查集是可以 A

code

因为线段树的太恶心了,直接贴并查集了

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
#define MAXN 1000010

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

struct Guess {
    int l,r,x;
    bool operator <(const Guess &q) const {
        return x>q.x;
    }
};

Guess a[MAXN],t[MAXN];
int n,T,ans;
int f[MAXN];

int find(int x) {
    return f[x]==x?x:f[x]=find(f[x]);
}

bool Judge(int k) {
    for(int i=0;i<=n+1;i++) f[i]=i;
    for(int i=1;i<=k;i++) t[i]=a[i];
    sort(t+1,t+k+1);
    int lmin=t[1].l,lmax=t[1].l,rmin=t[1].r,rmax=t[1].r;
    for(int i=2;i<=k;i++)
        if(t[i].x<t[i-1].x) {
            if(find(lmax)>rmin) return true;
            for(int j=find(lmin);j<=rmax;j++)
                f[find(j)]=find(rmax+1);
            lmin=lmax=t[i].l,rmin=rmax=t[i].r;
        } else {
            lmin=min(lmin,t[i].l),
            lmax=max(lmax,t[i].l),
            rmin=min(rmin,t[i].r),
            rmax=max(rmax,t[i].r);
            if(lmax>rmin) return true;
        }
    if(find(lmax)>rmin) return true;
    return false;
}

int main() {
    freopen("number.in","r",stdin);
    freopen("number.out","w",stdout);
    input(n),input(T);
    for(int i=1;i<=T;i++)
        input(a[i].l),input(a[i].r),input(a[i].x);
    int l=1,r=T,mid,ans=T+1;
    while(l<=r) {
        mid=l+r>>1;
        if(Judge(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

Round 2

1.水题(water)

Time Limit:

1000ms

Memory Limit:

128MB

题目描述

LYK出了道水题。
这个水题是这样的:有两副牌,每副牌都有 n 张。
对于第一副牌的每张牌长和宽分别是 xi yi 。对于第二副牌的每张牌长和宽分别是 aj bj 。第一副牌的第 i 张牌能覆盖第二副牌的第j张牌当且仅当xiaj并且 yibj (注意牌不能翻转)。当然一张牌只能去覆盖最多一张牌,而不能覆盖好多张。
LYK想让两副牌的各 n 张一一对应叠起来。它想知道第二副牌最多有几张能被第一副牌所覆盖。

输入格式(water.in)

第一行一个数 n
接下来 n 行,每行两个数 xi,yi
接下来 n 行,每行两个数 aj,bj

输出格式(water.out)

输出一个数表示答案。

输入样例

3
2 3
5 7
6 8
4 1
2 5
3 4

输出样例

2

数据范围

对于 50% 的数据 n10
对于 80% 的数据 n1000
对于 100% 的数据 1n100000,1xi,yi,aj,bj109


solution

  • 贪心

  • 首先将所有牌的 xi 排序

  • 然后每遇到第二副牌,就把 yi 放入一个数据结构中

  • 每遇到第一副牌,就在那个数据结构中找到小于 yi 且最大的一张牌

  • 然后把那张牌从数据结构中删除

  • 所以我们需要一个支持插入删除的数据结构,可以用平衡树,权值线段树,multiset来实现

  • 一个值得注意的地方:当 xi 相同的时候,应该把第二类牌排在前面

code

#include<set>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

#define MAXN 100010

struct Data {
    int x,y;
    bool flag;
    Data(int x=0,int y=0,bool flag=false):
        x(x),y(y),flag(flag) {}
    bool operator < (const Data &q) const {
        if(x!=q.x) return x<q.x;
        return !flag;
    }
};

Data a[MAXN<<1];

multiset<int> s;
multiset<int>::iterator it;

int main() {
    freopen("water.in","r",stdin);
    freopen("water.out","w",stdout);
    int n;
    input(n);
    for(int i=1;i<=n+n;i++)
        input(a[i].x),input(a[i].y),a[i].flag=i<=n;
    n<<=1;
    sort(a+1,a+n+1);
    int ans=0;
    for(int i=1;i<=n;i++)
        if(!a[i].flag) s.insert(a[i].y);
        else {
            it=s.upper_bound(a[i].y);
            if(it!=s.begin())
                s.erase(--it),ans++;
        }
    printf("%d\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

2.梦境(dream)

Time Limit:

1000ms

Memory Limit:

128MB

题目描述

LYK做了一个梦。
这个梦是这样的,LYK是一个财主,有一个仆人在为LYK打工。
不幸的是,又到了月末,到了给仆人发工资的时间。但这个仆人很奇怪,它可能想要至少 x 块钱,并且当LYK凑不出恰好 x 块钱时,它不会找零钱给LYK。
LYK知道这个 x 一定是 1n 之间的正整数。当然抠门的LYK只想付给它的仆人恰好 x 块钱。但LYK只有若干的金币,每个金币都价值一定数量的钱(注意任意两枚金币所代表的钱一定是不同的,且这个钱的个数一定是正整数)。LYK想带最少的金币,使得对于任意 x,都能恰好拼出这么多钱。并且LYK想知道有多少携带金币的方案总数。
具体可以看样例。

输入格式(dream.in)

第一行一个数 n ,如题意所示。

输出格式(dream.out)

输出两个数,第一个数表示LYK至少携带的金币个数,第二数表示方案总数。

输入样例

6

输出样例

3 2

样例解释

LYK需要至少带 3 枚金币,有两种方案,分别是 {1,2,3} {1,2,4} 来恰好得到任意的 1n 之间的 x

输入样例2

10

输出样例2

4 8

数据范围

对于 30% 的数据 n10
对于 60% 的数据 n100
对于 100% 的数据 n1000


solution

  • f[i][j][k] 表示拿了 i 个金币,金币的和是 j,金币的最大值是 k 的方案数

  • 那么考虑下一次会拿多大面值的金币,显然有

  • f[i+1][min(n,j+l)][l]+=f[i][j][k](k<lj+1)

  • 但是这样开不下数组,所以滚动数组优化一下

code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

#define MAXN 1010

int f[2][MAXN][MAXN];

int main() {
    freopen("dream.in","r",stdin);
    freopen("dream.out","w",stdout);
    int n;
    input(n);
    int Least=0;
    for(int i=1;i<=n;i<<=1)
        Least++;
    f[0][1][1]=1;
    for(int i=1;i<Least;i++) {
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                if(f[0][j][k])
                    for(int l=k+1;l<=j+1;l++)
                        f[1][min(n,j+l)][l]+=f[0][j][k];
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                f[0][j][k]=f[1][j][k],f[1][j][k]=0;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans+=f[0][n][i];
    printf("%d %d\n",Least,ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

3.动态规划(dp)

Time Limit:

1000ms

Memory Limit:

128MB

题目描述

LYK在学习dp,有一天它看到了一道关于dp的题目。
这个题目是这个样子的:一开始有 n 个数,一段区间的价值为这段区间相同的数的对数。我们想把这 n 个数切成恰好 k 段区间。之后这 n 个数的价值为这 k 段区间的价值和。我们想让最终这 n 个数的价值和尽可能少。
例如 6 个数 1,1,2,2,3,3 要切成 3 段,一个好方法是切成 [1],[1,2],[2,3,3],这样只有第三个区间有 1 的价值。因此这 6 个数的价值为 1
LYK并不会做,丢给了你。

输入格式(dp.in)

第一行两个数n,k
接下来一行 n 个数 ai 表示这 n 个数。

输出格式(dp.out)

一个数表示答案。

输入样例

10 2
1 2 1 2 1 2 1 2 1 2

输出样例

8

数据范围

对于 30% 的数据 n10
对于 60% 的数据 n1000
对于 100% 的数据 1n100000,1kmin(n,20),1ain
其中有 30% 的数据满足 ai 完全相同均匀分布在所有数据中。


solution

  • Codeforces 原题,等我学会1D1D动态规划优化会回来填坑的

code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

#define MAXN 100010

int p,q,n,k;
int c[MAXN],a[MAXN];

ll tot;
ll f[MAXN],g[MAXN];

void move(int l,int r) {  // [p,q]之前的区间 
    while (l<p) p--,tot+=c[a[p]],c[a[p]]++;
    while (r>q) q++,tot+=c[a[q]],c[a[q]]++;
    while (p<l) c[a[p]]--,tot-=c[a[p]],p++;
    while (r<q) c[a[q]]--,tot-=c[a[q]],q--;
}

void work(int l,int r,int fl,int fr) {
//需要求dp[fl] ~ dp[fr]  最优解一定从l~r中的某一个转移过来
    if(fl>fr) return;
    int mid=fl+fr>>1,mi;
    ll mx=1ll<<60;
    for(int i=l;i<=r;i++)
        if(i<mid) {
            move(i+1,mid);
            if(f[i]+tot<mx)
                mx=f[i]+tot,mi=i;
        }
    g[mid]=mx;
    work(l,mi,fl,mid-1);
    work(mi,r,mid+1,fr);
    return;
}

int main() {
    freopen("dp.in","r",stdin);
    freopen("dp.out","w",stdout);
    input(n),input(k);
    for(int i=1;i<=n;i++)
        input(a[i]);
    for(int i=1;i<=n;i++)
        f[i]=1ll<<60;
    while(k--) {
        p=1,q=0,tot=0;
        for(int i=1;i<=n;i++)
            c[i]=0;
        work(0,n-1,1,n);
        for(int i=0;i<=n;i++)
            f[i]=g[i],g[i]=0;
    }
    printf("%I64d",f[n]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值