DTOJ-2022-11-14-测试-题解

测试成果

100+100+0+92=292

还行

A 签到题

题目链接

DTOJ P6363

题面大意

Diana 有一个函数 f(x) 表示 x 十进制下的各位之和,例如 f(233)=2+3+3=8Diana 还有一个整数 n,她告诉你有两个正整数. A,B 满足 A+B=n,你需要求出 f(A)+f(B) 的最小值。

题解

显然如果进位了是不优的,如果 n10k 一定可以构造出不进位的 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 大根堆

题面链接

DTOJ P6364

题面大意

1n 大根堆,i 儿子的数量 di,求方案数,儿子有顺序

1T51n50000di<i

题解

考虑 1n1 的父亲 fai

显然 i<fain

我们考虑 fai=2,,n 依次往 {fai} 中填

fi,j 表示填了 fai=2,,i ,总共填了 j 个位置的方案数.

转移的话就是

fi,j=max(0,jd[i])kjfi1,k(i1kjk)(jk)!

新填了 jkfax=i,有 i>x 也就是说空位有 i1k 个,那么填法就是 (i1kjk) 个,注意到儿子有顺序所以要乘 (jk)!

调教一下式子

fi,j=max(0,jd[i])kjfi1,k(i1k)!(i1j)!=1(i1j)!max(0,jdi)kjfi1,k(i1k)! gi,j=kjfi1,k(i1k)! fi,j=1(i1j)!(gjgjdi1)

然后就可以 O(n2)

(前缀和优化是好东西)

代码

#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 抽卡

题面链接

portal

题面大意

n 个物品,每个时刻都会恰好出现一个物品,物品 i 会以
pi 的概率出现.

记物品 i 的第一次出现时间为 ti,那么定义出现时间的平均数 t 和方差 σ2 为:

t=1ni=1ntiσ2=1ni=1n(tit)2

E(t), E(σ2) ,对 998244353 取模.

题解

纯纯概率期望推式子题,可惜我不会(

先写题解再写题(

1.E(t)

首先我们要E(1nti)

E(1nti)=1nE(ti)=1nE(ti)

期望的线性性是个好东西! ̄ω ̄=

所以我们就要E(ti) :直接考虑每种事件的概率啊!

E(ti)=pi1+pi(1pi)2+pi(1pi)23+=pi(1+2(1pi)+3(1pi)2+)S=1+2q+3q2+S=1+q+q2+1q=1(1q)2E(ti)=pi1pi2=1pi

代回去!!

E(1nti)=1nE(ti)=1n1pi

诶 20 分不就到手了吗(!( ̄︶ ̄)

2.E(σ2)

来来来我们来推方差

注意一个小结论

1ni=1n(tit)2=1ni=1nti2(1ni=1nti)2

E(σ2)=E(1ni=1n(tit)2)=E(1nti2)E((1nti)2)=1nE(ti2)1n2E((ti)2)=1nE(ti2)1n2E(ti2+2j<ititj)=n1n2E(ti2)2n2j<iE(titj)

那现在我们要推两个东西 一个是 E(ti2) 一个是 E(titj)

(1) E(ti2)

跟刚才那个 E(ti) 有什么区别

E(ti2)=pi12+pi(1pi)22+pi(1pi)232+=pi(12+22(1pi)+32(1pi)2+)S=12+22q+32q2+S=1+3q+5q2+7q3+1qT=1+3q+5q2+7q3+T=1+2q+2q2+2q3+1q=1+q(1q)2S=1+q(1q)3E(ti2)=pi2pipi3=2pipi2

(2) E(titj)

直接考虑每种事件的概率啊!

E(titj)=piE(tj+1)+pjE(ti+1)+(1pipj)E((ti+1)(tj+1))=piE(tj+1)+pjE(ti+1)+(1pipj)(E(titj)+E(ti)+E(tj)+1)=1+(1pj)E(tj)+(1pi)E(ti)+(1pipj)E(titj)(pi+pj)E(titj)=1+(1pj)E(tj)+(1pi)E(ti)E(titj)=1pi+pj+1pjpi+pjE(tj)+1pipi+pjE(ti)=1pi+pj+1pjpj(pi+pj)+1pipi(pi+pj)=1pi+pj+pi+pj2pipjpipj(pi+pj)=1pipj1pi+pj

诶诶然后就完了呢! 代回去!

E(σ2)=n1n2E(ti2)2n2j<iE(titj)=n1n22pipi22n2j<i(1pipj1pi+pj)

这就完了. 直接做是 O(n2logn) 的,如果线性求 (pi+pj) 逆元就可以 O(n2) 了!

代码

#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 玩游戏

题目链接

DTOJ P6366

题面大意

集合 S:s1,s2,,sm ,队列 {bi}:b1,b2,,bk,D 和 A 交替进行以下操作,直到队列 b 为空:

  • b 中的第一个数放入 ,取走 S 中的一个数 x,然后把它累积到自己的得分中。

{ai}:a1,a2,,an

进行 q 场游戏。每场游戏三个数 l,r,x 询问,若选用区间 al,al+1,,ar 作为 b 进行一场游戏,在二者都采用最优策略的情况下,位置 x 的元素 ax 是被谁取走的。

对于所有测试数据,保证 1n,m,q1061ai,sin+m1lxrn,保证将 a,s 拼接后得到的序列为 1n+m 的一个排列。

题解

暴力

图:

我们可以通过观察和手模样例得出一个结论:

放入 [l,t] 的数之后,如果取走了 x 那么 x 一定是 a[lt]S 中前 tl+1 大的元素.

这个很好理解,假设 x 不是前 tl+1 大,因为取走 x 之前只取了 tl 个元素,所以一定有比 x 更大的数可以取.

所以我们就有一个 O(nlog2n) 做法,甚至可以拿到 92 分的高分()

注意到第 tl+1 大的元素就是第 m+1 小的元素,这是个常数,所以是单调递减的

首先我们在 [x,r] 区间二分找 t,使第 m+1 小的元素 =ax 这个时候就是 ax 被取走的时候

求区间第 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(nlogn)

线段树上二分确实是好东西

#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;
}

posted @   copper_carbonate  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示