11.12 game

题单介绍

### t1 定义函数 $f(x)$: 你有一个分数 $\dfrac{0}{x}$,每次你可以执行以下两种操作: - 分子加 ```1```。 - 分子和分母均加 ```1```。 每次操作后,如果分子分母能够约分,化简到最简形式。 直到分数的值等于 ```1``` 时操作结束。 $f(x)$ 的值为最小操次数。 每次输入给定正整数 $n$,输出 $\sum_{i=1}^{n}f(i) \bmod 998244353$。 诈骗题。 先考虑尽可能约分减小速度最快。 发现和 ```2``` 约分所需的前置操作次数最少;具体地,最优策略为: - 第一次使用操作 ```1```。 - 然后如果分子分母同奇偶就使用操作 ```2```,否则使用操作 ```1```,这样必能约调一个 ```2```。 容易发现操作次数为 $\lceil \log_{2}i\rceil+1$,然后直接算即可。 时间复杂度 $O(\log n)$。 ```cpp #include <cstdio> #include <cstdlib> #include <cmath> #include <cctype> #include <algorithm> #define FOR(i,a,b) for(int i = (a);i <= (b);++i) #define REP(i,a,b) for(int i = (a);i >= (b);--i) #define ll long long static char stkk[200]; template<typename T>inline void output(T x){ if(!x)return putchar('0'),void(); if(x<0)x = ~x+1,putchar('-'); int top = 0; for(;x;stkk[++top]=x%10^48,x/=10); for(;top;putchar(stkk[top--])); } template<typename T>inline void readx(T &x){ x = 0;int y = 1;char c = getchar(); for(;c<48||c>58;c = getchar())if(c=='-')y = -1; for(;c>=48&&c<=58;c = getchar())x = (x<<1)+(x<<3)+(c^48); x *= y; } const int N = 100,nf = 1e9+10,mo = 998244353; static ll pw[N]; signed main(){ freopen("function.in","r",stdin); freopen("function.out","w",stdout); ll n;readx(n); if(n==1)return output(1),0; if(n==2)return output(3),0; pw[0] = 1; for(int i = 1;pw[i-1]<=1e18;++i)pw[i] = pw[i-1]<<1; ll ans = 0; for(int i = 2;i;++i){ if(pw[i]<=n){ ans = (0ll+ans+1ll*(pw[i]-pw[i-1])%mo*(i+1)%mo)%mo; } else{ ans = (0ll+ans+1ll*(n-pw[i-1])%mo*(i+1)%mo)%mo; break; } } output((0ll+ans+3)%mo); return 0; } ``` ### t2 定义 $f(x)$,表示对可重集 $S$,每次操作可以选取 $S$ 的任意非空子集 $T$,将 $T$ 从 $S$ 中删去,并在 $S$ 中加入 $T$ 的 ```mex```。 $f(S)$ 是任意操作后当 $S$ 中只剩一个元素时该元素的最大值。 给定长度为 $n$ 的序列 $a$,请求出对于 $a$ 的每一个前缀 $S$ 的 $f(S)$。 $1 \leq n \leq 4 \times 10^5$。 对于 $a_i \geq n$ 的数,其必然不可能直接贡献 ```mex```,直接将其变成 ```0``` 即可。 答案不降,不妨思考如何判等。 对于答案 $x$,要求 $[0,x-1]$ 都至少被覆盖。 从大到小扫描 $[0,x-1]$,记 $c_i$ 表示数字 $i$ 出现个数;维护 ```p``` ```s``` 分别表示 【当前位合法至少要出现 $p$ 次】和 【如果有 $c_i \gt p$,就将多余 $c_i-p$ 变成 ```0```,这样的数的个数】。 每次扫到一个 $i$,有 $s \gets s+\max(0,c_i-p)$,$p \gets p+\max(0,p-c_i)$;```p``` 更新的含义是当前数少了 $p-c_i$,将其分摊到前缀。 更新式和 ```p``` 紧密相关,考虑在 $x$ 个不同的 $i$ 位置 ```p``` 被贡献,那么对 $\sum c_i$ 的要求至少增多了 $O(x^2)$,所以有效位置至多 $O(\sqrt n)$ 个。 考虑每次向左寻找第一个满足 $c_i \lt p$ 的位置,那么剩余的位置都满足 $c_i \geq p$,会对 $s$ 产生 $(\sum_j c_j)-totp$ 的贡献,```tot``` 是剩余位置数; 考虑在线段树上维护 $c_i$ 的最小值以及 $c_i$ 的和,然后每次直接在线段树上二分有效位置,并更新 $s,p$;时间复杂度 $O(n\sqrt n \log n)$。 考虑直接 ```dfs``` 线段树,这样时间复杂度是 $O(n \sqrt n)$; 对线段树的每层节点考虑,每层如果有 $x$ 个不同的节点的子数内存在 $c_i$ 会贡献 $p$,则其至少对 $\sum c_i$ 产生 $O(2^kx^2)$ 的贡献,所以一层至多有 $\sqrt{\dfrac{n}{2^k}}$ 个可贡献的节点;对不同层求和后量级显然是 $O(\sqrt n)$ 的。 ```cpp #include <cstdio> #include <cstdlib> #include <cmath> #include <cctype> #include <algorithm> #define FOR(i,a,b) for(int i = (a);i <= (b);++i) #define REP(i,a,b) for(int i = (a);i >= (b);--i) #define ll long long static char stkk[200]; template<typename T>inline void output(T x){ if(!x)return putchar('0'),void(); if(x<0)x = ~x+1,putchar('-'); int top = 0; for(;x;stkk[++top]=x%10^48,x/=10); for(;top;putchar(stkk[top--])); } template<typename T>inline void readx(T &x){ x = 0;int y = 1;char c = getchar(); for(;c<48||c>58;c = getchar())if(c=='-')y = -1; for(;c>=48&&c<=58;c = getchar())x = (x<<1)+(x<<3)+(c^48); x *= y; } const int N = 4e5+10; struct work1{ int a[N],n; struct D{ int mi[N<<2],su[N<<2]; #define ls (x<<1) #define rs (x<<1|1) #define mid ((l+r)>>1) inline void pushup(int x){ mi[x] = std::min(mi[ls],mi[rs]); } void my_pos(int x,int l,int r,int pos){ if(r<pos||l>pos)return; ++su[x]; if(l==r)return ++mi[x],void(); my_pos(ls,l,mid,pos),my_pos(rs,mid+1,r,pos); pushup(x); } void qy(int x,int l,int r,int pl,int pr,ll &s,ll &p,int tot){ if(p>tot)return; if(r<pl||l>pr)return s+=su[x],void(); if(pl<=l&&r<=pr){ if(mi[x]>p)return s+=su[x]-1ll*(r-l+1)*(p+1),void(); // printf("l:%d r:%d su:%d s:%lld p:%lld %lld\n",l,r,su[x],s,p,su[x]-1ll*(r-l+1)*(p+1)), // printf("l:%d r:%d su:%d\n",l,r,su[x]), } if(l==r)return p+=p+1-su[x],void(); qy(rs,mid+1,r,pl,pr,s,p,tot),qy(ls,l,mid,pl,pr,s,p,tot); } #undef ls #undef rs #undef mid }t0; inline void add(int x){ if(x>n)t0.my_pos(1,0,n,0); else t0.my_pos(1,0,n,x); } inline bool check(int x,int i){ ll s,p;s = p = 0; // printf("x:%d i:%d\n",x,i); t0.qy(1,0,n,1,x-1,s,p,i); // printf("s:%lld p:%lld\n",s,p); return s>=p+1; } void main(){ readx(n); int pre = 2; FOR(i,1,n){ readx(a[i]); add(a[i]); if(i==1)output(std::max(a[1],1)); else{ for(;check(pre,i);++pre); output(pre-1); } putchar(' '); } putchar(10); } }A; signed main(){ freopen("mex.in","r",stdin); freopen("mex.out","w",stdout); A.main(); return 0; } ``` ### t3 给定 $n$ 个传送器,每个传送器有两个属性:位置 $p_i$,功率 $f_i$。 一个位于位置 $A$ 的人,使用了编号为 $i$ 的传送器,会被传送到 $2p_i-A$ 的位置。 有两人 $a,b$ 分别位于 $A_i,B_i$ 位置,她们每次会各自选择两个传送器 $i,j$,然后一起传送,定义该次传送的代价为 $|f_i-f_j|$; 她们想要通过使用功率位于 $[L_i,R_i]$ 传送器相遇;定义这次相遇的代价为每次传送代价的最大值。 请对每次询问,如果可以相遇,输出相遇代价的最小值;否则输出 ```-1```。 $1 \leq n,m \leq 5 \times 10^4$ 两人一起传送可以看作一人传送两次。 不妨记奇数次选择的传送器位置集合为 $x$,偶数次选择的传送器位置集合为 $y$,则有位置相同等价于 $2(x_i-y_i)=A-B$,所以 $A,B$ 必须同奇偶,并且形如 $x_i-y_i$ 的数集的 $\gcd$ 必须是 $A-B$ 的因子。 暴力的想法是从大到小扫描询问的左端点,用维护三元组 $(|p_i-p_j|,|f_i-f_j|,j)$,用 ```set``` 让其第二个键值升序。可以做到 $O(n^2\log n+qn^2)$。 考虑优化,瓶颈在于数集的大小为 $O(n^2)$ 的;考虑只保留数集中 $c_i=p_i-p_{i-1}$ 的数,这样是正确的 [AI 证明](https://www.luogu.com.cn/article/jjh6n9bz)。 然后可以整体二分,用线段树维护区间 $\gcd$,可以做到 $O(n \log^2 n \log V)$,由于 $\gcd$ 常数很小,可以视为 $O(n \log^2 n)$。 ```cpp #include <cstdio> #include <cstdlib> #include <cctype> #include <cmath> #include <algorithm> #define FOR(i,a,b) for(int i = (a);i <= (b);++i) #define REP(i,a,b) for(int i = (a);i >= (b);--i) static char stkk[200]; template<typename T>inline void output(T x){ if(!x)return putchar('0'),void(); if(x<0)x = ~x+1,putchar('-'); int top = 0; for(;x;stkk[++top]=x%10^48,x/=10); for(;top;putchar(stkk[top--])); } template<typename T>inline void readx(T &x){ x = 0;int y = 1;char c = getchar(); for(;c<48||c>58;c = getchar())if(c=='-')y = -1; for(;c>=48&&c<=58;c = getchar())x = (x<<1)+(x<<3)+(c^48); x *= y; } const int N = 5e4+10; struct work{ int n,m; struct node1{int f,pos;bool operator<(const node1 &A)const {return f<A.f;}}a[N]; struct node2{ int l,r,id,v; }qy[N],qytmp[N]; int tp,ans[N]; bool flg[N]; struct node3{ int cpos,cf,id;bool operator<(const node3 &A)const {return cf<A.cf;} }c[N]; struct D{ int t[N<<2]; #define ls (x<<1) #define rs (x<<1|1) #define mid ((l+r)>>1) inline void pushup(int x){t[x] = std::__gcd(t[ls],t[rs]);} void bd(int x,int l,int r,node3 *c){ if(l==r)return t[x] = c[l].cpos,void(); bd(ls,l,mid,c),bd(rs,mid+1,r,c); pushup(x); } void my_pos(int x,int l,int r,int pos,int d){ if(r<pos||l>pos)return; if(l==r)return t[x]=d,void(); my_pos(ls,l,mid,pos,d),my_pos(rs,mid+1,r,pos,d); pushup(x); } int qy(int x,int l,int r,int pl,int pr){ if(r<pl||l>pr)return 0; if(pl<=l&&r<=pr)return t[x]; return std::__gcd(qy(ls,l,mid,pl,pr),qy(rs,mid+1,r,pl,pr)); } #undef ls #undef rs #undef mid }t0; void solve(int l,int r,int L,int R){ if(l>r)return; int mid = (l+r)>>1; FOR(i,mid+1,r)t0.my_pos(1,1,n-1,c[i].id,0); FOR(i,L,R){ int tmp = t0.qy(1,1,n-1,qy[i].l,qy[i].r); if(tmp&&!(qy[i].v%tmp))ans[qy[i].id] = c[mid].cf,flg[i] = 1; else flg[i] = 0; } int tl = L-1,tr; FOR(i,L,R)if(flg[i])qytmp[++tl] = qy[i]; tr = tl; FOR(i,L,R)if(!flg[i])qytmp[++tr] = qy[i]; if(tr^R)puts("?"),exit(0); FOR(i,L,R)qy[i] = qytmp[i]; t0.my_pos(1,1,n-1,c[mid].id,0); solve(l,mid-1,L,tl); FOR(i,mid,r)t0.my_pos(1,1,n-1,c[i].id,c[i].cpos); solve(mid+1,r,tl+1,R); } void main(){ readx(n),readx(m); FOR(i,1,n)readx(a[i].pos); FOR(i,1,n)readx(a[i].f); std::sort(a+1,a+1+n); //c FOR(i,1,n-1)c[i] = (node3){std::abs(a[i+1].pos-a[i].pos),a[i+1].f-a[i].f,i}; //qy int p0,p1,tl,tr; FOR(i,1,m){ ans[i] = -1; readx(p0),readx(p1),readx(tl),readx(tr); if(p0==p1)ans[i] = 0; else if((p0-p1)&1^1){ int pl = std::lower_bound(a+1,a+1+n,(node1){tl,0})-a,pr = std::upper_bound(a+1,a+1+n,(node1){tr,0})-a-1; if(pl<pr)qy[++tp] = (node2){pl,pr-1,i,std::abs(p0-p1)/2}; } } //solve t0.bd(1,1,n-1,c); std::sort(c+1,c+n); solve(1,n-1,1,tp); FOR(i,1,m)output(ans[i]),putchar(' '); putchar(10); } }A; signed main(){ freopen("teleporters.in","r",stdin); freopen("teleporters.out","w",stdout); // output((int)(N*std::__lg(N)*std::__lg((int)(1e9)))); A.main(); return 0; } ``` ### t4 定义 $f(x)$ 为 $x$ 各位数字之和。 给定长度为 $n$ 的序列 $a$,输出 $\min_{k=0}\{\sum_{i=1}^{n}f(a_i+x)\}$。 $1 \leq n \leq 10^6$。 定义个位为第 ```1``` 位,十位为第 ```2``` 位,以此类推。 从各位考虑加上 $x$ 的影响。 发现答案可以分成三部分: $\sum_{i=1}^{n}f(a_i),f(x)n,-9c$,$c$ 是每个数的各位上的进位次数和。 第一部分是好做的。考虑对后两部分 ```dp```。 关于进位,有以下两个性质: - $x$ 的前 $i-1$ 位至多对第 $i$ 位产生 ```1``` 的进位。 - 对于同样的 $x$,前 $i-1$ 位越大的数 $a_i$ 越容易进位。 所以定义 $f_{i,j}$ 表示考虑了前 $i$ 位,贡献了 $j$ 个进位的后两部分的最小和。 每次从 $f_{i,j}$ 主动转移,枚举 $x$ 在 $i+1$ 位的数。 细节:记得考虑进位,特殊考虑进位前该位数为 ```9``` 的数。 这样可以做到 $O(nB\log V+n\log n\log V)$ 的复杂度,瓶颈在排序;换成基数排序,可以做到 $O(nB \log V+n\log V)$。 ```cpp #include <cstdio> #include <cstdlib> #include <cmath> #include <cctype> #include <algorithm> #define FOR(i,a,b) for(int i = (a);i <= (b);++i) #define REP(i,a,b) for(int i = (a);i >= (b);--i) // #define int long long static char stkk[200]; template<typename T>inline void output(T x){ if(!x)return putchar('0'),void(); if(x<0)x = ~x+1,putchar('-'); int top = 0; for(;x;stkk[++top]=x%10^48,x/=10); for(;top;putchar(stkk[top--])); } template<typename T>inline void readx(T &x){ x = 0;int y = 1;char c = getchar(); for(;c<48||c>58;c = getchar())if(c=='-')y = -1; for(;c>=48&&c<=58;c = getchar())x = (x<<1)+(x<<3)+(c^48); x *= y; } const int N = 1e6+10,B = 20,nf = 2e9+10,base[] = {0,1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; struct work1{ int f[N],g[N],a[N],ct[B],n,mx,c[N]; inline void ckmax(int &x,int y){x<y&&(x=y);} inline void ckmin(int &x,int y){x>y&&(x=y);} inline void cal(int &ans,int &mx,int x){ int ct = 0; for(;x;ans+=x%10,x/=10,++ct); ckmax(mx,ct); } inline void dp(){ FOR(wei,1,mx){ std::swap(g,f); FOR(i,0,n)f[i] = nf; FOR(i,0,9)ct[i] = 0; FOR(i,1,n)++ct[(a[i]/base[wei])%10]; FOR(i,1,9)ct[i]+=ct[i-1]; for(int p = 0,pre = 0;p <= n;++p){ if(p){ int x = (a[p]/base[wei])%10; if(x==9)++pre; --ct[x]; } FOR(i,0,9){ ckmin(f[pre+ct[9]-ct[9-i]],g[p]+i*n-9*(pre+ct[9]-ct[9-i])); } } // sort FOR(i,0,9)ct[i] = 0; FOR(i,1,n)++ct[(a[i]/base[wei])%10]; REP(i,8,0)ct[i]+=ct[i+1]; REP(i,n,1)c[ct[(a[i]/base[wei])%10]--] = a[i]; FOR(i,1,n)a[i] = c[i]; // FOR(i,1,n){ // printf("%d ",a[i]%(base[wei+1])); // }putchar(10); } } inline void main(){ readx(n);int ans = 0,tmp = 0; FOR(i,1,n)readx(a[i]),cal(ans,mx,a[i]),f[i] = nf; dp(); FOR(i,0,n)ckmin(tmp,f[i]); output(ans+tmp); } }A; signed main(){ freopen("sum.in","r",stdin); freopen("sum.out","w",stdout); A.main(); return 0; } ```

题目列表