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!}\) 种情况,我们统计出全部可以得分的情况除以全部情况就可以了。答案就可以是:
时间复杂度 \(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\)
题目描述
\(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\) 可以作为一个划分点,那么就转移是了。
这个东西比较好优化,我们定义权值和数组 \(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;
}