2023.8.16 普及模拟1
众所周知,要有一张头图。
某些注意事项
这次比赛呢,就很无语……
\(total=50pts+80pts+80pts+\)"\(0\)"\(pts\)
题目感觉除了T1都好做,其它的题分数都错的很冤。就当教训吧。
T1 Past
题面
给出 \(n,d\) ,和序列 \(a\).
- 当 \(d=1\) 时,求所有的子区间和。
- 当 \(d=2\) 时,求所有子区间中最大的极差。
- 当 \(d=3\) 时,求所有子区间的平均数之和。
思路
部分分
前一半分还是好做的(考场也只得了这些分……)
只要暴力 \(O(n^3)\) 的算法就行,当然如果使用 ST算法 或 其他优化算法 可得 \(60\) 分或更高。
\(100pts\)
还有一些需要注意的细节,如要把负数转正,模数时要注意。
赛时代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+10,mod=1336363663;
int maxx,minn,n,d,a[N],sum[N],b[N],ans,jc,tot=1;
double pj;
inline int read(){
int x=0;bool f=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
return f?-x:x;
}
signed main(void){
freopen("pst.in","r",stdin);
freopen("pst.out","w",stdout);
n=read();d=read();
for(int i=1;i<=n;++i){
a[i]=read();
sum[i]=(sum[i-1]+a[i])%mod;
}
if(!d) return 0;
if(d==1){
for(int i=1;i<=n;++i){
for(int l=1;l+i-1<=n;++l){
int r=l+i-1;
ans=(ans+sum[r]-sum[l-1])%mod;
}
}
printf("%lld",ans);
}
if(d==2){
for(int i=1;i<=n;++i){
for(int l=1;l+i-1<=n;++l){
int r=l+i-1;
maxx=-0x3f3f3f3f,minn=0x3f3f3f3f;
ans=(ans+sum[r]-sum[l-1])%mod;
for(int k=l;k<=r;++k){
maxx=maxx>a[k]?maxx:a[k];
minn=minn<a[k]?minn:a[k];
}
jc=(jc+maxx-minn)%mod;
}
}
printf("%lld\n%lld",ans,jc);
}
if(d==3){
for(int i=1;i<=n;++i){
tot=tot*i%mod;
for(int l=1;l+i-1<=n;++l){
int r=l+i-1;
maxx=-0x3f3f3f3f,minn=0x3f3f3f3f;
ans=(ans+sum[r]-sum[l-1])%mod;
for(int k=l;k<=r;++k){
maxx=maxx>a[k]?maxx:a[k];
minn=minn<a[k]?minn:a[k];
}
jc=(jc+maxx-minn)%mod;
pj=pj+(double)(1.0*(sum[r]-sum[l-1])/i);
}
}
printf("%lld\n%lld\n",ans,jc);
cout<<(int)(pj*1.0*tot)%mod;
}
return 0;
/*
3 3
3 2 5
*/
}
赛后代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+10,mod=1336363663;
int maxx,minn,n,d,a[N],sum[N],b[N],ans,jc,tot,pp;
int o[N],f1[N],f2[N];
double pj;stack<int>q,p;
inline int read(){
int x=0;bool f=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
return f?-x:x;
}
signed main(void){
freopen("pst.in","r",stdin);
freopen("pst.out","w",stdout);
n=read();d=read();
f1[0]=f2[n+1]=1;
for(int i=1;i<=n;++i){
a[i]=read();
sum[i]=(sum[i-1]+a[i])%mod;
f1[i]=f1[i-1]*i%mod;
}
for(int i=n;i;--i){
f2[i]=f2[i+1]*i%mod;
}
if(!d) return 0;
if(d==1){
for(int i=1;i<=n;++i)
ans=(ans+a[i]*i%mod*(n-i+1))%mod;
//它前面的和后面的
printf("%lld",ans);
}
if(d==2){
for(int i=1;i<=n;++i) ans=(ans+a[i]*i%mod*(n-i+1)%mod)%mod;
a[n+1]=LLONG_MAX;
for(int x,i=1;i<=n+1;++i){
while(!q.empty()&&a[q.top()]<=a[i]){
x=q.top();q.pop();
jc=(jc+a[x]*((i-o[x]-1)%mod+(int)(x-o[x]-1)*(i-x-1)%mod+mod)%mod)%mod;
}
if(q.empty()) o[i]=0;
else o[i]=q.top();
q.push(i);
}
stack<int>().swap(q);
a[n+1]=-114154;
for(int x,i=1;i<=n+1;++i){
while(!q.empty()&&a[q.top()]>=a[i]){
x=q.top();q.pop();
jc=(jc-a[x]*((i-o[x]-1)%mod+1ll*(x-o[x]-1)*(i-x-1)%mod+mod)%mod+mod)%mod;
}
if(q.empty()) o[i]=0;
else o[i]=q.top();
q.push(i);
}
printf("%lld\n%lld",ans,jc);
}
if(d==3){
for(int i=1;i<=n;++i) ans=(ans+a[i]*i%mod*(n-i+1)%mod)%mod;
a[n+1]=LLONG_MAX;
for(int x,i=1;i<=n+1;++i){
while(!q.empty()&&a[q.top()]<=a[i]){
x=q.top();q.pop();
jc=(jc+a[x]*((i-o[x]-1)%mod+(int)(x-o[x]-1)*(i-x-1)%mod+mod)%mod)%mod;
}
if(q.empty()) o[i]=0;
else o[i]=q.top();
q.push(i);
}
stack<int>().swap(q);
a[n+1]=-114154;
for(int x,i=1;i<=n+1;++i){
while(!q.empty()&&a[q.top()]>=a[i]){
x=q.top();q.pop();
jc=(jc-a[x]*((i-o[x]-1)%mod+1ll*(x-o[x]-1)*(i-x-1)%mod+mod)%mod+mod)%mod;
}
if(q.empty()) o[i]=0;
else o[i]=q.top();
q.push(i);
}
for(int i=0;i<n;++i){
pp=(pp+sum[n-i]-sum[i]+mod)%mod;
tot=(tot+pp*f1[i]%mod*f2[i+2])%mod;
}
printf("%lld\n%lld\n%lld",ans,jc,tot);
}
return 0;
/*
3 3
3 2 5
*/
}
T2 Present
题面
模拟双端队列题,\(5\) 种操作。
- 往前出入一个数
- 查询前面第一个数
- 往后出入一个数
- 查询后面第一个数
- 翻转数列
思路
水题,唯一需要注意的是不要暴力翻转数列,可以拿一个变量记录是否反转,在做。(没想到这一点,悲)
赛时代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
deque<int>q;
int n,id,a[N];
signed main(void){
freopen("prs.in","r",stdin);
freopen("prs.out","w",stdout);
scanf("%d%d",&n,&id);
for(int opt,x,o,i=1;i<=n;++i){
scanf("%d",&opt);
if(opt==0){
scanf("%d",&x);
q.push_front(x);
}
if(opt==1){
if(!q.size()){
cout<<0<<endl;
}
else{
cout<<q.front()<<endl;
q.pop_front();
}
}
if(opt==2){
scanf("%d",&x);
q.push_back(x);
}
if(opt==3){
if(!q.size()){
cout<<0<<endl;
}
else{
cout<<q.back()<<endl;
q.pop_back();
}
}
if(opt==4){
o=q.size();
for(int i=1;i<=o;++i){
a[i]=q.front();
q.pop_front();
}
for(int i=o;i;--i){
q.push_back(a[i]);
a[i]=0;
}
}
}
return 0;
/*
8 0
1
2 111
0 222
4
0 333
3
1
3
*/
}
赛后代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
deque<int>q;bool pd;
int n,id,a[N];
signed main(void){
freopen("prs.in","r",stdin);
freopen("prs.out","w",stdout);
scanf("%d%d",&n,&id);
for(int opt,x,o,i=1;i<=n;++i){
scanf("%d",&opt);
if(opt==0){
scanf("%d",&x);
if(!pd) q.push_front(x);
else q.push_back(x);
}
if(opt==1){
if(!q.size()) puts("0");
else{
if(!pd){
cout<<q.front()<<endl;
q.pop_front();
}
else{
cout<<q.back()<<endl;
q.pop_back();
}
}
}
if(opt==2){
scanf("%d",&x);
if(!pd) q.push_back(x);
if(pd) q.push_front(x);
}
if(opt==3){
if(!q.size()) puts("0");
else{
if(!pd){
cout<<q.back()<<endl;
q.pop_back();
}
else{
cout<<q.front()<<endl;
q.pop_front();
}
}
}
if(opt==4){
if(pd) pd=0;
else pd=1;
}
}
return 0;
/*
8 0
1
2 111
0 222
4
0 333
3
1
3
*/
}
T3 Future
题面
一棵树,问从根节点(编号为 \(1\) 的节点)到叶子结点的最大距离(叶子结点任意,不过要是最长距离),边都是有向的。
思路
法一:SPFA 跑最长路
只需注意两点,把点权变成边权,初始值负无穷(没注意到为 \(90\) 分)。赛时刚开始是这么写的,但又想这毕竟是死掉的算法,就换成了树形dp。
code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10;
int ans=-0x3f3f3f3f,n,h[N],cnt,s[N],d[N],a[N];
struct node{int to,nxt,w;}e[N];
inline void add(int x,int y,int z){
e[++cnt].to=y;e[cnt].nxt=h[x];e[cnt].w=z;
h[x]=cnt;
}
queue<int>q;bool vis[N];
inline void spfa(){
memset(a,0x3f,sizeof a);
memset(vis,0,sizeof vis);
q.push(1);a[1]=s[1];vis[1]=1;
while(!q.empty()){
int u=q.front();q.pop();vis[u]=false;
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(a[v]>a[u]+e[i].w){
a[v]=a[u]+e[i].w;
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
return ;
}
signed main(void){
freopen("ftr.in","r",stdin);
freopen("ftr.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",s+i);
for(int x,i=1;i<=n;++i){
scanf("%lld",&x);
add(x,i,s[i]);
++d[x];
}
spfa();
for(int i=1;i<=n;++i){
if(!d[i]){
ans=max(ans,a[i]);
}
}
printf("%lld\n",ans);
/*
5
1 2 3 4 5
0 1 2 3 2
*/
}
法二:树形dp
板子,不多说,注意初始值,和判叶子结点。
code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10;
int ans,n,h[N],cnt,s[N],d[N],a[N],dp[N];
struct node{int to,nxt,w;}e[N];
inline void add(int x,int y,int z){
e[++cnt].to=y;e[cnt].nxt=h[x];e[cnt].w=z;
h[x]=cnt;
}
// queue<int>q;bool vis[N];
// inline void spfa(){
// memset(a,0x3f,sizeof a);
// memset(vis,0,sizeof vis);
// q.push(1);a[1]=s[1];vis[1]=1;
// while(!q.empty()){
// int u=q.front();q.pop();vis[u]=false;
// for(int i=h[u];i;i=e[i].nxt){
// int v=e[i].to;
// if(a[v]>a[u]+e[i].w){
// a[v]=a[u]+e[i].w;
// if(!vis[v]){
// q.push(v);
// vis[v]=1;
// }
// }
// }
// }
// return ;
// }
inline void dfs(int x,int fa){
if(!h[x]) dp[x]=0;
for(int i=h[x];i;i=e[i].nxt){
int v=e[i].to;
dfs(v,x);
dp[x]=max(dp[x],dp[v]);
}
dp[x]=dp[x]+s[x];
// cout<<x<<" "<<dp[x]<<endl;
}
signed main(void){
freopen("ftr.in","r",stdin);
freopen("ftr.out","w",stdout);
memset(dp,-0x3f,sizeof dp);
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",s+i);
for(int x,i=1;i<=n;++i){
scanf("%lld",&x);
add(x,i,s[i]);
++d[x];
}
// spfa();
// for(int i=1;i<=n;++i){
// if(!d[i]){
// ans=max(ans,a[i]);
// }
// }
dfs(1,0);
printf("%lld\n",dp[1]);
/*
5
1 2 3 4 5
0 1 2 3 2
*/
}
法3:拓扑优化dp,弱化版缩点
奇奇怪怪的做法捏……
T4 Beyond
题面太长,就不放了。
思路
meet in middle ,分别从 \((1,1)\) (往 对角线上的点 搜索) 和 对角线上的点(往 \((n,n)\) 搜索) 进行爆搜,当到达当下区域时,将所得到的值分别用两个 map 进行存储,又因为在当下区域可以进行传送至另一个当下区域,然后利用加法原理和乘法原理求解。
注意:当遍历 map 时用了range-based for + auto,请注意使用 C++ 14 -O2 ,不然会 CE(血的教训)。
(range-based for+auto
)+(C++ -O2
)+(\(AC\) 代码) = "\(0\)" 分
赛时( \(AC\) )代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100;
const int dx[]={1,0};
const int dy[]={0,1};
int ans,n,F,a[N][N],dp[N][N];
map<int,int>p,q;
bool vis[N][N];
inline int read(){
int x=0;bool f=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
return f?-x:x;
}
inline void dss(int x,int y,int cnt){
if(x+y-1==n){
++q[cnt];
return ;
}
for(int i=0;i<2;++i){
int xx=x+dx[i],yy=y+dy[i];
if(xx<=n&&y<=n&&x+y-1<=n)
dss(xx,yy,cnt^a[xx][yy]);
}
}
inline void dff(int x,int y,int cnt){
if(x==n&&y==n){
++p[cnt];
return ;
}
for(int i=0;i<2;++i){
int xx=x+dx[i],yy=y+dy[i];
if(xx<=n&&y<=n)
dff(xx,yy,cnt^a[xx][yy]);
}
}
signed main(void){
freopen("byd.in","r",stdin);
freopen("byd.out","w",stdout);
n=read();F=read();
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
a[i][j]=read();
}
}
// if(n==1){
// if(a[1][1]+F==a[1][1]) ++ans;
// printf("%lld\n",ans);
// }
// else if(n==2){
// int x=a[1][1]^a[1][2],y=a[1][1]^a[2][1];
// int z=x^a[2][2],g=y^a[2][2];
// if(x+F==z) ans+=2;
// if(y+F==g) ans+=2;
// printf("%lld\n",ans);
// }
// else{
dss(1,1,0);
for(int i=1;i<=n;++i) dff(i,n-i+1,0);
for(auto i:p){
int o=i.first-F;
if(q.find(o)!=q.end()) ans+=i.second*q[o];
}
printf("%lld\n",ans);
// }
return 0;
}
总结
选好编译环境,看清呐,我还以为学校OJ的 C++ -O2 默认 C++14 呢,还要注意文件输入输出。