test20230911

写在前面的话

今天考试心态不好,没有得分。100+36+0+40=176,rank3T3 读题读错耗费了一个多小时,所以对本场比赛不报什么希望了。之后的考试题目还是要读清楚一点。

考场的开题顺序是顺序开题,但是在进行改题之后认为开题顺序应该是 T1T4T2T3

T1

题目描述

有一张单项选择题试卷,有 nk 个选项。考试的时候小明第 i 个选项选择了 ci 个,但是标准答案第 i 个选项选择了 ai 个。保证 ci=ai=n 。问小明期望得分是多少,一题一分。

n109,k105

思路点拨

我们注意到 n 个选项的期望得分是一样的,所以我们求出每一个选项的期望得分之后乘 n 即可。

对于第 i 个选项,一共有 n!i=1kai! 种情况,我们统计出全部可以得分的情况除以全部情况就可以了。答案就可以是:

n×(i=1k(n1)!cii=1kci!n!i=1kai!)=i=1kaicin

时间复杂度 O(k)

T4

题目描述

现在有一个长度为 n 的序列 a 。我们认为一个三元素 (i,j,k) 合法当且仅当 i<j<k 并且 jikj ,它的价值是 ai+aj+ak

现在有 q 个询问,每一次询问一个区间内权值最大的合法三元组。

n,q2×105

思路点拨

我们考虑对于一个优秀的三元组 (i,j,k) 需要满足什么条件。那就是 i<mid<j,amid>ai,amid>aj 。不然就可以用 mid 代替 ai,aj 中的较小者,同时 ji 更短,kj 不会更短,所以依然合法。

现在有了这个约束,三元组中,我们只考虑前两个元素 (i,j) ,只谈这个二元组来说的话,只有 O(n) 级别个。
这个可以感性理解一下,相信还是比较好懂。

现在我们在一个区间内,我们考虑对于一个元素 k 可以匹配到那些 (i,j) 。因为需要满足 jikj ,所以等价于 2jik 。我们令 fi 表示以 i 为三元组 (i,j,k) 中的 k 的最优答案。我们将全部的询问按照左端点从大到小离线下来,对于一个左端点 i ,我们对于二元组 (i,j) ,执行 fk=max{fk,ak+ai+aj}(2jik) ,显然可以使用线段树维护。

