Akane-Weekly #3NOIP 十连测(3)
Akane-Weekly #3:NOIP 十连测(3)
有质量的比赛。
B.
简要题意:
有一个 \([1,n]\) 的排列,每次有三种操作:
- 整个序列向左循环移动一位,
- 整个序列向右循环移动一位,
- 在 \([1,m]\) 中选一位,把数加入 \(b\) 数组里面。
求至少进行多少次操作 \(1,2\) ,使数组 \(b\) 恰好为 \(1\) 到 \(n\) 。
Solution:
发现一个贪心,最优解满足每一步都要最优。
考虑 \(dp\) 转移, \(dp[i][0/1]\) 表示当前取到第 \(i\) 个数,取完了之后窗口左/右侧在 \(a[i]\) 的位置上时,最小的方案。
但这样不是最优的,因为有这样一种情况:前面的 \(i\) 转移完之后,后面可能又连续的一些数,满足正好在 \(i\) 的窗口里,不用移动。
考虑每得到 \(dp\) 状态之后往右推,直接转移到下一个不在窗口中的数。
考虑预处理 “不在窗口中的数”,可以值域线段树维护,详见代码。
线段树存区间最小的不在当前窗口的数,时间复杂度 \(O(n \log n)\)。
Code:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000010];
int sum[2000010],indl[500010];
int qd[500010][2];
long long fdy[500010];
long long dp[500010][2];// 0:start 1:end
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0',ch=getchar();
}
return x*f;
}
void pushup(int id){
sum[id]=min(sum[id<<1],sum[id<<1|1]);
}
void build(int id,int l,int r){
if(l==r){
sum[id]=n+1;
return;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
pushup(id);
}
void update(int id,int l,int r,int x,int v){
if(l==r){
indl[l]=v;
if(indl[l]>0){
sum[id]=l;
}else{
sum[id]=n+1;
}
}else{
int mid=(l+r)>>1;
if(x<=mid) update(id<<1,l,mid,x,v);
else update(id<<1|1,mid+1,r,x,v);
pushup(id);
}
}
int cur,curp;
void query(int id,int l,int r,int x,int y){
if(x>y||l>r){
return;
}
if(x<=l&&r<=y){
cur=min(cur,sum[id]);
return;
}
int mid=(l+r)>>1;
if(x<=mid) query(id<<1,l,mid,x,y);
if(y>mid) query(id<<1|1,mid+1,r,x,y);
}
int pre;
signed main(){
n=read(),m=read();
if(n==m){
printf("0");
return 0;
}
for(int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i],fdy[a[i]]=i;
build(1,1,n);
for(int i=m+1;i<=n;i++) update(1,1,n,a[i],1);
int l,r;
for(int i=m;i<=n+m-1;i++){
l=a[i-m+1],r=n,cur=n+1;
query(1,1,n,l,r);
qd[a[i-m+1]][0]=cur-1;
update(1,1,n,a[i-m+1],1),update(1,1,n,a[i+1],0);
}
build(1,1,n);
for(int i=m+1;i<=n;i++) update(1,1,n,a[i],1);
for(int i=m;i<=n+m-1;i++){
l=a[i],r=n,cur=n+1;
query(1,1,n,l,r);
qd[a[i]][1]=cur-1;
update(1,1,n,a[i-m+1],1),update(1,1,n,a[i+1],0);
}
memset(dp,0x3f,sizeof dp);
for(int i=1;i<=n;i++){
if(fdy[i]>m){
pre=i;
break;
}
}
long long inf=dp[0][0];
long long ans=inf;
long long sm,bg,tp;
dp[pre][0]=min(fdy[pre]-1,1+n-fdy[pre]);
dp[pre][1]=min(fdy[pre]-m,m+n-fdy[pre]);
for(int i=1;i<=n;i++){
if(dp[i][0]!=inf){
if(qd[i][0]==n) ans=min(ans,dp[i][0]);
else{
sm=min(fdy[qd[i][0]+1],fdy[i]);
bg=max(fdy[qd[i][0]+1],fdy[i]);
dp[qd[i][0]+1][0]=min(dp[qd[i][0]+1][0],min(dp[i][0]+bg-sm,dp[i][0]+sm+n-bg));
tp=fdy[qd[i][0]+1]-m+1;
if(tp<=0) tp+=n;
sm=min(tp,fdy[i]);
bg=max(tp,fdy[i]);
dp[qd[i][0]+1][1]=min(dp[qd[i][0]+1][1],min(dp[i][0]+bg-sm,dp[i][0]+sm+n-bg));
}
}
if(dp[i][1]!=inf){
if(qd[i][1]==n) ans=min(ans,dp[i][1]);
else{
sm=min(fdy[qd[i][1]+1],fdy[i]);
bg=max(fdy[qd[i][1]+1],fdy[i]);
dp[qd[i][1]+1][1]=min(dp[qd[i][1]+1][1],min(dp[i][1]+bg-sm,dp[i][1]+sm+n-bg));
tp=fdy[qd[i][1]+1]+m-1;
if(tp>n) tp-=n;
sm=min(tp,fdy[i]);
bg=max(tp,fdy[i]);
dp[qd[i][1]+1][0]=min(dp[qd[i][1]+1][0],min(dp[i][1]+bg-sm,dp[i][1]+sm+n-bg));
}
}
}
ans=min(ans,min(dp[n][0],dp[n][1]));
printf("%lld\n",ans);
C.
最神仙的一题,推了一整节音乐课。
简要题意:
给你一个有向图,让你求 \(1-n-1\) 的最短路径,重复边路径的费用只算一次。
\(1 \le n \le 200\) 。
Solution:(80 pts)
考虑我们最后会走哪些路径。
发现会张成这个样子:

黑色的是我们去的时候走的路径,红色的边是逐步往上跳,然后重复经过蓝色的边(免费)。
我们可以根据这个设计出一种比较暴力的算法。
令 \(dp[u][v]\) ,表示我们构造了一条 \(1-v\) 的黑色路径,下一级红色的边将跳到 \(u\) 然后回来的时候将从 \(v\) 像上一级的点跳的最小花费。
转移也不难写,设从 \(dp[u][v]\) 转移至 \(dp[i][j]\) ,有:

如图,黑色的是 \(dp[u][v]\) 包含的边,红色和绿色的是加的边。
那么怎么进行 \(dp\) 的过程呢,我们可以用类似 \(dijkstra\) 的思路,每次取最小的作为 \(u,v\) 进行更新。
预处理 \(dis\) ,则可以在 \(O(n^4\log n)\) 时间内解决问题,结合特殊性质分可以得到 $80 $ pts.
Solution:(100 pts)
为啥刚刚那个能得 80?因为它离正解很近了。
考虑转移的过程,一次枚举两个点 \(i,j\) 太麻烦了,考虑只枚举一个点 \(i\) 。现在考虑怎么利用它来更新状态。
我们首先可以更新到 \(dp[u][i]\) ,方程为 \(dp[u][i]=\min(dp[u][i],dp[u][v]+dis[v][i])\) ,因为从下面转上来的点还在 \(u\) ,我们只需要修改一下状态,把往上转的点为 \(v\) 改成为 \(u,v\) 中的某个点即可。这样的话就可以这样转移了。
我们还可以更新 \(dp[v][i]\) ,这个很直接,加上 \((v,i),(i,u)\) 即可实现。
\(dp[v][i]=min(dp[v][i],dp[u][v]+dis[v][i]+dis[i][u])\) 。
时间复杂度 \(O(n^3\log n)\) 。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int dp[220][220];
int ans;
bool vis[220][220];
struct node{
int w,u,v;
bool operator <(const node &a) const{
return a.w<w;
}
};
priority_queue<node> pq;
inline int read(){
int x=0,f=1;
char ch;
ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0',ch=getchar();
}
return x*f;
}
int dis[220][220];
void Floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
}
int x;
signed main(){
n=read();
memset(dis,0x3f,sizeof dis);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
x=read();
if(i==j||x!=0) dis[i][j]=x;
}
}
Floyd();
memset(dp,0x3f,sizeof dp);
ans=dp[1][1];
dp[1][1]=0;
pq.push(node{0,1,1});
while(!pq.empty()){
node tmp=pq.top();
pq.pop();
int u=tmp.u,v=tmp.v;
if(vis[u][v]==1) continue;
vis[u][v]=1;
for(int i=1;i<=n;i++){
if(dp[v][i]>dis[v][i]+dis[i][u]+dp[u][v]){
dp[v][i]=dis[v][i]+dis[i][u]+dp[u][v];
pq.push(node{dp[v][i],v,i});
}
if(dp[u][i]>dis[v][i]+dp[u][v]){
dp[u][i]=dis[v][i]+dp[u][v];
pq.push(node{dp[u][i],u,i});
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) ans=min(ans,dp[i][j]+dis[j][n]+dis[n][i]);
}
cout<<ans<<endl;
}
D.
不太难。
简要题意:
给你一个左右各 \(n\) 个点的二部图,让你连不多于 \(m\) 条边,使得对于 \(\forall k \in [1,n]\) 该二部图最大匹配为 \(k\) 时权值和的最大值。
对于一个二部图,它的权值定义为 \(\sum a[i][deg_i]\) ,\(deg_i\) 为点 \(i\) 的度数。
\(n \le 30,m\le 60\) 。
Solution:
一开始我们的思路是爆搜,每次一条一条的加边,为了判断最大匹配正好为 \(n\) ,我们在判断时会用到给点染色。
将每一条匹配边中选恰好一个点加入点覆盖中,只有在点覆盖的点能够连非匹配边。
所以我们的点有四种:
1.点覆盖中的点
2.匹配边上的不在点覆盖中的点
3.非匹配边上的不在点覆盖中的点
4.没有连边的点。
边也有三种:
1.匹配边
2.连接两个 \(2\) 类点的边
3.连接一个 \(1\) 类点一个 \(3\) 类点的边。
我们发现可以将二部图的左右部分开考虑。
设我们做出了左部里面总共连了 \(u_0\) 条边,有 \(v_0\) 条左部点是 \(2\) 类点的非匹配边,\(j_0\) 条匹配边,\(k_0\) 个二类点的最大权值和,右部类似。
我们发现当
时即可。
(最后一条是大于因为一些边可能左右都是 \(2\) 类点,要容斥掉)。
那我们直接两个部分分开 \(dp\) 即可,要记得滚动数组。
dp[cur][j][k][u+d][v]=max(dp[cur][j][k][u+d][v],dp[pre][j][k][u][v]+a[i][d]);
if(d) dp[cur][j+1][k][u+d][v]=max(dp[cur][j+1][k][u+d][v],dp[pre][j][k][u][v]+a[i][d]);
if(d) dp[cur][j+1][k+1][u+d][v+d]=max(dp[cur][j+1][k+1][u+d][v+d],dp[pre][j][k][u][v]+a[i][d]);
时间复杂度 \(O(n^3m^3)\) ,可以通过。
Code:
#include<bits/stdc++.h>
using namespace std;
int a[110][110];
int dp[2][35][35][65][65];//
int dp2[2][35][35][65][65];
int n,m;
inline int read(){
int x=0,f=1;
char ch;
ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0',ch=getchar();
}
return x*f;
}
int main(){
n=read(),m=read();
for(int i=1;i<=2*n;i++){
for(int j=0;j<=m;j++){
a[i][j]=read();
}
}
memset(dp,~0x3f,sizeof dp);
memset(dp2,~0x3f,sizeof dp2);
dp[0][0][0][0][0]=0,dp2[0][0][0][0][0]=0;
for(int i=1;i<=n;i++){
int cur=i&1,pre=cur^1;
memset(dp[cur],~0x3f,sizeof dp[cur]);
for(int j=0;j<=i;j++){
for(int k=0;k<=j;k++){
for(int u=j;u<=m;u++){
for(int v=0;v<=u;v++){
for(int d=0;d<=m-u;d++){
//匹配边选一个覆盖,覆盖点才能连非匹配边。
//d 第 i 个点增加的。
//u 前面总共连得边
//k 前面的在匹配边上的覆盖的点的个数
//j 前面连的匹配边
//v 前面的在费匹配边覆盖的点的个数
dp[cur][j][k][u+d][v]=max(dp[cur][j][k][u+d][v],dp[pre][j][k][u][v]+a[i][d]);
if(d) dp[cur][j+1][k][u+d][v]=max(dp[cur][j+1][k][u+d][v],dp[pre][j][k][u][v]+a[i][d]);
if(d) dp[cur][j+1][k+1][u+d][v+d]=max(dp[cur][j+1][k+1][u+d][v+d],dp[pre][j][k][u][v]+a[i][d]);
}
}
}
}
}
}
for(int i=1;i<=n;i++){
int cur=i&1,pre=cur^1;
memset(dp2[cur],~0x3f,sizeof dp2[cur]);
for(int j=0;j<=i;j++){
for(int k=0;k<=j;k++){
for(int u=j;u<=m;u++){
for(int v=0;v<=u;v++){
for(int d=0;d<=m-u;d++){
//匹配边选一个覆盖,覆盖点才能连非匹配边。
//d 第 i 个点增加的。
//u 前面总共连得边
//k 前面的在匹配边上的覆盖的点的个数
//j 前面连的匹配边
//v 前面的在非匹配边以覆盖点为开始的个数。
dp2[cur][j][k][u+d][v]=max(dp2[cur][j][k][u+d][v],dp2[pre][j][k][u][v]+a[i+n][d]);
if(d) dp2[cur][j+1][k][u+d][v]=max(dp2[cur][j+1][k][u+d][v],dp2[pre][j][k][u][v]+a[i+n][d]);
if(d) dp2[cur][j+1][k+1][u+d][v+d]=max(dp2[cur][j+1][k+1][u+d][v+d],dp2[pre][j][k][u][v]+a[i+n][d]);
}
}
}
}
}
}
int ans=0;
for(int k=1;k<=n;k++){
ans=0;
for(int i=0;i<=k;i++){
for(int e=0;e<=m;e++){
for(int x=0;x<=m;x++){
for(int y=0;y<=m;y++){
if(e<=x+y){
if(dp[n&1][k][i][e][x]>=0&&dp2[n&1][k][k-i][e][y]>=0){
ans=max(ans,dp[n&1][k][i][e][x]+dp2[n&1][k][k-i][e][y]);
}
}
}
}
}
}
printf("%d ",ans);
}
}
end

浙公网安备 33010602011771号