今天早上又考了一场。
现在题还没改完,先写一下考试路程。
我习惯性的先看完了三道题。
然后昨天教练还读了一下那个IOI选手的答题思路,就是先切第三题然后攻T2,我的话打算先把T3打个暴力就不管他,跑到考试结束连个6也没跑出来于是结束了。
然后我就开始写T1的暴力了。
看出来是欧拉路,但是我不会统计,然而用map判重一个傻逼重载运算符我卡了半天写不对,最后还是用了随机化hash的思想rand出了几百个数,挨个和边经过次数乘起来作为hash值这样的,然后判重过了,这道题就弃了开始干T2。
T2我的思路真的是一点一点来的。
T2是这次比较成功的一部分,虽然分数不是很理想(卡常不到位),我和天皇的暴力基本一样,但是他加了个clock然后70了。
说一下暴力的思路,我的期望得分是(80),但是这个数据稍微有点苟,大于1e5的数据全都是1e9的,导致我全都MLE。
1.首先狗掉了k==1和k==0的思路,k==0的话就直接GCD即可,k==1也同样是GCD,只需要枚举不同的集合GCD,然后判断不包含的那个是不是剪掉之后小于1,之后选出来最大的一个GCD即可。
2.在观察数据范围,1e5范围的值域其实可以暴力切掉,1e5*100暴力判断即可,到这里是40。
3.在接着qj不了数据范围了。
但是我找到了一个性质:如果一个数d不符合要求,那么他的倍数也不符合要求,这样我们就可以打个埃筛,筛掉很多后面的数。
复杂度变成了一个loglogn的,虽然还要判断,不过只需要判断质数也就是ln(n)的大小,虽然还是要循环O(n),但是判断只需要判断质数是否成立即可,因为前面的全部被他的最小质因子筛掉了,线筛会更优秀一些,基本接近线性了,所以我估分到了80,虽然是50,但是收获还是蛮大的。
正解是数论分块,学反演的时候学过一次,同样和筛是一起学的,不过我对筛的认识还是比分快清晰一些,这样考场上打了筛而不是分块。
好了现在可以写一下题解了。
第一题的话,基本没怎么想正解,不过欧拉路那个的确想到了,没接着想,打了个暴力就过了,这个决定挺正确的,因为我后来看了正解之后,也想了好久才写了30分。
首先可以把每条边全都分为正反向两条边,这样每个点的度数都是偶数了,这些有向边的话随便去除两条就可以达到效果,即其中两条是只走一次。
那么剩下来的那些边必然也要保证是一条欧拉回路,也就是说,删的边有所要求,符合要求方案就是ans。
三种情况:任意两个自环被删掉,任意自环和一条普通边被删掉(从普通边出发即可),两条有共同端点的边(保证奇点个数为偶)。
这样计算一次出答案,特判掉虫洞不联通的情况。
第二题,暴力的思路基本被我挖干净了,直接正解就行了。
数论分块的一个小板子,推一下式子吧。
$ sum limits_{i=1}^n (left lceil frac{a_i}{d} ight ceil d-a_i) leq k $
$ d sum limits_{i=1}^n left lceil frac{a_i}{d}
ight
ceil leq k+sum limits_{i=1}^n a_i $
$
sum limits_{i=1}^n left lceil frac{a_i}{d}
ight
ceil leqslant
left lfloor frac{k+ sum limits_{i=1}^n a_i }{d}
ight
floor $
发现右边是个数论分块的裸板子,可以$ O(sqrt{n}) $处理。
那么来证明一下数论分块的正确性(之前听学长讲反演的时候听过),但是一直没有试着自己证明一下来的。
现在来试一试。
结论是:满足
$ frac{n}{last} = frac{n}{i} $ 的最大的last的值是 $ left lfloor
frac{n}{left lfloor frac{n}{i}
ight
floor }
ight
floor $
那么证明:
$ last=i+d $
$ ki+p=n $
$ k(i+d) + p'= n $
$ p' = p-kd $
$ d_{max} = left lfloor frac{(p-p')}{k} ight floor = left lfloor frac{p}{k} ight floor $
$ last = i+d =i+ left lfloor frac{p}{k} ight floor $
$ last = i+d =i+ left lfloor frac{n- left lfloor frac{n}{i} ight floor i }{ left lfloor frac{n}{i} ight floor } ight floor $
$ last = i+d = left lfloor i+ frac{n- left lfloor frac{n}{i} ight floor i }{ left lfloor frac{n}{i} ight floor } ight floor $
$ last = i+d = left lfloor frac{n}{ left lfloor frac{n}{i} ight floor } ight floor $
得证了。
那么这题可以分块根号n解决了。
还有一个优化。
我们思考一下d的上界,如果d减去最小的 $ a_i $ 大于k,显然不成立,那么分块上界可以缩小到了 $ d_{min}=a_{min}+k $这样这题基本挖光了。
下面是第三题,一个神仙dp。
dp本身没什么说的,神就是神,思路也神,设的也神,sdfz的神犇全都打了正解也神。
虽然本身神的我无话可说但是还是有一些可以吸收的营养。
另num=dp[i-1][l]*dp[i-1][r]
分情况:
1.什么都不做:dp[i][l+r]+=num
2.新根作一条:dp[i][l+r+1]+=num
3.根和两边的连成一条:dp[i][l+r-1]+=2*num*l*r
4.根和两边的某一条连:dp[i][l+r]+=2*num*(l+r)
5.根和两边(同一侧)的两条连起来:dp[i][l+r-1]+=num*(l*(l-1)+r*(r-1))
下面说一下我的理解。
首先dp是一种设计。你需要用你的决策方程来维护你设计的dp状态,使得终阶段的答案状态是正确,满足要求的。
那么看一下这五个方程分别满足了原题的什么性质。
1.满足继承性,我的子树里所拥有的方案我也要拥有。
2.满足扩展性,新来的根要扩展出。
3.满足融合性,我们考虑把新根融入上一阶段的状态和决策中。
4.满足有序性,我正着反着连到同一条边上,是不同的方案。
5.满足新增性,也就是说多了我这一个点,上一阶段的某一些状态也会在这一阶段作出改变。
满足这些性质,这个dp虽然神的要死。
但终归是令人惊叹但正确无误的。
关于题目的加速度。
前几天的时候刷题的速度有点快,我自己也感觉到不太对劲了。但是我觉得对面那个D队的做题实在太快了,我不敢松懈,我觉得在自家OJ让人虐让人打脸我很不爽,我真不爽。
然后就刻意提高速度,说实话这个过程很难受,感觉就是强行加速一样,那几天身体状况都不太好了起来,我觉得可能我那时候多半心态有一些问题。刻意提速是有问题的,当时可能是很受打击之类反正有一种情绪在里面,情绪这个东西控制不好就会炸。当时实在是心急了,现在想想教练说的挺对的,自己不要被别人的速度所干扰,不要因为别人比你强就是强行追赶他,这样下去你的速度被干扰了,最后肯定会垮掉,如果真的你用自己的速度到最后赶不上他那就认了就行了,因为你尽了全力还是不如他,就承认不如他就可以了,这也没什么丢人的。说的挺有道理的。
压力这个是必须要有的,但是加速度必须是恒定的,也就是说你的进步速度必须是恒定的,不被外界干扰,不被情绪干扰,可以有情绪,但不能被他干扰,这很重要,这样会进步,而且会很快很稳。
不要贪大贪多,能力多少就一次吃多少,不图快,图稳,扎实,这样后期一定会进步飞快。
什么不行就要练什么,现在对于那个第一机房还是第二机房我觉得应该淡然一点,如果你的考试能力足够了,就去学知识,如果不够的话,那么就去考试就行了,两方面都很重要,教练分给你的一定是当前最适合你的一种训练方式。
sdfz那边的确很强,这会让很多人有压力,学长那届也比我们强,但是最后我们不一定就会差。
竞赛这个东西,毕竟不学到最后,是不会知道结果的。
那今天就这么多。
//T1
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,m,x,y,tot,cnt,first[maxn],vis[maxn],d[maxn];
ll ans,sum;
struct Road{
int u,t,nxt;
}eage[maxn<<1];
void add(int x,int y)
{
eage[++tot].u=x;
eage[tot].t=y;
eage[tot].nxt=first[x];
first[x]=tot;
}
void dfs(int x)
{
vis[x]=1;
for(int i=first[x];i;i=eage[i].nxt,cnt++)
if(vis[eage[i].t]==0)
dfs(eage[i].t);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
if(x==y) sum++;
if(x!=y) d[x]++,d[y]++;
}
for(int i=1;i<=n;i++)
if(d[i])
{
dfs(i);
break;
}
if(cnt<m*2)
{
puts("0");
return 0;
}
for(int i=1;i<=n;i++) ans+=1LL*d[i]*(d[i]-1)/2;
ans+=1LL*(m-sum)*sum;
ans+=1LL*sum*(sum-1)/2;
printf("%lld
",ans);
return 0;
}
//T2
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=105;
ll ans,res,sum,n,k,a[maxn];
int main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum+=a[i];
}
sum+=k;
for(ll i=1,pre;i<=sum;i=pre+1)
{
pre=sum/(sum/i);res=0;
for(int j=1;j<=n;j++)
{
ll d=(a[j]-1)/pre+1;
res+=pre*d;
}
if(res<=sum) ans=pre;
}
printf("%lld
",ans);
return 0;
}
//T3
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=305;
ll ans,n,m,dp[maxn][maxn];
int main()
{
scanf("%lld%lld",&n,&m);
dp[1][0]=dp[1][1]=1;
const int mod=m;
for(register int i=1;i<=n-1;++i)
{
int t=(i>=20?n-i+1:min((ll)(1<<i)-1,n-i+1));
for(register int l=0;l<=t;++l)
for(register int r=0;r<=t&&l+r-1<=n-i+1;++r)
{
ll sum=dp[i][l]*dp[i][r]%mod;
dp[i+1][l+r]=(dp[i+1][l+r]+sum)%mod;
dp[i+1][l+r+1]=(dp[i+1][l+r+1]+sum)%mod;
dp[i+