[18.8.22]校内NOIP模拟赛
T1 无聊的数列
题意
有一单调不递增序列$x$。现定义序列$a,b$。$a_i$表示在序列$x$内比$x_i$小的数有多少个,$b_i$表示在序列$x$内比$x_i$大的数有多少个。现定义序列$c$,$c_i=(a_i,b_i)$。给出一个打乱的序列$c$,其中某些元组是错误的,求将至少要修改哪些元组,使存在序列$x$(修改一个元组的代价始终为1,$c$可随意打乱)。
$1\leq n \leq 1000$
题解
先考虑正确的序列$a$应该是什么样子的。
序列$a$一定是由数段相等的序列组成的,每一段单调递增,其中每一段值等于第一项位置-1。
对应的$x$序列一定是,在$a$序列相等位置,$x$序列对应的位置也相等(同理$a$内不等的位置$x$内也不等)。
已知每段的位置和长度后,便可以算出唯一对应的$b$的值($n-len-pos$)。
因此我们可以预处理一个数组$cnt_{i,j}$,表示输入序列内有多少个元组为$(i,j)$。
定义$dp_i$表示当前段的结尾是位置$i$,那么只需要枚举上一段的结尾,就可知道这一段的长度,便可得到此段需要的元组。使用预处理的$cnt$数组可以算出需要修改的元组数量(注意最小为0)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
x=0;
int f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)) {x=x*10+c-'0';c=getchar();}
x*=f;
}
int n;
int x[1005],y[1005];
int buk[1005][1005];
int dp[1005];
int main()
{
read(n);
for(int i=1;i<=n;i++)
{
read(x[i]);
read(y[i]);
}
for(int i=1;i<=n;i++)
if(x[i]>=0 && x[i]<=n && y[i]>=0 && y[i]<=n)
buk[x[i]][y[i]]++;
memset(dp,23,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++)
dp[i]=min(dp[i],dp[j]+max(0,i-j-buk[j][n-i]));
printf("%d\n",dp[n]);
return 0;
}
T2 金字塔
题意
给你一个$n\times m$的矩阵,求一个价值总和最大的”回“字形,输出其位置信息。
“回”字形:一个$a\times b$的矩阵内抛去一个$c \times d$的子矩阵,且子矩阵和原矩阵边不能重合。
$1\leq n,m \leq 1000$
题解
先处理出所有$c\times d$的子矩阵的和,之后对于每个$(i,j)$求以$(i,j)$作为”回“字形左上角的最小的子矩阵。因为矩阵均长宽均固定,所以用两次单调队列维护即可。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
x=0;
int f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)) {x=x*10+c-'0';c=getchar();}
x*=f;
}
int n,m,a,b,c,d;
int s[1005][1005];
int pre[1005][1005];
int s2[1005][1005];
int s3[1005][1005];
int mv1[1005][1005];
int mv1pos[1005][1005];
int mv2[1005][1005];
int mv2pos1[1005][1005];
int mv2pos2[1005][1005];
int q[1005];
int head,tail;
int main()
{
read(m);
read(n);
read(b);
read(a);
read(d);
read(c);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
read(s[i][j]);
pre[i][j]=s[i][j]+pre[i][j-1];
}
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
pre[i][j]+=pre[i-1][j];
for(int i=1;i<=n-a+1;i++)
for(int j=1;j<=m-b+1;j++)
s2[i][j]=pre[i+a-1][j+b-1]-pre[i+a-1][j-1]-pre[i-1][j+b-1]+pre[i-1][j-1];
for(int i=1;i<=n-c+1;i++)
for(int j=1;j<=m-d+1;j++)
s3[i][j]=pre[i+c-1][j+d-1]-pre[i+c-1][j-1]-pre[i-1][j+d-1]+pre[i-1][j-1];
int cn=b-d-1;
for(int i=1;i<=n;i++)
{
head=tail=0;
for(int j=1;j<=m;j++)
{
while(head<tail && q[head]<=j-cn) head++;
while(head<tail && s3[i][q[tail-1]]>=s3[i][j]) tail--;
q[tail++]=j;
mv1[i][j]=s3[i][q[head]];
mv1pos[i][j]=q[head];
}
}
cn=a-c-1;
for(int j=1;j<=m;j++)
{
head=tail=0;
for(int i=1;i<=n;i++)
{
while(head<tail && q[head]<=i-cn) head++;
while(head<tail && mv1[q[tail-1]][j]>=mv1[i][j]) tail--;
q[tail++]=i;
mv2[i][j]=mv1[q[head]][j];
mv2pos1[i][j]=q[head];
mv2pos2[i][j]=mv1pos[q[head]][j];
}
}
int ans=0;
for(int i=1;i<=n-a+1;i++)
for(int j=1;j<=m-b+1;j++)
ans=max(ans,s2[i][j]-mv2[i+a-c-1][j+b-d-1]);
for(int i=1;i<=n-a+1;i++)
for(int j=1;j<=m-b+1;j++)
if(ans==s2[i][j]-mv2[i+a-c-1][j+b-d-1])
{
printf("%d %d\n%d %d\n",j,i,mv2pos2[i+a-c-1][j+b-d-1],mv2pos1[i+a-c-1][j+b-d-1]);
return 0;
}
return 0;
}
T3 新型计算机
题意
现有一个机器从一个序列进行读入操作。
具体读入过程是这样的:每次读入一个$x$,之后读入$x$个数,直到$x$不存在。
求次数最少的修改序列方案,使机器正好读完整个序列。
具体的,修改序列为:选中序列的某一项,将其+1或-1;
$1\leq n \leq 10^6$
题解
一个显然的$n^2\ dp$是定义$dp_i$表示当前读到第$i$个位置恰好结束时的最小花费,转移显然。
可以将题目模型稍微转换一下,变成:一个人在网格上走,每次要往后跳第$a_i$格。可以花费代价来让$a_i$
变化。显而易见,$a_i$增加或减少1,相当于人跳跃后往左或往右走一格。这个的代价是1。
因此可以将此题抽象为最短路模型。每个格子之间连代价为1的双向边。对于每个$a_i$,连接第$i$格和第$i+a_i+1$格,代价为0($i+a_i+1>n$需要特殊处理)。
显然,点1不能连出边,因为不存在任何一个方法跳跃到点1然后走到别的地方(点1是初始位置)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
x=0;
int f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)) {x=x*10+c-'0';c=getchar();}
x*=f;
}
int box[1000005],las[3000005],edv[3000005],edw[3000005],cnt=0;
void adde(int u,int v,int w)
{
las[++cnt]=box[u];
box[u]=cnt;
edv[cnt]=v;
edw[cnt]=w;
}
int n;
int s[1000005];
typedef pair<long long,int> ip;
bool vis[1000005];
long long dis[1000005];
ip mp(long long a,int b)
{
return make_pair(a,b);
}
priority_queue<ip>q;
int main()
{
read(n);
for(int i=1;i<=n;i++)
{
read(s[i]);
if(i+s[i]+1<=n+1) adde(i,i+s[i]+1,0);
else adde(i,n+1,i+s[i]-n);
}
for(int i=1;i<=n;i++)
{
if(i!=1)
adde(i,i+1,1);
adde(i+1,i,1);
}
memset(dis,23,sizeof(dis));
dis[1]=0;
q.push(mp(0,1));
while(!q.empty())
{
int now=q.top().second;
q.pop();
if(vis[now]) continue;
vis[now]=1;
for(int i=box[now];i;i=las[i])
if(!vis[edv[i]] && dis[edv[i]]>dis[now]+edw[i])
{
dis[edv[i]]=dis[now]+edw[i];
q.push(mp(-dis[edv[i]],edv[i]));
}
}
printf("%lld\n",dis[n+1]);
return 0;
}