test20230911

写在前面的话

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

考场的开题顺序是顺序开题,但是在进行改题之后认为开题顺序应该是 \(T1-T4-T2-T3\)

\(T1\)

题目描述

有一张单项选择题试卷,有 \(n\)\(k\) 个选项。考试的时候小明第 \(i\) 个选项选择了 \(c_i\) 个,但是标准答案第 \(i\) 个选项选择了 \(a_i\) 个。保证 \(\sum c_i=\sum a_i =n\) 。问小明期望得分是多少,一题一分。

\(n \leqslant 10^9,k \leqslant 10^5\)

思路点拨

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

对于第 \(i\) 个选项,一共有 \(\dfrac{n!}{\sum_{i=1}^k a_i!}\) 种情况,我们统计出全部可以得分的情况除以全部情况就可以了。答案就可以是:

\[n\times (\dfrac{\sum_{i=1}^k \frac{(n-1)!c_i}{\sum_{i=1}^k c_i!}}{\frac{n!}{\sum_{i=1}^k a_i!}})=\sum_{i=1}^k \dfrac{a_ic_i}{n} \]

时间复杂度 \(O(k)\)

\(T4\)

题目描述

现在有一个长度为 \(n\) 的序列 \(a\) 。我们认为一个三元素 \((i,j,k)\) 合法当且仅当 \(i<j<k\) 并且 \(j-i \leqslant k-j\) ,它的价值是 \(a_i+a_j+a_k\)

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

\(n,q \leqslant 2\times 10^5\)

思路点拨

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

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

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

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

#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

\(n \leqslant 10^{18},c \leqslant 7,m \leqslant 10^4\)

思路点拨

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

\[f_i = \sum_{k=1}^c \sum_{j<i} f_j(c|(i-j),vis_j=1) \]

这个东西比较好优化,我们定义权值和数组 \(sum_{i,j}\) 表示 \(\sum_{k=1}f_k(k \bmod i=j)\) 。每一次转移的时候 \(f_i=\sum_{j=1}^c sum_{j,i \bmod j}\) ,之后更新 \(sum\) 。可以做到 \(O(nc)\) 。这么做的问题就是 \(n\) 不可以做到 \(10^{18}\) ,我们没有发挥 \(c\) 比较小的优势。

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

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

一点细节就是两个隔板之间的转移矩阵会有细微的差异,所以我们可以构造另一个矩阵 \(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 @ 2023-09-13 08:21  Diavolo-Kuang  阅读(14)  评论(2编辑  收藏  举报