2022“杭电杯”中国大学生算法设计超级联赛(4)
1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | |
赛时过题 | O | O | O | O | O | ||||||
赛后补题 | 待补 | 待补 | 待补 |
赛后总结:
没想到到最后还是没调出1011来。。。
比赛最后一小时我写了1011的暴力,企图从中找到一些规律
实际上规律是找到了,但是不确定规律对不对,而且线性基又打假了,就一直在怀疑结论对不对。。。
其实应该勇敢点相信猜的结论是对的,实际上题目给的数列性质就是为了保证这个结论正确的,是板子写错了,要不然不能解释怎么这么多人过1011
说起来我也不太能理解为啥1007这么多人过,这个贪心好像不好证吧。。
1004也是猜结论,今天全部是猜结论,全都不会证,有点麻
时间利用率还是不太行,1007和1011卡太久了,否则还能尽可能去写一下1003和1005
今天这场几乎是结论场,太多结论要猜了,我在这方面还是太薄弱
排名情况:
6题末尾:189名
7题末尾:126名
8题末尾:80名
9题末尾:38名
1004 Link with Equilateral Triangle
题目难度:check-in
题目大意:一个边长为n的大三角形,内部有n2个边长为1的小三角形。为每个顶点填入0/1/2,问是否有一种方案能满足一下条件:
1、大三角形左边的顶点不可为0;大三角形右边的顶点不可为1;大三角形底边的顶点不可为2;
2、没有一个小三角形的三个顶点的数字之和是3的倍数
解题分析:
比赛不到十分钟就有几百个人过了,因此我们猜这是一道结论题,并由于手动构造不出解我们就猜全输出No,过了。
证明:(没看懂,直接抄的题解)
对于一个合法的解,应当满足不存在同时包含0,1,2的三角形,下面我们证明这样的三角形一定存在。
左下角必然是1,右下角必然是0,底边不能含有2,则底边上必然有奇数条1-0的边,这些边都属于一个
小三角形。考虑其他的0-1边,由于不在两个斜边上,其他的0-1边必然属于两个三角形。因此“每个三角
形内0-1边的数量”的和必然为奇数。
但是,假设不存在0-1-2的三角形,则所有三角形都必然包含0条或2条的0-1边,产生了矛盾。
因此一定存在0-1-2的三角形。
参考代码:
查看代码
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, k, T;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> n;
puts("No");
}
}
1007 Climb Stairs
题目难度:medium-easy ( 如果直接猜出结论了那就是easy题,但是证明结论的难度是medium的)
题目大意:一个建筑物中有n层,每一层有一个怪物,怪物血量为ai,打败他后可以增加ai的攻击力。
人物一开始在第0层,初始攻击力为a0。每次人物可以选择往上跳1,2,...,k层,也可往下走一层,前提是这一层未访问过且这一层的怪物血量ai<=当前人物攻击力。
T组数据,每次询问人物是否能够不重不漏打败所有怪物?
数据范围:
1<=n,k<=1e5,1<=a0,ai<=1e9,∑n<=1e6
解题分析:
有三个重要结论:
一:首先假设人物在x层,那么为了打败所有怪物,可以确定人物一定是先跳到r,再回过来跳r-1,r-2,...,x+1,然后再往后跳。
否则假设先跳到r1,再跳到r2,然后再往回跳,最多跳到r1+1,r1-1就调不到了(不可访问已访问层)。
二:在力量值相同(即访问的层数相同)的情况下,当前位置越后面越好
设当前已访问了r层,当前在x(x<=r)层。那么x越大可选择跳的层数越多,那必然是更优的。
三:当前人物在x层,有多个r满足从x跳到r再回到x+1都合法,那么最近的那个点最优
设有两个合法的r1、r2,且r1<r2
1、
情况一:假设我现在先跳r1,然后回到x+1,由于此时的力量值肯定比在x的时候大,故肯定可以跳r2。此时的位置为r1+1,拿到了x+1~r2的所有力量值。
情况二:假如我现在先跳r2,那么必然会回到x+1。此时的位置为x+1,拿到了x+1~r2的所有力量值。
由于r1+1>x+1,根据结论二,跳r1肯定更优。
2、
情况一:假设我现在先跳r1,然后回到x+1,我会从x+1+1~x+1+k中选择合法的跳(此时x+1~r1已被访问),然后再回,一定会到达r1+1。
情况二:假设我现在先跳r2,然后回到x+1,我会从x+1+1~x+1+k中选择合法的跳(此时x+1~r2已被访问),此时虽然我的力量比情况一大,故r2+1~x+1+k中情况一可以跳的层我都可以跳,但是情况一中还可以跳到r1+1~r2中的合法位置,情况二却完全跳不了了。
比如:k=2,a0=1, a[]=1 1 5 3 一开始一定要跳最近的不能跳最远的
综上,跳r1一定比跳r2更优,先跳r2可能会导致原本可达变成不可达。更进一步,每次应该要跳最近的点。
考虑如何知道x+1~x+k哪个层是最近的合法的r?如何加速这个过程?
先尝试跳到x+1层,如果合法那就直接跳;如果不行,那就记录下来我如果想拿下x+1层还需要多少力量值power_needed,然后尝试跳到x+2层。
如果当前力量值够跳到x+2层:
如果x+2层的力量值>=power_needed,则直接跳到x+2层;如果不行就让power_needed减去x+2层的力量值,然后尝试x+3层。
如果当前力量值不够跳到x+2层,那就先让power_needed减去x+2层的力量值,再和x+2层所需的力量值取一个max,然后尝试x+3层。
以此类推....复杂度O(n),比标程还好hhh
赛后总结:
我对结论的敏感性还是不够啊,像这种题一般都是结论题,得想办法证明r1、r2选哪个不会更劣。
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+1000;
long long a[N],sum[N];
int main()
{
int T;scanf("%d",&T);
while (T--)
{
int pos=0,last_vis=0,n,k;long long now_power=0,power_needed=0;
scanf("%d%lld%d",&n,&now_power,&k);
For(i,1,n) scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
while (last_vis<n)
{
int flag=0;
For(i,last_vis+1,std::min(pos+k,n))
{
if (now_power>=a[i])
{
if (a[i]>=power_needed)
{
now_power+=sum[i]-sum[last_vis];
power_needed=0;
pos=last_vis+1;
last_vis=i;
flag=1;
}
else power_needed-=a[i];
}
else
{
power_needed-=a[i];
power_needed=std::max(power_needed,a[i]-now_power);
}
}
if (!flag) {printf("NO\n");break;}
}
if (last_vis==n) printf("YES\n");
}
return 0;
}
1011 Link is as bear
题目难度:medium-easy
题目大意:给定一个n个元素的数列a[1...n]。
每次操作可以选择一个区间[l,r],将[l,r]中的所有数替换为 xor(l,r)(xor(l,r)即[l,r]中的所有数的异或和)。
可以进行任意次操作,要求最终将所有数变成同一个数。询问最后变成的这个数最大为多少?
赛时经历:
比赛过去没多久的时候这题就过了好多人了,到后来这题的人数越过越多,最终过了500人+.....
那么显然,这题一定是一道结论题,而且大概率是一道比较模板的结论题。
但是到了比赛后期我们还是完全没思路,于是我提出我写个暴力。。。
写完暴力后发现可以对一段长度为偶数的区间操作2次将其置零,更进一步地,长度>=2的区间都可以靠两个两个置零最终达到全部置零。
然后我猜这题的结论是可以转化为任意多个数的最大异或和,宇彬也是猜这个结论。
唯一一个不太理解的地方是题目中给的条件“一定有两个数相同”有什么用,是为了保证长度为1的区间也能够置零吗?
宇彬写完以后和我的暴力拍了一下,发现基本都能对得上,不过如果没有2个数相同就对不上,那就让他先交一发,反正也没思路,然后就WA了。
宇彬还没意识到自己写的是线性基,纯凭印象,我们也没往板子题想。。。于是就怀疑是结论推错了
而且和暴力进行对拍的时候没有拍大数据。。。导致没发现宇彬写的程序实际是对的,只是1LL<<i写成了1<<i。。。
以后测试程序的时候一定要测:①边界小数据的正确性②边界大数据的时间复杂度③n很小,数字也很小(方便手动验算)④n很小,数字很大(方便暴力程序能及时跑出来)
标答解题思路:
是个思维+结论题,可以证明,从这些数里任取一些数异或起来的方案,都是可以构造出对应的操作来做到的。
所以,问题完全等价于给n个数,从中选一些数,使得这些数的异或和最大。
这是线性基的板题,抄一个板子即可。
证明:
1、①如果序列中有存在a[i]=0,a[i+1]=0,那么这个0 0可以平移到任何地方,如0 0 a b-> a a a b-> a 0 0 b -> a b b b->a b 0 0
②又这个0 0可以让周围的某个数清零:0 0 a-> 0 a a-> 0 0 0
因此,如果序列中有连续的两个0,那么任意一种选数方案都能通过操作①②构造出来。
2、如果序列中有两个连续的数字要保留,即形如a b c ,要保留a b删去c,那么a,b,c -> a^b,a^b,c -> a^b ,a^b^c,a^b^c -> a^b,0,0 这样就能得到两个连续0了。
3、如果序列中有两个连续的数字要删去,即形如a b c,要保留a 删去b c,那么a,b,c ->a,b^c,b^c -> a,0,0 这样就能得到两个连续0了。
4、如果数字中没有两个连续的数字要保留,即 保 删 保 删 保 删 保 删 保 删 保 删 保 删 保 删...
那么考虑题目给定的那一对相同数x:
如果答案中需要异或x,则在序列中这两个位置的数一定是这个保那个删,那么改成这个删那个保,就一定能出现2和3的情况了。
如果答案不需要异或x,则在序列中这两个位置的数一定是都删(都保),那么改成都保(都删),就一定能出现2或3的情况了。
补充知识:
线性基实际上是用一组基 来代替一组数,从而保证“任取一些数的异或和”的值域不变。
线性基本质上是利用了异或的一个性质:如果给定a,b,c且c=a^b,那么任意两个数都可以推出第三个数。(a^b=c,b^c=a,a^c=b)
参考代码:
查看代码
//需要注意2个位置的1LL!!
#include<iostream>
#include<cstdio>
unsigned long long e[100];//代表一组线性基,e[i]代表一个 最高位的1位于第i位的 数。
int queryk()
{
}
int main()
{
int T;scanf("%d",&T);
while (T--)
{
int n;scanf("%d",&n);
for(int i=63;i>=0;i--) e[i]=0;
for(int i=1;i<=n;i++)//构造线性基
{
unsigned long long x;
scanf("%llu",&x);
for(int i=63;i>=0;i--) if (x&(1LL<<i)) //每次考虑最高位的1
{
if (!e[i]) //线性基在这个位置还没有数,直接插入线性基
{
e[i]=x;
break;
}
x^=e[i];//线性基在这个位置有数了,根据异或和性质,插入x和插入x^e[i]对最终值域没有任何影响。
//如果某个方案需要异或上x,那么改成异或上(x^e[i])^e[i]即可
}//如果x变成0了还没被插入,就说明这个数已经可以被其他数字表示出来了,可以丢掉了。
}//最终构造的线性基e[i]可以保证e[i]的最高位是第i位(如果e[i]!=0的话),且值域不变
unsigned long long ans=0;
for(int i=63;i>=0;i--) if ((ans&(1LL<<i))==0) ans^=e[i];
//贪心,如果e[i]=0对答案无影响。如果ans在第i位是0,那么他异或上e[i]会使答案变优;如果ans在第i位是1,那么他异或e[i]会变劣。
//那么显然,从高位到地位,只要这一位是0,就异或e[i],答案一定最优。
printf("%llu\n",ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~