1. 程式人生 > >2018.10.15 NOIP2018個人訓練賽第一場總結與題解

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題卻恰恰陷入了思維誤區。

還是要再接再厲。

畢竟現在凌晨來做訓練賽也並不容易,加油!!