【考试总结】2022-02-18 暨 《<遇到困难睡大觉> 命题报告》学习笔记
遇到困难睡大觉
一点想说的话,和题目没有关系就折叠起来了
大概2021年论文出来的时候我就觉得这个题目名称非常有意思,后来高一暑假训练的时候由于劳逸过于不平衡还报复性地写了一份缺省源存到了博客园里面,标题也多多少少代表了当时的状态:耐心不足,缺少解决问题的恒力
最近看到了 wfls 的 Cirno_9 同学写的一篇 “电竞选手现状” 来在一定程度上反映了浮躁心理和对真正应该做的训练的抵触,我看了深有体会,加之那天开始阅读论文,非常自闭
解题过程很长,看的时候我也一度质疑我究竟是不是有足够的耐心来进行更为困难的问题的研究,甚至参与复杂问题的解决;也产生了大量类似于 “我学了这么久 OI 连这么点耐心都没有培养起来” 的想法并深陷惭愧中。
但是方法论就是方法论,题目一步步的推理逻辑和题目之外的冗余想法是毫不相关的,一味捂着脸揉眼睛确实对做出题目一点帮助没有。即使对符号语言不那么敏感,但是沿着搭建好的逻辑大路一步步向前走总是没问题的。
无论何时,都希望你在完成题目的时候抛开题目本身逻辑之外的杂念,享受不断扩展关于题目本身的逻辑的快乐!
如果强制所有的 \(b_{p_i}+i\times k\) 不超过给定的 \(M\),求最满足条件的最大的 \(a_{p_i}+i\times k\) 是基础的,此时不难得到 \(r_i=\lfloor\frac{M-b_i}k\rfloor\) 表示 \(b_i\) 能放置的右界点
这是可以将所有 \(r_i\) 排序后用堆找到最有决策,枚举 \(1\sim n\) 的每个 \(b_i\) 放到 \(1\sim n\) 的每个位置,要进行一共 \(\Theta(n^2)\) 种 \(M\) 的判定,复杂度 \(\Theta(n^3\log n)\)
上面的贪心过程还可以被刻画成按照 \(r_i\) 从小到大,\(a_i\) 从大到小找一个空位,每次找合法区间里面最大的即可,可以使用并查集来优化,可以去掉 \(\log\)
接下来的每个部分都需要一定程度的观察
考虑到 \(r_i\) 的相对大小关系是和 \(M\) 无关的,而题目中所需求的 \(\min\{a_{p_i}+i\times k\}-\max\{b_{p_i}+i\times k\}\) 是和 \(i\) 的相对大小而非绝对大小相关的,所以我们大可将下标平移到 \(t\sim t+n-1\) 来做,也就是说 \(r_i\) 不受制于 \(1\sim n\) 了,只要是连续的 \(n\) 个正整数即可
观察到 \(M\to M+k\) 时每个 \(r_i\) 都增加了 \(1\) 但是这和不增加时问题本质相同,所以原来要判定 \(\Theta(n^2)\) 次,现在可以减少到 \(\Theta(\min(n^2,k))\) 了
考察 “每个数字填入一个位置,同时受一个填入右端点限制” 发现这和二分图匹配是完全相同的,那么问题就是找到存在的合法完美匹配中最大的 \(\min\{a_{p_i}+i\times k\}\)
首先判定是否存在一个完美匹配,使用 \(\rm Hall\) 定理可以发现将 \(r_i\) 排序之后得到 \(t=\min\{r_i-i+1\}\) 就是最大值
注意 \(r_i\) 在 \(M\) 固定时界还要和 \(t+n-1\) 取 \(\min\),所以选择最大的 \(t\) 判定即可,那么判定数量降至 \(\Theta(\min\{n,k\}n)\),可以得到 \(50\) 分
尝试二分最后的答案 \(\rm mid\),那么每个元素还会有放置的上界 \(l_i=\lceil\frac{M+mid-a_i}k\rceil\),仍然根据 \(\rm Hall\) 定理,设 \(s(l,r)\) 表示被 \([l,r]\) 包含的 \((l_i,r_i)\) 个数,如果满足 \(\forall l\le r,r-l+1-s(l,r)\ge 0\) 那么存在完美匹配
非常直接的做法还是枚举每个要判定的 \(M\),求出来 \(l_i,r_i\) 并二维数点即可
最终的标算是考虑 \(M\in[0,k)\) 不断增加的过程中 \(l_i,r_i\) 只会增加一次,记这个时间为 \(tl_i,tr_i\),问题变成了找到合法的 \(M\)。
设 \(q(l,r)=r-l+1-s(i,j)\),对于合法的 \(M\) 要求所有的 \(q(l,r)\) 都是非负数
对于一个固定的 \(s(l,r)\),\(r-l+1\) 越大那么越可能合法,所以问题变成了保证 \(\forall\ l_i\ge r_j,q(l_i,r_j)\ge 0\)
仍然可以扫描线,对应的修改操作就是遇到一个 \(r_i\) 就给 \([1,l_i]\) 这个区间减一来表示更新 \(s(l,r)\)
如果存在 \(q(l_i,r_j)<-1\) 一定不合法,同时也不用考虑 \(q(l_i,r_j)>1\) 的 \(i,j\)
对于 \(q(l,r)=-1\) 的情况,当 \(r\) 增大且 \(l\) 没增大的时候合法,否则不合法;对于 \(q(l,r)=0\) 的情况,当 \(l\) 增大且 \(r\) 没增大时不合法,否则合法
那么发现对于相同的 \(r\) 取 \(l\) 变化时间最小者(\(\min\{tl_i\}\))就是最紧的限制
具体而言,要求的是 \(q(l,r)\) 的最小值,在线段树上按照 \(l_i\) 的从小到大每个叶子上放 \((-l_i,\Delta l_i)\),同时维护区间最小的 \(-l_i\) 和对应最小的 \(\Delta l_i\)
注意我们需要取出的是可能的 \(q(i,j)=0/-1\) 的限制,那么需要维护 \(l_i\) 最小和次小两个二元组,真实的 \(q(i,j)\) 还要加上当前的 \(r_i+1\),分上面说得 \(4\) 中情况讨论即可
时间复杂度 \(\Theta(n\log n\log A)\),其中 \(A\) 是题目给定变量的绝对值的最大值
Code Display
#include
using namespace std;
#define pii pair
#define sec second
#define fir first
#define int long long
#define ull unsigned long long
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define Down(i,a,b) for(int i=a;i>=b;--i)
templateinline void ckmax(T &x,T y){x=xinline void ckmin(T &x,T y){x=xinline T read(){
T res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
char OPUT[100];
templateinline void print(T x){
if(x<0) putchar('-'),x=-x;
int cnt=0; do OPUT[++cnt]=x%10,x/=10; while(x);
Down(i,cnt,1) putchar(OPUT[i]+'0'); putchar(' '); return ;
}
#define read() read()
}using namespace FastIO;
const int N=1e5+10,inf=0x3f3f3f3f3f3f3f3f;
pair a[N];
int deltl[N],deltr[N],id[N],ord[N],lef[N],rig[N],n,k,rigtim,Mnt;
#define ls p<<1
#define rs p<<1|1
int tag[N<<2];
struct data{
int val,mn; data(){}
data(int a,int b){val=a; mn=b;}
};
inline data Merge(data a,data b){
if(a.val==b.val) return data(a.val,min(a.mn,b.mn));
return a.val t[N<<2];
inline pair merge(pair a,pair b){
pair res;
if(a.fir.val==b.fir.val){
res.fir=Merge(a.fir,b.fir);
res.sec=Merge(a.sec,b.sec);
return res;
}
if(a.fir.val>b.fir.val) swap(a,b);
res.fir=a.fir;
res.sec=Merge(a.sec,b.fir);
return res;
}
inline void push_tag(int p,int v){
tag[p]+=v;
t[p].fir.val+=v; t[p].sec.val+=v;
return ;
}
inline void push_down(int p){
if(tag[p]){
push_tag(ls,tag[p]); push_tag(rs,tag[p]);
tag[p]=0;
} return ;
}
inline pair query(int st,int ed,int p=1,int l=1,int r=n){
if(st<=l&&r<=ed) return t[p];
int mid=(l+r)>>1; push_down(p);
if(ed<=mid) return query(st,ed,ls,l,mid);
if(st>mid) return query(st,ed,rs,mid+1,r);
return merge(query(st,ed,ls,l,mid),query(st,ed,rs,mid+1,r));
}
inline void upd(int st,int ed,int v,int p=1,int l=1,int r=n){
if(st<=l&&r<=ed) return push_tag(p,v);
int mid=(l+r)>>1; push_down(p);
if(st<=mid) upd(st,ed,v,ls,l,mid);
if(ed>mid) upd(st,ed,v,rs,mid+1,r);
t[p]=merge(t[ls],t[rs]); return ;
}
inline void build(int p,int l,int r){
tag[p]=0;
if(l==r){
t[p].fir=data(-lef[ord[l]],deltl[ord[l]]);
t[p].sec=data(inf,0);
return ;
} int mid=(l+r)>>1;
build(ls,l,mid); build(rs,mid+1,r);
t[p]=merge(t[ls],t[rs]); return ;
}
#undef ls
#undef rs
vector > zero;
int L,R;
inline void work(int q,int lt,int rt){
// four cases of q
if(q<-1) R=-inf; // illegal
else if(q==-1) ckmax(L,rt),ckmin(R,lt-1); //find the cap
else if(q==0){
if(rt<=lt) return ; //available now
zero.emplace_back(lt,rt);
} return ;
}
int tmp1[N],tmp2[N];
inline bool check(int mid){
rep(i,1,n) tmp1[i]=rig[i],tmp2[i]=deltr[i];
L=0,R=k-1; zero.clear();
rep(i,1,n){
lef[i]=(mid-a[i].fir)/k;
while(lef[i]*k+a[i].fir now=query(1,id[i]);
if(tmp1[i]==Mnt+n-1) ckmax(tmp2[i],rigtim);
else if(tmp1[i]>Mnt+n-1) tmp1[i]=Mnt+n-1,tmp2[i]=rigtim;
work(tmp1[i]+1+now.fir.val,now.fir.mn,tmp2[i]);
work(tmp1[i]+1+now.sec.val,now.sec.mn,tmp2[i]);
}
if(RL) return 1;
ckmax(L,t.sec);
if(Rb.sec;
});
for(int i=1;i<=n;++i) ord[i]=i;
sort(ord+1,ord+n+1,[&](const int x,const int y){
if(a[x].fir==a[y].fir) return xa[y].fir;
});
for(int i=1;i<=n;++i) id[ord[i]]=i;
//formula l[i]=ceil((M+mid-a[i])/k) so sort it in decerasing order
for(int i=1;i<=n;++i){
rig[i]=-a[i].sec/k;
while(rig[i]*k>-a[i].sec) --rig[i];
deltr[i]=k-(-a[i].sec-rig[i]*k);
//calculate the right bound then get the increasing time
if(rig[i]-i+1==Mnt) ckmax(rigtim,deltr[i]);
else if(rig[i]-i+1>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
} print(ans);
return 0;
}
进制转换
问题是求满足下述条件的最小的 \(b\)
\[\begin{cases}\exist\ a_0b^0+a_1b_1+\dots a_mb^m=y\\ \forall\ i\in[0,m] 0\le a_i\le 9\\ \overline{a_m\dots a_0}\ge l \end{cases}\]先处理掉 \(b\le 100\) 的情况开始看这个 \(m\),这时候 \(m\in[1,9]\)
考虑逼近:\(a_mb^m\le y\le a_mb^m+9\times\frac{b^m-1}{b-1}\)
第二个不等号表示将前面一些位全部调大,并利用每个数位都 \(\in[0,9]\) 来得到的
使用第一个不等号可以得到 \(b\) 的上界 \(\sqrt[m]{\frac{y}{a_m}}\)
将等比数列求和式子中的 \(b\) 换成 \(b_m\) 是无伤大雅的,本质上还是调大低位权值,可以卡出来一个 \(b\) 的下界,大概是
\[\sqrt[m]{\frac{y-9\times \frac{b^m-1}{b-1}}{a_m}}\le y\le \sqrt[m]{\frac{y}{a_m}} \]枚举 \(m\in [1,8],a_m\in [1,9]\) 可以得到一个界,逐个枚举即可,这里注意如果能得到合法解直接输出,还真不太关心是不是满足最后进制转换的位数和最高位是不是 \(m,a_m\)
精度要求比较高,需要使用 powl
(pow
在 long long
中的别型)和 long double
Code Display
inline int turn(int a,int b){
int bas=1,res=0;
while(a){
if(a%b>=10) return -1;
res+=bas*(a%b);
a/=b; bas*=10;
} return res;
}
inline int get_lg(int x){
int lg=0;
while(x) x/=10,++lg;
return lg;
}
inline void solve(){
int y=read(),l=read();
for(int m=get_lg(l)-1;m<=8;++m){
for(int am=1;am<=9;++am){
int rbound=powl(y/am+1,1.0L/m);
int lbound=powl((y-9.0L*(powl(rbound,m)-1)/(rbound-1))/am,1.0L/m);
ckmax(lbound,10ll);
int e=turn(y,lbound);
if(e!=-1&&e=lbound){
if(turn(y,rbound)>=l) return print(rbound);
--rbound;
}
}
}
int b=100;
while(turn(y,b)
張士超你昨天晚上到底把我家鑰匙放在哪了
设 \(f_{i,j,k,l}\) 表示考虑了前 \(i\) 个藏匿点,其中有 \(l\) 个藏匿点的钥匙被找到了,强制钥匙数量大于对应 \(a_i\) 的藏匿点钥匙的 \(\sum a+1=j\),被找到的强制不合法的藏匿点的钥匙的 \(\sum a+1=k\),对应的概率
转移考虑枚举当前藏匿点是否强制不合法,和是不是被找到即可
注意这里都是 \(a+1\) 求和,可以使用题目中给的 \(d\) 来数组大小
考虑枚举每个 \(j,k,l\),问题可以转化成:有 \(N-(j+k)*d\) 把钥匙分给若干个组,前 \(l\) 个组的钥匙不得少于 \(n-jd\),每个组可以为空,求方案数
比较巧妙的计算方式是将这些组拍扁到序列上,枚举第 \(n-jd\) 把钥匙的位置不超过 \(k\),前面后面的钥匙均可以使用组合数计算,同时在 \(k\) 增加的过程中贡献系数是前缀和的形式
那么将组合数的下降幂进行处理即可计算答案
Code Display
const int N=110;
int dp[2][N][N][N],p[N],a[N],d,ned,n,m;
int dfac1[N],dfac2[N],ifac[N],coef[N];
signed main(){
freopen("key.in","r",stdin); freopen("key.out","w",stdout);
n=100; ifac[0]=ifac[1]=1;
rep(i,2,n) ifac[i]=mod-mul(mod/i,ifac[mod%i]);
rep(i,1,n) ifac[i]=mul(ifac[i-1],ifac[i]);
m=read(); d=read(); n=read(); ned=read();
rep(i,1,m) a[i]=read(),p[i]=read();
int cur=0; dp[0][0][0][0]=1;
for(int i=1;i<=m;++i){
for(int j=0;j<=n/d;++j){
//被钦定不合法的 钥匙数量/d 和
for(int k=0;k<=j;++k){
//被找到的 钥匙数量/d 的和
for(int l=0;l