Round 1
1.立方数(cubic)
Time Limit:
1000ms
Memory Limit:
128MB
题目描述
LYK定义了一个数叫“立方数”,若一个数可以被写作是一个正整数的
3
次方,则这个数就是立方数,例如
现在给定一个数
当然你有可能随机输出一些莫名其妙的东西来骗分,因此LYK有
T
次询问~
输入格式(cubic.in)
第一行一个数
接下来
输出格式(cubic.out)
输出
输入样例
3
8
27
28
输出样例
YES
YES
NO
数据范围
对于
30%
的数据
p≤100
对于
60%
的数据
p≤106
对于
100%
的数据
p≤1018,T≤100
solution
- 1018−−−−√3=106 ,直接枚举 1−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;
}
#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
次方,则这个数就是立方数,例如
LYK还定义了一个数叫“立方差数”,若一个数可以被写作是两个立方数的差,则这个数就是“立方差数”,例如
现在给定一个数
P
,LYK想要知道这个数是不是立方差数。
当然你有可能随机输出一些莫名其妙的东西,因此LYK有
这个问题可能太难了…… 因此LYK规定
P
是个质数!
输入格式(cubicp.in)
第一行一个数
接下来
输出格式(cubicp.out)
输出
输入样例
5
2
3
5
7
11
输出样例
NO
NO
NO
YES
NO
数据范围
对于
30%
的数据
p≤100
对于
60%
的数据
p≤106
对于
100%
的数据
p≤1012,T≤100
solution
考虑立方差公式 a3−b3=(a−b)(a2+ab+b2)
假设 p 是立方差数,那么
p=(a−b)(a2+ab+b2) 又因为 p 是素数,所以
a−b=1 ,否则 p 就不是素数所以
p=a2+ab+b2 a−b=1⇒b=a−1 ,把 b 代入就可以得到
p=a2+a(a−1)+(a−1)2=3a2−3a+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每次猜一段区间的最小值。形如
我们总能构造出一种方案使得LYK满意。直到…… LYK自己猜的就是矛盾的!
例如LYK猜
[1,3]
的最小值是
2
,
你需要告诉LYK,它第几次猜数字开始就已经矛盾了。
输入格式(number.in)
第一行两个数
接下来
输出格式(number.out)
输出一个数表示第几次开始出现矛盾,如果一直没出现矛盾输出 T+1 。
输入样例
20 4
1 10 7
5 19 7
3 12 8
1 20 1
输出样例
3
数据范围
对于
50%
的数据
n≤8,T≤10
。
对于
80%
的数据
n≤1000,T≤1000
。
对于
100%
的数据
1≤n,T≤1000000,1≤li≤ri≤n,1≤xi≤n
(但并不保证一开始的所有数都是
1−n
的)。
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,mid−1] 肯定也可行
如果 [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 ,覆盖一个区间就把这个区间的最小值全部设为 1nlognα(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出了道水题。
这个水题是这样的:有两副牌,每副牌都有
对于第一副牌的每张牌长和宽分别是
xi
和
yi
。对于第二副牌的每张牌长和宽分别是
aj
和
bj
。第一副牌的第
i
张牌能覆盖第二副牌的第j张牌当且仅当
LYK想让两副牌的各
n
张一一对应叠起来。它想知道第二副牌最多有几张能被第一副牌所覆盖。
输入格式(water.in)
第一行一个数
接下来
n
行,每行两个数
接下来
n
行,每行两个数
输出格式(water.out)
输出一个数表示答案。
输入样例
3
2 3
5 7
6 8
4 1
2 5
3 4
输出样例
2
数据范围
对于
50%
的数据
n≤10
对于
80%
的数据
n≤1000
对于
100%
的数据
1≤n≤100000,1≤xi,yi,aj,bj≤109
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凑不出恰好
LYK知道这个
x
一定是
具体可以看样例。
输入格式(dream.in)
第一行一个数 n ,如题意所示。
输出格式(dream.out)
输出两个数,第一个数表示LYK至少携带的金币个数,第二数表示方案总数。
输入样例
6
输出样例
3 2
样例解释
LYK需要至少带
输入样例2
10
输出样例2
4 8
数据范围
对于
对于
60%
的数据
n≤100
对于
100%
的数据
n≤1000
solution
f[i][j][k] 表示拿了 i 个金币,金币的和是
j ,金币的最大值是 k 的方案数那么考虑下一次会拿多大面值的金币,显然有
f[i+1][min(n,j+l)][l]+=f[i][j][k](k<l≤j+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
个数,一段区间的价值为这段区间相同的数的对数。我们想把这
例如
6
个数
LYK并不会做,丢给了你。
输入格式(dp.in)
第一行两个数
接下来一行
n
个数
输出格式(dp.out)
一个数表示答案。
输入样例
10 2
1 2 1 2 1 2 1 2 1 2
输出样例
8
数据范围
对于
对于
60%
的数据
n≤1000
对于
100%
的数据
1≤n≤100000,1≤k≤min(n,20),1≤ai≤n
其中有
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;
}