2018.10.15 NOIP2018个人训练赛第一场总结与题解

题目来源:https://www.jisuanke.com/contest/777?view=challenges


T1

纯暴力时间复杂度 O ( n 2 k 2 ) O(n^2k^2) 显然不可取,注意到我们可以通过前缀和优化掉一个 k k ,最后的时间复杂度 O ( n 2 k ) O(n^2k)

由于题目本身难度不大,所以在这里并不直接给出相关模拟推导。

数据有一点卡常。

被卡掉的 90 90 分代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define repl(i,x,y) for(int i=(x);i<(y);i++)
#define repd(i,x,y) for(int i=(x);i>=(y);i--)
using namespace std;

const int N=2e3+5;

int n,k,ans,mp[N][N],sum[N][N];

char buf[1<<20],*p1,*p2;
#define getchar (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
inline int read() {
	int x=0;char ch=getchar;bool f=0;
	while(ch>'9'||ch<'0'){if(ch=='-')f=1;ch=getchar;}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar;}
	return f?-x:x;
}

int main() {
	n=read(),k=read();
	
	rep(i,1,n) rep(j,1,n) {
		mp[i][j]=read();
		sum[i][j]=sum[i][j-1]+mp[i][j];
	}
		
	rep(i,1,n) rep(j,1,n) {
		int ret=0;
		repd(p,i,i+1-k) {
			if(p>=1) {
				int dlt=p-(i+1-k);
				int x=j-dlt,y=j+dlt;
				ret+=sum[p][y<n?y:n];
				ret-=sum[p][x-1<0?0:x-1];
			} else break;
		}
		rep(p,i+1,i+k-1) {
			if(p<=n) {
				int dlt=(i+k-1)-p;
				int x=j-dlt,y=j+dlt;
				ret+=sum[p][y<n?y:n];
				ret-=sum[p][x-1<0?0:x-1];
			} else break;
		} 
		
		ans=max(ans,ret);
	}
	
	printf("%d",ans);

	return 0;
}

可以通过的 A C AC 代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define repl(i,x,y) for(int i=(x);i<(y);i++)
#define repd(i,x,y) for(int i=(x);i>=(y);i--)
using namespace std;

const int N=2e3+5;

int n,k,ans,mp[N][N],sum[N][N];

char buf[1<<20],*p1,*p2;
#define getchar (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
inline int read() {
	int x=0;char ch=getchar;bool f=0;
	while(ch>'9'||ch<'0'){if(ch=='-')f=1;ch=getchar;}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar;}
	return f?-x:x;
}

int main() {
//	freopen("A.in","r",stdin);
	
	n=read(),k=read();
	
	rep(i,1,n) rep(j,1,n) {
		mp[i][j]=read();
		sum[i][j]=sum[i][j-1]+mp[i][j];
	}
		
	rep(i,1,n) rep(j,1,n) {
		int ret=sum[i][min(n,j+k-1)]-sum[i][max(0,j-k)];
		
		repl(p,1,k) {
			int x=max(j-k+p,0),y=min(j+k-p-1,n);
			if(i+p<=n) ret+=(sum[i+p][y]-sum[i+p][x]);
			if(i-p>=1) ret+=(sum[i-p][y]-sum[i-p][x]);
		}
		
		ans=max(ans,ret);
	}
	
	printf("%d",ans);

	return 0;
}

T2

设当前节点为 x x ,它的父亲节点为 f a [ x ] fa[x] ,它与父亲节点的边的权值为 e d g e [ x ] edge[x] ,以 x x 为根的子树的结点个数为 s i z e [ x ] size[x]

我们考虑一下 e d g e [ x ] edge[x] 对于答案的贡献。

若以 x x 为根的子树中存在一点 p p ,则 p p 与该子树外的结点经过 e d g e [ x ] edge[x] 的次数就等于该子树外的结点数量 n s i z e [ x ] n-size[x] ,所以对于答案的贡献为 e d g e [ x ] ( n s i z e [ x ] ) edge[x]*(n-size[x]) ,而在以 x x 为根的子树当中有 s i z e [ x ] size[x] 个这样的结点,所以我们最后得到 e d g e [ x ] edge[x] 对于答案的贡献为:
a n s + = e d g e [ x ] s i z e [ x ] ( n s i z e [ x ] ) ans+=edge[x]*size[x]*(n-size[x])

然后我们再考虑修改后的影响。