时间复杂度 O(nlogn) 。感觉有点抽象啊,可以参考一下代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-f;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
const int MAXN=5e5+10;
int n,q,a[MAXN];
int s[MAXN],top;
vector<int> p[MAXN];
struct node{
    int r,pos;
    node(int x=0,int y=0){
        r=x,pos=y;
    }
};
vector<node> e[MAXN];
int tmp[MAXN],t[MAXN<<2],f[MAXN<<2],tag[MAXN<<2];
void build(int i,int l,int r){
    if(l==r){
        t[i]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(i<<1,l,mid);
    build(i<<1|1,mid+1,r);
    t[i]=max(t[i<<1],t[i<<1|1]);
}
void pushup(int i){
    f[i]=max(f[i<<1],f[i<<1|1]);
}
void pushdown(int i){
    f[i<<1]=max(f[i<<1],t[i<<1]+tag[i]);
    tag[i<<1]=max(tag[i<<1],tag[i]);
    f[i<<1|1]=max(f[i<<1|1],t[i<<1|1]+tag[i]);
    tag[i<<1|1]=max(tag[i<<1|1],tag[i]);
    tag[i]=0;
}
void update(int i,int l,int r,int L,int R,int k){
    if(L<=l&&r<=R){
        f[i]=max(f[i],t[i]+k);
        tag[i]=max(tag[i],k);
        return ;
    }
    if(l>R||r<L) return ;
    int mid=(l+r)>>1;
    pushdown(i);
    update(i<<1,l,mid,L,R,k);
    update(i<<1|1,mid+1,r,L,R,k);
    pushup(i);
}
int query(int i,int l,int r,int L,int R){
    if(L<=l&&r<=R) return f[i];
    if(l>R||r<L) return 0;
    int mid=(l+r)>>1,ans;
    pushdown(i);
    ans=max(query(i<<1,l,mid,L,R),query(i<<1|1,mid+1,r,L,R));
    pushup(i);
    return ans;
}
vector<int>::iterator it;
signed main(){
	freopen("fake.in","r",stdin);
	freopen("fake.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++){
        while(top&&a[s[top]]<=a[i]){
            p[s[top]].push_back(i);
            top--;
        }
        if(top>0) p[s[top]].push_back(i);
        s[++top]=i;
    }
    q=read();
    for(int i=1;i<=q;i++){
        int l=read(),r=read();
        e[l].push_back(node(r,i));
    }
    build(1,1,n);
    for(int l=n;l;l--){
        for(it=p[l].begin();it!=p[l].end();it++){
            int pos=(*it)*2-l;
            if(pos<=n) update(1,1,n,pos,n,a[l]+a[*it]);
        }
        for(int i=0;i<e[l].size();i++){
            int r=e[l][i].r;
            tmp[e[l][i].pos]=query(1,1,n,l,r); 
        }
    }
    for(int i=1;i<=q;i++) cout<<tmp[i]<<endl;
    return 0;
}

T2

题目描述

image

n1018,c7,m104

思路点拨

考虑一个 O(n2c) 的动态规划。我们定义状态 fi 表示考虑到下标 i 的方案数。转移就是对于每一个 1kc ,枚举一个转移点 j ,如果 k|(ij) 并且 j 可以作为一个划分点,那么就转移是了。

fi=k=1cj<ifj(c|(ij),visj=1)

这个东西比较好优化,我们定义权值和数组 sumi,j 表示 k=1fk(kmodi=j) 。每一次转移的时候 fi=j=1csumj,imodj ,之后更新 sum 。可以做到 O(nc) 。这么做的问题就是 n 不可以做到 1018 ,我们没有发挥 c 比较小的优势。

我们注意到这个操作是可以以 sum 构造处一个转移矩阵的,比较麻烦但是的确可行。构造矩阵的方法很多,大家可以自己思考(并不难但是需要一点耐心),或者参考我在最后给出的代码。这个矩阵是固定的,所以只可以应对 m=0 的情况。也就是说,在 m=0 的时候我们可以做到 O(c6logn)

一个比较naive的想法就是我们对于每两个不可以放隔板的元素之间单独做一遍 dp ,时间就来到了惊人的 O(c6mlogn) ,不可以通过。我们注意到,转移有多次,并且装在矩阵是一个向量,而向量与矩阵的乘法只有 O(c4) 。我们考虑在 O(c6logn) 时间内预处理出来转移矩阵的 2i 次幂。对于每两个不可以放隔板的元素之间我们可以单独跑矩阵乘法,因为我们预处理了幂,所以我们拿幂去乘状态矩阵这个向量,时间就是 O(c4m+c6logn) ,可以通过本题。

一点细节就是两个隔板之间的转移矩阵会有细微的差异,所以我们可以构造另一个矩阵 C 来用矩阵乘向量来弥补这个差异,还是比较抽象。但是构造矩阵的方式很多并且不难,实在不会可以参考一下我的构造。

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-f;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int MAXN=1e4+10,mod=998244353;
int n,m,c,a[MAXN];
bool flag=0;
struct matrix{
	int m[30][30];
	matrix(int opt=0){
		memset(m,0,sizeof(m));
		if(opt==1){
			for(int i=0;i<30;i++)
				m[i][i]=1;
		}
	}
	matrix friend operator*(const matrix &A,const matrix &B){
		matrix C;
		if(!flag){
			for(int i=0;i<30;i++)
				for(int j=0;j<30;j++)
					for(int k=0;k<30;k++)
						C.m[i][j]=(C.m[i][j]+A.m[i][k]*B.m[k][j])%mod;
			return C;
		}
		else{
			for(int i=0;i<30;i++)
				for(int j=0;j<30;j++)
					C.m[i][0]=(C.m[i][0]+A.m[i][j]*B.m[j][0])%mod;	
		}
		return C;
	}
};
int pos[30][30],tot;
void print(matrix A){
	for(int i=0;i<=tot;i++,cout<<endl)
		for(int j=0;j<=tot;j++)
			cout<<A.m[i][j]<<" ";
	cout<<endl;
}
matrix A,B,C,e;//e表示单位矩阵 
matrix pw[100];//pw[i]表示矩阵A的2^i次方 
//A是状态矩阵,B是一般的转移矩阵,C是转移向量 
matrix qpow(matrix A,int b){
	matrix Base=A,ans;
	for(int i=0;i<=tot;i++)
		ans.m[i][i]=1;
	while(b){
		if(b&1) ans=Base*ans;
		Base=Base*Base;
		b>>=1;
	}
	return ans;
}
signed main(){
	freopen("paper.in","r",stdin);
    freopen("paper.out","w",stdout);
	n=read(),m=read(),c=read();
	for(int i=1;i<=m;i++)
		a[i]=read()+1;
	sort(a+1,a+m+1);
	m=unique(a+1,a+m+1)-a-1;
	for(int i=1;i<=c;i++)
		for(int j=0;j<i;j++)
			pos[i][j]=++tot;
	for(int i=1;i<=c;i++){
		for(int j=0;j<i;j++){
			if(j<i-1) B.m[pos[i][j]][pos[i][j+1]]=1;
			else B.m[pos[i][j]][pos[i][0]]=1;
		}
		B.m[pos[i][i-1]][0]=1;
		B.m[0][pos[i][i==1?0:1]]=1;
	}
	A.m[0][0]=B.m[0][0]=1;
	pw[0]=B;
	for(int i=1;i<=64;i++)
		pw[i]=pw[i-1]*pw[i-1];
	for(int i=1;i<=c;i++){
		for(int j=0;j<i;j++){
			if(j<i-1) C.m[pos[i][j]][pos[i][j+1]]=1;
			else C.m[pos[i][j]][pos[i][0]]=1;
		}
		C.m[pos[i][i-1]][0]=1;
	}
	int pre=0;
	flag=1;
	for(int i=1;i<=m&&a[i]<n;i++){
		int b=a[i]-pre-1;
		for(int j=0;j<62;j++)
			if(b&(1ll<<j))
				A=pw[j]*A;
		A=C*A;
		pre=a[i];
	}
	int b=n-pre;
	for(int j=0;j<62;j++)
		if(b&(1ll<<j))
			A=pw[j]*A;
	cout<<A.m[0][0];
	return 0;
}
posted @   Diavolo-Kuang  阅读(18)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示