DTOJ-2022-11-14-测试-题解
测试成果
\(100+100+0+92=292\)
还行
A 签到题
题目链接
题面大意
Diana
有一个函数 \(f(x)\) 表示 \(x\) 十进制下的各位之和,例如 \(f(233) = 2 + 3 + 3 = 8\)。 Diana
还有一个整数 \(n\),她告诉你有两个正整数. \(A,B\) 满足 \(A + B = n\),你需要求出 \(f(A) +f(B)\) 的最小值。
题解
显然如果进位了是不优的,如果 \(n\ne 10^k\) 一定可以构造出不进位的 \(A,B\) ,这时候答案 \(=f(n)\).
否则答案 \(=10\).
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+5;
int T;
char s[N];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%s",s+1);
int n=strlen(s+1),res=0;
for(int i=1; i<=n; i++) res+=s[i]-'0';
printf("%d\n",(res==1)?10:res);
}
return 0;
}
B 大根堆
题面链接
题面大意
\(1\sim n\) 大根堆,\(i\) 儿子的数量 \(\le d_i\),求方案数,儿子有顺序
\(1 \le T \le 5\),\(1 \le n \le 5000\),\(0 \le d_i < i\)
题解
考虑 \(1\sim n-1\) 的父亲 \(fa_i\)
显然 \(i< fa_i\le n\)
我们考虑 \(fa_i=2,\dots,n\) 依次往 \(\{fa_i\}\) 中填
记 \(f_{i,j}\) 表示填了 \(fa_i=2,\dots, i\) ,总共填了 \(j\) 个位置的方案数.
转移的话就是
新填了 \(j-k\) 个 \(fa_x=i\),有 \(i>x\) 也就是说空位有 \(i-1-k\) 个,那么填法就是 \(\binom{i-1-k}{j-k}\) 个,注意到儿子有顺序所以要乘 \((j-k)!\)
调教一下式子
然后就可以 \(O(n^2)\) 了
(前缀和优化是好东西)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5005, P = 998244353;
int T,n,d[N],f[N][N],g[N][N],fc[N],fci[N];
int ksm(int a, int b)
{
int res=1;
for(; b; b>>=1,a=(ll)a*a%P) if(b&1) res=(ll)a*res%P;
return res;
}
void init()
{
fc[0]=1;
for(int i=1; i<N; i++) fc[i]=(ll)fc[i-1]*i%P;
fci[N-1]=ksm(fc[N-1],P-2);
for(int i=N-1; i; i--) fci[i-1]=(ll)fci[i]*i%P; //预处理阶乘哦
}
int C(int n, int m)
{
if(n<0 or m<0 or n<m) return 0;
return (ll)fc[n]*fci[m]%P*fci[n-m]%P;
}
int main()
{
scanf("%d",&T);
init();
//for(int i=0; i<=10; i++) printf("%d %d\n",fc[i],fci[i]);
while(T--)
{
scanf("%d",&n);
memset(f,0,sizeof(f));
for(int i=1; i<=n; i++) scanf("%d",&d[i]);
f[1][0]=g[1][0]=g[1][1]=1;
/*
O(n^3) 暴力版本
for(int i=2; i<=n; i++)
for(int j=0; j<i; j++)
for(int k=0; k<=j; k++)
if(j-k<=d[i]) f[i][j]=(f[i][j]+(ll)f[i-1][k]*C(i-k-1,j-k)%P*fc[j-k]%P)%P;*/
for(int i=2; i<=n; i++)
{
for(int j=0; j<i; j++)
{
int res=g[i-1][j];
if(j-d[i]>0) res=(res-g[i-1][j-d[i]-1]+P)%P;
f[i][j]=(ll)fci[i-1-j]*res%P;
g[i][j]=(ll)fc[i-j]*f[i][j]%P;
if(j) g[i][j]=(g[i][j]+g[i][j-1])%P;
}
g[i][i]=g[i][i-1]; //记得加这个要不然转移会出问题(
}
printf("%d\n",f[n][n-1]);
}
return 0;
}
C 抽卡
题面链接
题面大意
有 \(n\) 个物品,每个时刻都会恰好出现一个物品,物品 \(i\) 会以
\(p_i\) 的概率出现.
记物品 \(i\) 的第一次出现时间为 \(t_i\),那么定义出现时间的平均数 \(\overline{t}\) 和方差 \(\sigma^2\) 为:
求 \(E(\overline{t})\), \(E(\sigma^2\)) ,对 \(998244353\) 取模.
题解
纯纯概率期望推式子题,可惜我不会(
先写题解再写题(
1.\(E(\overline{t})\)
首先我们要求 \(E(\frac{1}{n}\sum t_i)\)
期望的线性性是个好东西! ̄ω ̄=
所以我们就要求 \(E(t_i)\) :直接考虑每种事件的概率啊!
代回去!!
诶 20 分不就到手了吗(!( ̄︶ ̄)\(\uparrow\)
2.\(E(\sigma^2)\)
来来来我们来推方差
注意一个小结论:
那现在我们要推两个东西 一个是 \(E\left(t_i^2\right)\) 一个是 \(E\left( t_it_j\right)\)
(1) \(E\left(t_i^2\right)\)
跟刚才那个 \(E(t_i)\) 有什么区别
(2) \(E\left( t_it_j\right)\)
直接考虑每种事件的概率啊!
诶诶然后就完了呢!\(\sim\) 代回去!
这就完了. 直接做是 \(O(n^2\log n)\) 的,如果线性求 \((p_i+p_j)\) 逆元就可以 \(O(n^2)\) 了!
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5005, P = 998244353;
int n,n_inv,q[N],p[N],pi[N];
int ksm(int a, int b)
{
int res=1;
for( ; b; b>>=1, a=(ll)a*a%P) if(b&1) res=(ll)a*res%P;
return res;
}
void init()
{
scanf("%d",&n); int s=0; n_inv=ksm(n,P-2);
for(int i=1; i<=n; i++) scanf("%d",&q[i]),s=(s+q[i])%P;
s=ksm(s,P-2); for(int i=1; i<=n; i++) p[i]=(ll)q[i]*s%P;
for(int i=1; i<=n; i++) pi[i]=ksm(p[i],P-2);
}
void work1()
{
int res=0;
for(int i=1; i<=n; i++) res=(res+pi[i])%P;
printf("%lld\n",(ll)res*n_inv%P);
}
void work2()
{
int res1=0,res2=0;
for(int i=1; i<=n; i++) res1=(res1+(ll)(2-p[i]+P)*pi[i]%P*pi[i]%P)%P;
res1=(ll)res1*(n-1+P)%P*n_inv%P*n_inv%P;
for(int i=1; i<=n; i++) for(int j=1; j<i; j++)
res2=(res2+(ll)pi[i]*pi[j]%P-ksm(p[i]+p[j],P-2)+P)%P;
res2=(ll)res2*2*n_inv%P*n_inv%P;
printf("%d\n",(res1-res2+P)%P);
}
int main()
{
init(); work1(); work2(); return 0;
}
D 玩游戏
题目链接
题面大意
集合 \(S:s_1, s_2, \dots , s_m\) ,队列 \(\{b_i\}:b_1, b_2, \dots , b_k\),D 和 A 交替进行以下操作,直到队列 \(b\) 为空:
- 把 \(b\) 中的第一个数放入 ,取走 \(S\) 中的一个数 \(x\),然后把它累积到自己的得分中。
\(\{a_i\}: a_1, a_2, \dots , a_n\)
进行 \(q\) 场游戏。每场游戏三个数 \(l,r,x\) 询问,若选用区间 \(a_l , a_{l+1}, \dots , a_r\) 作为 \(b\) 进行一场游戏,在二者都采用最优策略的情况下,位置 \(x\) 的元素 \(a_x\) 是被谁取走的。
对于所有测试数据,保证 \(1 \le n, m, q \le 10^6\),\(1 \le a_i , s_i \le n + m\),\(1 \le l \le x \le r \le n\),保证将 \(a, s\) 拼接后得到的序列为 \(1 \sim n + m\) 的一个排列。
题解
暴力
图:

我们可以通过观察和手模样例得出一个结论:
放入 \([l,t]\) 的数之后,如果取走了 \(x\) 那么 \(x\) 一定是 \(a[l\sim t]\cup S\) 中前 \(t-l+1\) 大的元素.
这个很好理解,假设 \(x\) 不是前 \(t-l+1\) 大,因为取走 \(x\) 之前只取了 \(t-l\) 个元素,所以一定有比 \(x\) 更大的数可以取.
所以我们就有一个 \(O(n\log^2n)\) 做法,甚至可以拿到 92 分的高分()
注意到第 \(t-l+1\) 大的元素就是第 \(m+1\) 小的元素,这是个常数,所以是单调递减的
首先我们在 \([x,r]\) 区间二分找 \(t\),使第 \(m+1\) 小的元素 \(= a_x\) 这个时候就是 \(a_x\) 被取走的时候
求区间第 \(k\) 小使用主席树
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+5;
int n,m,q;
int a[N],s[N];
int rd()
{
int x; char ch;
while(!isdigit(ch=getchar()));
for(x=(ch^48); isdigit(ch=getchar()); x=(x<<1)+(x<<3)+(ch^48));
return x;
}
struct Seg
{
int ls,rs,dat;
} t[N<<5];
int rt[N],rt2[N],tot;
void build(int &p, int l, int r, int x, int v)
{
if(!p) p=++tot;
if(l==r) { t[p].dat+=v; return ; }
int mid=(l+r)>>1;
if(x<=mid) build(t[p].ls,l,mid,x,v);
else build(t[p].rs,mid+1,r,x,v);
t[p].dat=t[t[p].ls].dat+t[t[p].rs].dat;
}
void change(int &p, int q, int l, int r, int x, int v)
{
t[p=++tot]=t[q];
if(l==r) { t[p].dat+=v; return ; }
int mid=(l+r)>>1;
if(x<=mid) change(t[p].ls,t[q].ls,l,mid,x,v);
else change(t[p].rs,t[q].rs,mid+1,r,x,v);
t[p].dat=t[t[p].ls].dat+t[t[p].rs].dat;
}
int query(int p, int q, int l, int r, int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
int lcnt=t[t[p].ls].dat-t[t[q].ls].dat;
if(k<=lcnt) return query(t[p].ls,t[q].ls,l,mid,k);
else return query(t[p].rs,t[q].rs,mid+1,r,k-lcnt);
}
int main()
{
n=rd(),m=rd(),q=rd();
for(int i=1; i<=n; i++) a[i]=rd();
for(int i=1; i<=m; i++) s[i]=rd();
for(int i=1; i<=m; i++) build(rt[0],1,n+m,s[i],1);
for(int i=1; i<=n; i++) change(rt[i],rt[i-1],1,n+m,a[i],1),change(rt2[i],rt2[i-1],1,n+m,a[i],1);
int L,R,x;
/*while(q--)
{
L=rd(),R=rd(),x=rd();
printf("%d\n",query(rt[R],rt2[L-1],1,n+m,x));
}*/
while(q--)
{
L=rd(),R=rd(),x=rd();
if(query(rt[R],rt2[L-1],1,n+m,m+1)>a[x]) { puts("-1"); continue ; }
int l=x,r=R;
while(l<r)
{
// printf("%d %d\n",l,r);
int mid=(l+r)>>1;
if(query(rt[mid],rt2[L-1],1,n+m,m+1)<=a[x]) r=mid;
else l=mid+1;
}
// printf("%d\n",l);
puts(((l-L+1)&1)?"Diana":"Ava");
}
return 0;
}
正解
别催了别催了在补题了
补完了!
注意没有强制在线,我们离线做!
从小到大处理询问,先把集合 \(S\) 排个序,然后就可以用一个指针维护 \(S\) 中比 \(a[x]\) 小的数的个数.
然后以下标建线段树,就可以在线段树 \([l,r]\) 区间二分一个位置,使得 \(a[x]\) 是第 \(m+1\) 小.
时间复杂度 \(O(n\log n)\)
线段树上二分确实是好东西
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+5;
int rd()
{
int x; char ch;
while(!isdigit(ch=getchar()));
for(x=(ch^48); isdigit(ch=getchar()); x=(x<<1)+(x<<3)+(ch^48));
return x;
}
struct Sagiri
{
int l,r,dat;
} t[N<<2];
#define ls (p<<1)
#define rs (p<<1)|1
void build(int p, int l, int r)
{
t[p].l=l,t[p].r=r;
if(l==r) return ;
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
t[p].dat=t[ls].dat+t[rs].dat;
}
void change(int p, int x, int v)
{
if(t[p].l==t[p].r) { t[p].dat+=v; return ; }
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) change(ls,x,v);
else if(x>mid) change(rs,x,v);
t[p].dat=t[ls].dat+t[rs].dat;
}
int query(int p, int l, int r)
{
if(t[p].l==l and t[p].r==r) return t[p].dat;
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) return query(ls,l,r);
else if(l>mid) return query(rs,l,r);
else return query(ls,l,mid)+query(rs,mid+1,r);
}
int query2(int p, int k)
{
if(t[p].l==t[p].r) return t[p].l;
if(t[ls].dat>=k) return query2(ls,k); // 线段树上二分
else return query2(rs,k-t[ls].dat);
}
int n,m,q,nr;
int a[N],s[N];
struct Nazuna { int l,x,r,id,ans,val; } rq[N<<1]; // 拿小荠来离线询问!
void work()
{
build(1,1,n);
int cur=0;
for(int i=1; i<=nr; i++)
{
while(cur<m and s[cur+1]<rq[i].val) cur++;
if(!rq[i].id) change(1,rq[i].x,1);
else
{
if(query(1,rq[i].l,rq[i].r)<m-cur) rq[i].ans=n+1;
else
{
int t=m-cur+((rq[i].l>1)?query(1,1,rq[i].l-1):0);
if(t==0) rq[i].ans=rq[i].x; //记得各种特判
else
{
int pos=query2(1,t);
rq[i].ans=max(pos,rq[i].x);
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
nr++; rq[nr].x=i,rq[nr].val=a[i];
}
for(int i=1; i<=m; i++) scanf("%d",&s[i]);
sort(s+1,s+m+1);
int l,r,x;
for(int i=1; i<=q; i++)
{
scanf("%d%d%d",&l,&r,&x);
nr++; rq[nr]=(Nazuna){l,x,r,i,0,a[x]};
}
sort(rq+1,rq+nr+1, [&] (const Nazuna &a, const Nazuna &b) { return a.val<b.val or (a.val==b.val and a.id>b.id); });
work();
sort(rq+1,rq+nr+1, [&] (const Nazuna &a, const Nazuna &b) { return a.id<b.id; });
for(int i=1; i<=nr; i++)
if(rq[i].id)
{
if(rq[i].ans>rq[i].r) puts("-1");
else puts(((rq[i].ans-rq[i].l+1)&1)?"Diana":"Ava");
}
return 0;
}