test20190815 NOIP2019 模拟题
100+60+40=200,被后面两个题卡着我很不爽。
立方数
【问题描述】
作为 XX 战队的狂热粉丝,MdZzZZ 看到了自己心仪的队伍在半决赛落败,顿时 心灰意冷。看着自己手中的从黄牛那里抢来的天价总决赛门票,MdZzZZ 觉得去鸟 巢已经没有意义了,于是他决定去跳“水立方”。在他准备进“水立方”体育馆 时,一位大妈拦住了他的去路,并产生了一下对话:
大妈:“年轻人我看你印堂发黑,恕我冒昧直言,此去一行怕是会有什么不测。”
MdZzZZ:“大妈别拦我,我要跳水立方发泄一下!”
大妈:“年轻人,做事要三思而后行,你知道这水立方最著名的是什么吗?”
MdZzZZ:“不知...”
大妈:“这水立方最有名的是‘立方数’!”
MdZzZZ:“哦?”
大妈:“别急,听我细细道来。‘立方数’就是,如果一个数可以被写作是一个正整数的 3 次方,则这个数就是立方数。例如 1,8,27 就是最小的 3 个立方 数。”
MdZzZZ:“……”
大妈:“当然,想要在这水立方中来去自如,你需要知道‘立方差数’!”
大妈:“若一个数可以被写作是两个立方数的差,则这个数就是‘立方差数’,例如 7(8-1),26(27-1),19(27-8)都是立方差数。如果你能够判断随便一个数是不是‘立方差数’,那么你就可以真正地在这一片小天地中当一条无忧无虑的小鱼...”
未等 MdZzZZ 反应过来,大妈以飘然远去,留下他一个人在那边细细思索。那么现在你的问题来了,你需要帮助 MdZzZZ 解决这个问题。 现在给定一个数 P,MdZzZZ 想要知道这个数是不是立方差数。
当然你有可能随机输出一些莫名其妙的东西,因此 MdZzZZ 有 T 次询问~这个问题可能太难了…… 因此 MdZzZZ 规定 P 是个质数!
【输入格式】
输入文件名为 cubicp.in。 第一
行一个数 T,表示有 T 组数据。
接下来 T 行,每行一个数 P。
【输出格式】
输出文件名为 cubicp.out。 输出 T 行,对于每个数如果是立方差数,输出“YES”,否则输出“NO”。
【输入输出样例 1】
cubicp.in
5
2
3
5
7
11
cubicp.out
NO
NO
NO
YES
NO
【数据规模与约定】
对于 30%的数据 p<=100。
对于 60%的数据 p<=10^6。
对于 100%的数据 p<=10^12,T<=100。
noip2018模拟赛 立方数2
int main(){
freopen("cubicp.in","r",stdin),freopen("cubicp.out","w",stdout);
for(int t=read<int>();t--;){
LL p=read<LL>();
int a=(3+sqrt(12*p-3))/6;
puts(p==3LL*a*a-3*a+1?"YES":"NO");
}
return 0;
}
动态规划
【问题描述】
MdZzZZ 在悟得人生终极奥义之后,决定去西藏净化一下自己的灵魂。正当他欣赏沿途的风景之时,突然有一大片羊群闯入了他的视线。
这时 MdZzZZ 想到了一 个问题,问题是这样的:假如羊群一共有 n 只羊,把它们排成一条直线,并且给 每个羊标上一个价值。定义一段区间的价值为这段区间相同的数的对数。MdZzZZ 想要将这个羊群分成恰好 k 段区间,之后计算这 n 只羊的价值为这 k 段区间的价值 和。MdZzZZ 现在想让最终这 n 只羊的价值和尽可能少。
例如 6 只羊的价值分别为 1,1,2,2,3,3,现在要将他们切成 3 段。MdZzZZ 立马 想到了一个好方法,分成[1],[1,2],[2,3,3],这样只有第三个区间有 1 的价值。 因此这 6 只羊的价值和为 1。当 MdZzZZ 的思维探索到更多只羊的时候,他不禁陷入 了沉思...然后 MdZzZZ 深深沉迷在思考的世界里面,并且睡着了。
然而美好的时光总是那么短暂,马上天就要黑了。MdZzZZ 必须要在天黑之前找到旅馆并且住下来。你现在需要帮助 MdZzZZ 解决这个问题,并且尽快把他叫醒。当然你要先解决问题再叫醒他,不然 MdZzZZ 可能会带着起床气一直不肯离开。
【输入格式】
输入文件名为 dp.in。
第一行两个数 n,k。
接下来一行 n 个数 ai 表示这 n 个数。
【输出格式】
输出文件名为 dp.out。
一个数表示答案。
【输入输出样例 1】
dp.in
10 2
1 2 1 2 1 2 1 2 1 2
dp.out
8
【数据规模与约定】
对于 30%的数据 n<=10。
对于 60%的数据 n<=1000。
对于 100%的数据 1<=n<=100000,1<=k<=min(n,20),1<=ai<=n。
其中有 30%的数据满足 ai 完全相同均匀分布在所有数据中。
CF868F Yet Another Minimization Problem
先写出DP方程
因为\(k\)很小,所以用不着二分导数斜率。考虑后面那个\(w\)函数,它满足四边形不等式。
这个很直观,考虑那个 包含≥交叉 的四条线段的图像,发现你找不出来反例。
尝试证明\(\forall a<b\)
移项得到
而下面那个差式相当于\([a,b]\)和\([a+1,b]\)向右拓展一格的增量,而这个大小关系很显然。
由于\(w\)的贡献无法快速计算,所以采用值域分治做法。
设当前的求解区间为\([l,r]\),最优决策区间\([L,R]\)。对于当前分治的中点\(mid\),我们需要在\([L,\min(R,mid)]\)中暴力找到最优决策\(k\)。注意到从\(w(l,r)\)到\(w(l,r+1)\)或者从\(w(l,r)\)到\(w(l+1,r)\)都是可以做到\(O(1)\)的,只要开一个桶记录当前区间每个颜色出现次数就可以啦。把指针\(i\)从\(L\)移到\(\min(R,mid)\)并不断的算\(f(i)+w(i,mid)\),最终可以找到\(k\)。
注意一点,当进入求解区间时,我们的应该要确保\([L,l−1]\)的信息的存在,这样才能保证分治的复杂度。
于是我们考虑进子问题之前如何先处理出子问题的答案。先看左边的子问题(\([l,mid−1],[L,k]\))显然和当前问题的\([L,l−1]\)是一样的。注意到我们在求\(k\)的时候对\(w\)和桶都做了修改,那么我们直接还原回来就可以进左子问题了。
而右子问题呢?(\([mid+1,r],[k,R]\))它要预处理的是\([k,mid]\),而当前的是\([L,l−1]\)。所以我们先把右端点指针从\(l−1\)移到\(mid\),桶和\(w\)都加上去,再把左端点从\(L\)移到\(k\),桶和\(w\)都减掉,接着进去就好了。回溯的时候还是要还原到\([L,l−1]\),因为上一层要接着用。
配上LaTeX画图???
求解区间:\(|\gets预处理\to| l\frac{\qquad\qquad\qquad\downarrow^{mid}\qquad\qquad\qquad}{}r\)
决策区间:\(L\frac{\qquad\qquad\qquad\qquad\downarrow^{k}\qquad\qquad\qquad}{}R\)
时间复杂度\(O(nk \log n)\)。
考试的时候还是太naive了,一心想着找出\(w\)的表达式再来看单调性什么的。没想到可以直接从含义来看……另外我也没有尝试打表。感觉我可能需要掌握更多考试猜想技巧。
co int N=100000+10;
int n,k,a[N],b[N];
LL f[N],g[N];
void solve(int l,int r,int L,int R,LL w){
if(l>r) return;
int mid=(l+r)>>1,p=min(mid,R),k=0;
for(int i=l;i<=mid;++i) w+=b[a[i]]++;
for(int i=L;i<=p;++i) w-=--b[a[i]],f[mid]>g[i]+w?f[mid]=g[i]+w,k=i:0;
for(int i=l;i<=mid;++i) w-=--b[a[i]];
for(int i=L;i<=p;++i) w+=b[a[i]]++;
solve(l,mid-1,L,k,w);
for(int i=L;i<k;++i) w-=--b[a[i]];
for(int i=l;i<=mid;++i) w+=b[a[i]]++;
solve(mid+1,r,k,R,w);
for(int i=l;i<=mid;++i) --b[a[i]];
for(int i=L;i<k;++i) ++b[a[i]];
}
int main(){
freopen("dp.in","r",stdin),freopen("dp.out","w",stdout);
read(n),read(k);
for(int i=1;i<=n;++i)
g[i]=g[i-1]+b[read(a[i])]++;
memset(b,0,sizeof b);
while(k--){
memset(f,0x3f,sizeof f);
solve(1,n,1,n,0);
swap(f,g);
}
printf("%lld\n",f[n]);
return 0;
}
游戏
【问题描述】
MdZzZZ 最近十分的苦恼,因为他的好朋友都去玩 Pokemon Go!了,而他向来对这一类的游戏不屑一顾,因此每天都没有人来找他玩。于是 MdZzZZ 自己想了一 个新的游戏,他自豪地将这游戏取名为猜数字游戏。
游戏规则是这样的:一共有 n 个互不相同的正整数,MdZzZZ 可以每次猜一段 区间的最小值。例如对于某段区间[li,ri]一定会有一个最小值。
于是他开心的邀请了他的好友 WdZzZZ 来玩这个游戏。令 MdZzZZ 自豪的是,在这个游戏中,他总能构造出一种方案使得 WdZzZZ 满意,直到……WdZzZZ 自己猜的就是矛盾的!例如 WdZzZZ 猜[1,5]的最小值是 2,[1,6]的最小值是 3,这显然就是矛盾的。
现在你需要帮助 MdZzZZ 判断,在 WdZzZZ 第几次猜数字开始就已经矛盾了。
【输入格式】
输入文件名为 number.in。 第一行两个数 n 和 T,
表示有 n 个数字,LYK 猜了 T 次。
接下来 T 行,每行三个数分别表示 li,ri 和 xi。
【输出格式】
输出文件名为 number.out。 输出一个数表示第几次开始出现矛盾,
如果一直没出现矛盾输出 T+1。
【输入输出样例 1】
number.in
20 4
1 10 7
5 19 7
3 12 8
1 20 1
number.out
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】 建议使用读入优化
ChiTongZ的题解
直接说正解,将操作离线,然后二分到哪一个操作就挂了,对于当前区间内的操作,把它排序,按照第三权值从大到小。
考虑哪一些情况是不行的,首先是一个数出现在了两个不相交的区间,其次是权值大的区间覆盖了权值小的区间,然后就没有然后了。
注意第二种情况要取交集,就是所有权值为当前权值的区间的交集。
那么对于第一种情况,直接 lmax, rmin 维护出交集就可以了。if (lmax > rmin) return 挂了;
然后对于第二种情况,标称提供了一种极佳的并查集做法,就是并查集内维护每一个元素 i 延伸最右的点 p。这个是根据每种 x 最后的并集来更新的。
考虑到排序是从大到小的,若当前的交集为 [lmax,rmin] 并且(可以借助标程理解)find(lmax) > rmin
由于当前的权值尚未去更新并查集,所以只有之前的区间覆盖当前区间,而之前的区间权值一定更大,所以情况不合法,属于情况二。
时间复杂度\(O(T \log T)\)。
大概这道题的关键在于那些讨论,考试的时候我都没有去尝试骗分……没准就推出正解了。然后比较妙的就是按照权值从大到小来做(机房里讨论认为从小到大做),然后就是用并查集来维护并集。
co int N=1000000+10;
int n,T,fa[N];
struct node{int l,r,x;}p[N],e[N];
il bool operator<(co node&a,co node&b){
return a.x>b.x;
}
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
bool check(int k){
for(int i=1;i<=n+1;++i) fa[i]=i;
copy(e+1,e+k+1,p+1),sort(p+1,p+k+1);
int lmax=p[1].l,lmin=p[1].l,rmax=p[1].r,rmin=p[1].r;
for(int i=2;i<=k;++i){
if(p[i].x<p[i-1].x){
if(find(lmax)>rmin) return 0; // 交集被覆盖
for(int j=find(lmin);j<=rmax;++j) // 并集合并
fa[find(j)]=find(rmax+1);
lmin=lmax=p[i].l,rmin=rmax=p[i].r;
}
else{
lmin=min(lmin,p[i].l),lmax=max(lmax,p[i].l);
rmin=min(rmin,p[i].r),rmax=max(rmax,p[i].r);
if(find(lmax)>rmin) return 0;
}
}
if(find(lmax)>rmin) return 0;
return 1;
}
int main(){
freopen("number.in","r",stdin),freopen("number.out","w",stdout);
read(n),read(T);
for(int i=1;i<=T;++i) read(e[i].l),read(e[i].r),read(e[i].x);
int l=1,r=T;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
printf("%d\n",l+1);
return 0;
}