[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;
}

posted @ 2018-08-22 18:31  ranwen  阅读(217)  评论(0编辑  收藏  举报