HNOI2021游记
我是一只小黄鸭,我只是来划水的。感受到了被全方位吊打如老坛酸菜牛肉面般的酸爽。
考试的九个小时就像被ri了一样,我终于自食了颓废的恶果(到初三了还考不过初二的小朋友,丢不丢人)
\(\text{Day 1: }\) 噩梦的开始
-
T1,题面简洁而优美,而我却不会。脑子里闪过的全是“二分答案?”“wqs二分?”,然而想来想去二分答案是\(nlog^2\)的,不会wqs。无奈写了二分答案的暴力分,写了40,水到80
-
T2,恶心构造题,想了好久,期间一度以为解决,然后非常傻逼地把自己hack掉,最后20暴力跑路,甚至忘记去写\(m=2\)和01的部分分。
-
T3,想着时光倒流,不会维护强连通分量,无奈写了前四个点的暴力,莫名其妙也挂掉了,Day1成功成为全场最菜毋庸置疑。
Day1三道暴力分,还有一道连暴力都没写对。T1是sb题,我回家看题解看了几个字就会了,就是个普及组水平;T2考场上几乎退出来,结果嫌式子丑放弃了;T3没什么好说的。
听说同学人均切T1?听说高二学长人均200+?听说lk几乎AK?听说初二学弟人均100+?那我岂不是 真垫底了!没脸了没脸了
\(\text{Day 2: }\) 噩梦的高潮
-
T1,大概要树剖?那码量要上天!先写部分分,拿了\(nq\)和链的,由此连想到了\(nlog^2\)的树剖+倍增,最后40分钟没调出来,血亏60pts
-
T2,暴力的\(n!n\)好像有40pts耶,就这样吧。加了个小优化,并没有什么太大效果。数据较水,最后有60pts
-
T3,没有想法,暴力10pts溜了,最终得分20pts
Day2仍旧是三道暴力分,彻底没希望了(虽然好像就没有过)。还是我太菜,出来一问,有切T1的,有切T2的,那个切T1的问我:“T1你九点半还码不完吗?”我:“……wdnmd”
最后那个切T1的进E队了(恭喜恭喜),我几乎掉出省前50了(节哀)
反思这场考试,总结出以下几点原因跟改进方案:
-
我太菜了,菜是原罪(平日机房划水导致实力的急剧下滑甚至有一段时间没有摸过电脑是本次考试失利的根本原因)
-
我太保守了,不要拘泥于暴力,看到可做题应直接想正解,才不会导致没有深入挖掘性质以至于想不到正解或想到了正解却没时间码了。
-
我没有对拍,实际上是我太久没用linux不会对拍了。
想想明年就高一了,高二的学长们这次几乎都进了省队,我们进高中时他们也都是半闲的集训队爷了,重担还在我们和高一两届肩上。
初二我校这一届较弱,外校一位初二小朋友把高中生吊起来打,外校初三也不乏劲敌,何况我校自身竞争激烈,还要和同学学长们去抢前5,我更要发奋图强了。
我想这次的联合省选是给我们敲响了警钟, 是时候该搞搞学了。
由题,\(\{a_i\}\)单调递增,故只有可能翻首尾连续的两段,直接二分答案即可。
#include<bits/stdc++.h>
using namespace std;
#define inf 1e9+1
const int maxn=1e6+10;
const int mod=1e9+7;
int n,m,a[maxn],b[maxn],ans;
int pmin[maxn],pmax[maxn],smin[maxn],smax[maxn];
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
inline int check(int val){
for(int i=1,j=1;i<=n;i++){
while(j<=i&&a[i]-a[j]>val)j++;
int mn=min(a[j],min(pmin[j-1],smin[i+1]));
int mx=max(a[i],max(pmax[j-1],smax[i+1]));
if(mx-mn<=val&&i-j+1>=n-m)return 1;
}
return 0;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)b[i]=read();
pmin[0]=smin[n+1]=inf;
for(int i=1;i<=n;i++)pmin[i]=min(pmin[i-1],b[i]);
for(int i=1;i<=n;i++)pmax[i]=max(pmax[i-1],b[i]);
for(int i=n;i>=1;i--)smin[i]=min(smin[i+1],b[i]);
for(int i=n;i>=1;i--)smax[i]=max(smax[i+1],b[i]);
int l=0,r=inf;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
return 0;
}
考虑一个显然的\(\Theta(nn!)\)的做法,枚举顺序\(\{p_i\}\),显然\(b_{p_i}=b_{p_{i-1}}+\max\{a_{p_{i-1}}-a_{p_i}+[p_{i-1}<p_i],0\}\)
考虑正解状压,设\(f[i][j][k]\)为状态为\(i\),目前最大为\(j\),费用为\(k\)的方案数。
由于我们只知道增量,故计算增量的贡献,即\(\Delta b (n-popcount(i))\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=15;
const int M=505;
int n,m,a[N],cnt[1<<N];ll f[1<<13][N][M],ans;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int main(){
n=read(),m=read();
int ma=0,mp=0;
for(int i=1;i<=n;i++){
a[i]=read();
if(a[i]>ma)ma=a[i],mp=i;
}
int t=(1<<n)-1,sum;
for(int i=1;i<=t;i++)
cnt[i]=cnt[i>>1]+(i&1);
for(int i=1;i<=n;i++){
sum=n*(ma-a[i]+(mp<i));
if(sum<=m)f[1<<(i-1)][i][sum]=1;
}
for(int i=1;i<t;i++)
for(int j=1;j<=n;j++)
if(i&(1<<j-1))
for(int k=0;k<=m;k++){
if(f[i][j][k]==0)continue;
for(int p=1;p<=n;p++)
if((i&(1<<p-1))==0){
sum=k+(n-cnt[i])*max(a[j]-a[p]+(j<p),0);
if(sum<=m)f[i|(1<<p-1)][p][sum]+=f[i][j][k];
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
ans+=f[t][i][j];
printf("%lld\n",ans);
return 0;
}