修改一条边的权值,无非就是去掉之前权值对于答案的影响,再加上新权值对于答案的影响:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define rep(i,x,y) for(ll i=(x);i<=(y);i++)
#define red(i,x,y) for(ll i=(x);i>=(y);i--)
using namespace std;

const ll N=1e5+5;

ll n,m,ans,fa[N],edge[N],size[N]; 

inline ll read() {
	ll x=0;char ch=getchar();bool f=0;
	while(ch>'9'||ch<'0'){if(ch=='-')f=1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return f?-x:x;
}

int main() {
	n=read();
	
	rep(x,2,n) {
		ll y=read(),z=read();
		fa[x]=y,edge[x]=z,size[x]=1;
	}
	
	red(x,n,2) size[fa[x]]+=size[x];
	
	rep(x,2,n) ans+=edge[x]*size[x]*(n-size[x]);
	
	printf("%lld\n",ans);
	
	m=read();
	
	rep(i,1,m) {
		ll x=read(),z=read();
		ans-=edge[x]*size[x]*(n-size[x]);
		edge[x]=z;
		ans+=edge[x]*size[x]*(n-size[x]);printf("%lld\n",ans);
	}

	return 0;
}

T3

这道题别看 t , k t,k 的数据范围小就以为是状态压缩,因为你根本找不到任何状态来压缩。

f [ x ] [ y ] [ z ] f[x][y][z] 表示当你在 ( x , y ) (x,y) 时已经使用了 z z 次技能时受到的最小伤害。

那么你在 ( x , y ) (x,y) 时,一共有 4 4 种情况。

  1. 正常向下走

  2. 正常向右走

  3. 开始发动技能。

  4. 正处于之前的某次技能中。

对于第1,2种情况,我们进行朴素的转移即可。

对于第3种情况,我们进行 d f s dfs 来转移状态。

对于第4种情况,我们已经在之前某个点开始的 d f s dfs 中进行了转移。

这道题仍然有一点点卡常:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define red(i,x,y) for(int i=(x);i>=(y);i--)
using namespace std;

const int N=5e2+5;
const int Inf=1e18;

int n,m,t,k,h,atk;
int mp[N][N],f[N][N][12];

inline int read() {
	int x=0;char ch=getchar();bool f=0;
	while(ch>'9'||ch<'0'){if(ch=='-')f=1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return f?-x:x;
}

int calc(int x,int y,int aatk) {
	return (h-1)/aatk*mp[x][y];
}

void dfs(int x,int y,int z,int cnt,int hurt,int aatk) {
	if(cnt) hurt+=calc(x,y,aatk);
	if(cnt==k) {
		f[x][y][z+1]=min(hurt,f[x][y][z+1]);return ;
	}
	if(x+1<=n) dfs(x+1,y,z,cnt+1,hurt,aatk+mp[x+1][y]);
	if(y+1<=m) dfs(x,y+1,z,cnt+1,hurt,aatk+mp[x][y+1]);
}

int main() {
	n=read(),m=read();
	t=read(),k=read();
	h=read(),atk=read();
	
	rep(i,1,n) rep(j,1,m) mp[i][j]=read();
	
	memset(f,127,sizeof(f));f[1][1][0]=0;
	
	rep(p,0,t) rep(i,1,n) rep(j,1,m) {
		if(i+1<=n) f[i+1][j][p]=min(f[i+1][j][p],f[i][j][p]+calc(i+1,j,atk));
		if(j+1<=m) f[i][j+1][p]=min(f[i][j+1][p],f[i][j][p]+calc(i,j+1,atk));
		if(p!=t) dfs(i,j,p,0,f[i][j][p],atk);
	}
	
	printf("%d\n",f[n][m][t]);
	
	return 0;
}

小结

做了 2 2 小时,得分 90 + 100 + 0 = 190 90+100+0=190

第一题无端被卡常,第二题的做法比较常见。

第三题爆零的原因在于看到了 t , k t,k 的小数据范围就以为要进行状态压缩但是到死都没有找到任何状态进行压缩,而这道题的难度也并不大。

这套题的难度偏小,除去会被卡常的情况下, A K AK 或者 280 \geq 280 也比较正常,但是第3题却恰恰陷入了思维误区。

还是要再接再厉。

毕竟现在凌晨来做训练赛也并不容易,加油!!

猜你喜欢

转载自blog.csdn.net/yanzhenhuai/article/details/83053491