历年NOIP最后一题解题报告

历年NOIP最后一题解题报告

NOIP2011 观光公交

主要思路:贪心

该题如果说没有氮气加速的话,那就是一个非常显然的模拟题,但是如果说加入了一个氮气加速,那就需要一些 奇淫巧技 高端技巧了

主要关于一个地方需要等人,这个等人就很烦,而且这个人是没有办法优化的,所以我们考虑可以想一下那一条路上对后面的影响最大,就是说如果说这个地方的车后来,人先来的话,那么我们就可以对这个地方进行一个氮气加速。但是氮气加速不是无限的,而且如果说车等人的话明显是不赚的。

而且关于一个氮气加速怎么才能使价值最大化,那么就需要用堆来优化这个过程了。

如果说该路径在优化之后可以惠及到后面的尽可能多的点,那么我们觉得这个路径的优化是非常有价值的,所以我们保留他。如果说该路径在优化之后惠及不了多少点,那么这个优化就是十分拉跨的,所以我们把它放在后面优化。

这个证明是比较显然的,关键是代码的实现,这里只放出 work 代码的实现。

inline void work() {
	init();
	int max_num,max_pos,tem_num;
	while(k--) {
		max_num = 0;
		FOR(i,2,n) {
			if(!d[i-1]) continue;
			tem_num = 0;
			FOR(j,i,n) {
				tem_num += sta[j].off;
				if(sta[j].arrive <= sta[j].last) break;
			}
			if(tem_num > max_num) {
				max_num = tem_num;
				max_pos = i;
			}
		}
		d[max_pos - 1]--;
		FOR(i,max_pos,n) {
			sta[i].arrive--;
			if(sta[i].arrive < sta[i].last) break;
		}
	}
	return ;
}

时间复杂度 \(O(k\times 玄学)\)

主要是因为 break 的存在优化了暴力算法,对于数据进行优化,当然如果要卡还是卡的掉的。

NOIP2012 疫情控制

主要思路:倍增,二分答案,贪心

几个显然的贪心结论:

  1. 每个点都尽量网上爬。

  2. 如果说该点到了顶端,那么就一定只会下降一个点。

有了这两个结论,我们就可以快乐的做题了,但是问题又来了,如何才能求得答案呢?

题面给了很重要的提示,由于我们要求一个满足条件的最小值,那么显然比这个最小值大的所有答案都是成立的,所以说答案符合单调性,用二分答案来 check 答案。

但是对于每一个点往上爬那么时间复杂度不是烂掉了吗,变成了 \(O(\log_2 1e9 \times n^2)\) ,这显然不行,那么对于这种情况,用倍增就可以迅速得解。

#include<bits/stc++.h>
#define ll long long
using namespace std;
const int N = 5e4 + 1;
int n,m,len,tot,atot,btot,ctot;
int d[N],query[N],f[N][20];
int t[N << 1],val[N << 1],nxt[N << 1],hd[N];
bool ok,sta[N],need[N];
ll ans,tim[N],ned[N],dist[N][20];
pair<ll,int> h[N];
queue<int>q;
void add(const int x,const int y,const int z) {
	t[++tot] = y,val[tot] = z,nxt[tot] = hd[x],hd[x] = tot;
}
void bfs() {
	q.push(1);
	d[1] = 1;
	while(!q.empty()) {
		int x = q.front();q.pop();
		for(int i = hd[x];i;i = nxt[i]) {
			int y = t[i];
			if(d[y]) continue;
			d[y] = d[x] + 1;
			f[y][0] = x, dist[y][0] = val[i];
			for(int j = 1; j <= len; j++) {
				f[y][j] = f[ f[y][j - 1] ][j - 1];
				dist[y][j] = dist[y][j - 1] + dist[ f[y][j - 1] ][j - 1];
			}
			q.push(y);
		}
	}
}
bool dfs(const int x) {
	bool ret = false;
	if(sta[x]) return true;
	for(int i = hd[x];i;i = nxt[i]) {
		int y = t[i];
		if(d[y] < d[x]) continue;
		ret = true;
		if(!dfs(y)) return false;
	}
	return ret;
}

bool check(ll lim) {
	memset(sta,0,sizeof(sta));
	memset(tim,0,sizeof(tim));
	memset(ned,0,sizeof(ned));
	memset(h,0,sizeof(h));
	memset(need,0,sizeof(need));
	atot = btot = ctot = 0;
	for(int i = 1; i <= m; i++) {
		ll x = query[i],cnt = 0;
		for(int j = len; j >= 0; j--) {
			if(f[x][j] > 1 && cnt + dist[x][j] <= lim) cnt += dist[x][j],x = f[x][j];
		}
		if(f[x][0] == 1 && cnt + dist[x][0] <= lim) h[++ctot] = make_pair(lim - cnt - dist[x][0], x);
		else sta[x] = true;
	}
	for(int i = hd[1];i;i = nxt[i]) if(!dfs(t[i])) need[t[i]] = true;
	
	sort(h + 1, h + ctot + 1);
	for(int i = 1; i <= ctot; i++) {
		if(need[h[i].second] && h[i].first < dist[h[i].second][0]) need[h[i].second] = false;
		else tim[++atot] = h[i].first;
	}
	for(int i = hd[1];i;i = nxt[i]) {
		if(need[t[i]]) ned[++btot] = dist[t[i]][0];
	}
	if(atot < btot) return false;
	sort(tim + 1,tim + atot + 1),sort(ned + 1,ned + btot + 1);
	int i = 1,j = 1;
	while(i <= btot && j <= atot) {
		if(tim[j] >= ned[i]) i++,j++;
		else j++;
	}
	return i > btot;
}
int main() {
	ll l = 0,r = 0,mid;
	scanf("%d",&n);
	len = log2(n) + 1;
	for(int x,y,z,i = 1; i < n; i++) {
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z),add(y,x,z);
		r += z;
	}
	bfs();
	scanf("%d",&m);
	for(int i = 1; i <= m; i++) scanf("%d",&query[i]);
	while(l <= r) {
		mid = l + r >> 1;
		if(check(mid)) r = mid - 1,ans = mid, ok = true;
		else l = mid + 1;
	}
	if(!ok) puts("-1");
	else cout << ans << endl;
	return 0;
}

时间复杂度 \(O(log_2 1e9 \times n\log_2 n)\)

这个时间复杂度还是比较好算的。

NOIP2013 华容道

主要思路:最短路,广度优先搜索

这个题,看起来就像个广搜,如果说要记录状态的话,可以只用记录空白格子和所需格子的位置就可以了,如果直接广搜,时间复杂度 \(O((nm)^2q)\) 死掉了,所以说要优化。

怎么优化呢,很明显的就是所需棋子与空白格子是联通的,如果说空白格子动了,那么所需棋子肯定是要动,如果要求答案,那么空白格子肯定要在所需格子周围乱转,所以说这个图就联通起来了,然后求最短路就可以了,主要看代码实现。

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++) 
#define ROF(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
#define db double
#define INF 0x7fffffff
using namespace std;
const int N = 31;
const int M = 3e3 + 6e2 + 1;
const int SUM = M * 5;
const int dx[4] = {-1,0,1,0};
const int dy[4] = {0,1,0,-1};
int n,m,p;
bool a[N][N],vis[M];
int predis[N][N],dis[M];
int hd[M],nxt[SUM],to[SUM],val[SUM],tot;
void add(const int u,const int v,const int w) {
	to[++tot] = v,nxt[tot] = hd[u],val[tot] = w,hd[u] = tot; 
}
queue<pair<int,int> >q;
queue<int>Q;
int calc(const int x,const int y) {return (x-1)*m+(y-1)<<2;}
inline void bfs(int ex,int ey,int px,int py,int d){
	int cx,cy,nx,ny;
	memset(predis,-1,sizeof(predis));
	predis[px][py] = 1;
	predis[ex][ey] = 0;
	q.push(make_pair(ex,ey));
	while(!q.empty()) {
		auto u = q.front();q.pop();
		cx = u.first,cy = u.second;
		for(int i = 0; i < 4; i++) {
			nx = cx + dx[i],ny = cy + dy[i];
			if(a[nx][ny] && predis[nx][ny] == -1) {
				predis[nx][ny] = predis[cx][cy] + 1;
				q.push(make_pair(nx,ny));
			}
		}
	}
	if(d == 8) return;
	int tmp = calc(px,py);
	for(int i = 0; i < 4; i++) {
		int x = px + dx[i],y = py + dy[i];
		if(predis[x][y] > 0) add(tmp + d,tmp + i,predis[x][y]);
	}
	add(tmp + d,calc(ex,ey) + (d + 2) % 4 ,1);
}
void SPFA(const int sx,const int sy) {
	int tmp;
	memset(dis,-1,sizeof(dis));
	for(int i = 0; i < 4; i++) {
		int x = sx + dx[i],y = sy + dy[i];
		if(predis[x][y] != -1) {
			tmp = calc(sx,sy) + i;
			dis[tmp] = predis[x][y];
			vis[tmp] = true;
			Q.push(tmp);
		}
	}
	while(!Q.empty()) {
		int u = Q.front();Q.pop();
		vis[u] = false;
		for(int eg = hd[u];eg;eg = nxt[eg]) {
			int v = to[eg];
			if(dis[v] == -1 || dis[v] > dis[u] + val[eg]) {
				dis[v] = dis[u] + val[eg];
				if(!vis[v]) vis[v] = true,Q.push(v);
			}
		}
	}
	
}
int main() {
	scanf("%d%d%d",&n,&m,&p);
	FOR(i,1,n) FOR(j,1,m) scanf("%d",&a[i][j]);
	FOR(i,1,n) FOR(j,1,m) if(a[i][j]) {
		if(a[i-1][j]) bfs(i-1,j,i,j,0);
		if(a[i][j+1]) bfs(i,j+1,i,j,1);
		if(a[i+1][j]) bfs(i+1,j,i,j,2);
		if(a[i][j-1]) bfs(i,j-1,i,j,3);
	}
	int ex,ey,sx,sy,tx,ty,ans;
	while(p--) {
		scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
		if(sx == tx && sy == ty) {
			puts("0");//特判一下
			continue;
		}
		bfs(ex,ey,sx,sy,8);
		SPFA(sx,sy);
		ans = INF;
		int tmp = calc(tx,ty);
		for(int i = 0;i < 4; i++) {
			if(dis[tmp + i] != -1) ans = min(ans,dis[tmp + i]);
		}
		if(ans == INF) ans = -1;
		printf("%d\n",ans);
	}
	return 0;
} 

时间复杂度 \(O(懒得算)\)

主要是有一个 BFS 导致时间复杂度有一点不好算,所以我就懒得算了。

NOIP2014 解方程

主要思路:秦九韶算法,暴力枚举

m 不大,n 也不大,所以 \(O(nm)\) 的算法是可以接受的,直接暴力搞就完事了!

但是一般的暴力是 \(O(nm\log_2 n)\) 影响不大,总归是不太好,但是该题烦就烦在 a 的数据极大,所以说要在读入的时候就做优化,随便取一个自己喜欢的模数,如果不放心可以用两个,然后再从一开始枚举到 m ,枚举完了之后 calc 函数算一算就可以了。

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define rd read()
#define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
char buf[100000],*p1(buf),*p2(buf);
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e2 + 1;
const int M = 1e6 + 1;
long long a[N],key[M],cnt;
long long n,m,ans,sum = 0;
long long read() {
	long long x = 0,f = 1;char ch = gc;
	while(!isdigit(ch)) {if(ch == '-')f = 0;ch = gc;}
	while(isdigit(ch)) x = ((x * 10) + (ch - '0')) % mod,ch = gc;
	return f ? x : -x;
}
bool calc(const long long x) {
	sum = 0;
	for(int i = n; i >= 1; i--) {
		sum = ((a[i] + sum) * x) % mod;
	}
	sum = (sum + a[0]) % mod;
	return sum == 0;
}
int main() {
	n = rd,m = rd;
	for(int i = 0; i <= n; i++)a[i] = rd;
	for(long long i = 1; i <= m; i++){
		if(calc(i)) key[++ans] = i;
	}
	cout << ans << endl ;
	for(int i = 1; i <= ans; i++) cout << key[i] << endl;
	return 0;
} 

时间复杂度 \(O(nm)\)

虽然不知道读入的时间复杂度要不要算。

感觉是做的这些题里面最简单的一道。

NOIP2015运输计划

主要思路:树链剖分

其实这题不用树链剖分的,但是我觉得树链剖分的思路比较简单就用了,没想到代码难度如此之高,淦。

不想说了,没什么奇淫巧技,直接上code。

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 1;
const int M = N << 1;
const int INF = 0x7fffffff;
template<typename T>inline void cmax(T&x,T y){x = (x < y ? y : x);}
template<typename T>inline void cmin(T&x,T y){x = (x > y ? y : x);}
inline int read(){
	int x = 0,f = 1;char ch = getchar();
	while(!isdigit(ch)){if(ch == '-') f = 0;ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
	return f ? x : -x;
}
int x[N],y[N],z[N],p[N];
int to[M],nxt[M],hd[N],cnt;
int son[N],fa[N],siz[N],dep[N],root;
int id[N],top[N],rak[N],num;
int c[N],d[N],srt[N];
int ma,mb,mc;
int n,m;
struct BITTREE{
	int a[N];
	inline int lowbit(const int k){return k&(-k);}
	inline void update(const int x,const int k){for(int i = x; i <= n; i += lowbit(i)) a[i] += k;}
	inline int query(int x){int ret = 0;for(int i = x; i >= 1; i -= lowbit(i)) ret += a[i];return ret;}
	inline void build(const int x){for(int i = 1; i <= n; i++) update(i,p[rak[i]]);}
	inline int sum(const int l,const int r) {return query(r) - query(l - 1);}
}bit;//complete

struct segment_tree{
	#define ls o << 1
	#define rs ls | 1
	int mx[N << 2],tag[N << 2];
	inline void pushup(const int o) {mx[o] = max(mx[ls],mx[rs]);}
	inline void pushdown(const int o) {
		if(tag[o]) {
			int k = tag[o];
			cmax(mx[ls],k),cmax(mx[rs],k);
			cmax(tag[ls],k),cmax(tag[rs],k);
			tag[o] = 0;
		}
		return;
	}
	void update(const int o,const int l,const int r,const int x,const int y,const int k) {
		if(l > r || r < x || l > y) return;
		if(x <= l && y >= r)return cmax(mx[o],k),cmax(tag[o],k);
		pushdown(o);
		int mid = l + r >> 1;
		update(ls,l,mid,x,y,k),update(rs,mid+1,r,x,y,k);
		pushup(o);
	}
	int query(const int o,const int l,const int r,const int p) {
		if(l == r) return mx[o];
		pushdown(o);
		int mid = l + r >> 1;
		if(p <= mid) return query(ls,l,mid,p);
		else return query(rs,mid+1,r,p);
	}
}sgt;//complete

inline void add(const int u,const int v) {to[++cnt] = v,nxt[cnt] = hd[u],hd[u] = cnt;}
void dfs1(const int o,const int pre,int d) {
	fa[o] = pre,dep[o] = d,siz[o] = 1;
	for(int eg = hd[o];eg;eg = nxt[eg]) {
		int v = to[eg];
		if(v == pre) continue;
		dfs1(v,o,d+1);
		siz[o] += siz[v];
		if(siz[v] > siz[son[o]]) son[o] = v;
	}
	return ;
}
void dfs2(const int o,const int t) {
	top[o] = t,id[o] = ++num, rak[id[o]] = o;
	if(!son[o]) return;
	dfs2(son[o],t);
	for(int eg = hd[o];eg;eg = nxt[eg]) {
		int v = to[eg];
		if(v != fa[o] && v != son[o]) dfs2(v,v);
	}
	return ;
}//complete
inline int sum(int x,int y) {
	int tx = top[x],ty = top[y],ret = 0;
	while(tx != ty) {
		if(dep[tx] >= dep[ty]) ret += bit.sum(id[tx],id[x]),x = fa[tx],tx = top[x];
		else ret += bit.sum(id[ty],id[y]),y = fa[ty] ,ty = top[y];
	}
	if(id[x] <= id[y]) ret += bit.sum(id[x] + 1,id[y]);
	else ret += bit.sum(id[y] + 1,id[x]);
	return ret;
}//complete

inline bool cmp(const int x,const int y){return c[x] < c[y];}
inline void update(int x,int y,int z) {
	int tx = top[x],ty = top[y],tot = 0;
	while(tx != ty) {
		if(dep[tx] >= dep[ty]) c[++tot] = id[tx],d[tot] = id[x],x = fa[tx],tx = top[x];
		else c[++tot] = id[ty],d[tot] = id[y],y = fa[ty],ty = top[y];
	}
	if(id[x] <= id[y]) c[++tot] = id[x] + 1,d[tot] = id[y];
	else c[++tot] = id[y] + 1,d[tot] = id[x];
	for(int i = 1; i <= tot; i++)srt[i] = i;
	sort(srt + 1,srt + 1 + tot,cmp);
	if(c[srt[1]] > 1) sgt.update(1,1,n,1,c[srt[1]] - 1,z);
	if(c[srt[tot]] < n) sgt.update(1,1,n,d[srt[tot]] + 1,n,z);
	for(int i = 1; i < tot; i++) sgt.update(1,1,n,d[srt[i]] + 1,c[srt[i + 1]] - 1,z);
	return ;
}//complete 
inline int FindAns(int x,int y) {
	int ans = INF;
	if(x == y) return 0;
	if(dep[x] < dep[y]) swap(x,y);
	while(dep[x] != dep[y]) cmin(ans,max(mc - p[x],sgt.query(1,1,n,id[x]))),x = fa[x];
	while(x != y) {
		if(dep[x] > dep[y]) cmin(ans,max(mc - p[x],sgt.query(1,1,n,id[x]))),x = fa[x];
		else cmin(ans,max(mc - p[y],sgt.query(1,1,n,id[y]))), y = fa[y];
	}
	return ans;
}
int main() {
	n = read(), m = read();
	for(int i = 1; i < n; i++) x[i] = read(),y[i] = read(),z[i] = read();
	for(int i = 1; i < n; i++) add(x[i],y[i]),add(y[i],x[i]);
	root = rand() % n + 1;
	dfs1(root,0,1),dfs2(root,root);
	for(int i = 1; i < n; i++) {
		if(dep[x[i]] > dep[y[i]]) p[x[i]] = z[i];
		else p[y[i]] = z[i];
	}
	bit.build(n);
	for(int i = 1; i <= m; i++) {
		int a = read(),b = read(),tem;
		tem = sum(a,b),update(a,b,tem);
		if(tem >= mc) ma = a,mb = b,mc = tem;
	}
	printf("%d\n",FindAns(ma,mb));
	return 0;
}

时间复杂度: \(O(m \log^2 n)\)

写这个题,让我学会了树链剖分这种平白无故让代码长度加 1k 的算法。

NOIP2016 愤怒的小鸟

主要思路:状压dp

但是我用的是搜索。

主要思路:搜索!

code!

#include<bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
const int N = 21;
bool dy(double a,double b) {
	return fabs(a - b) < eps;
}
int n,m,ans;
double x[N],y[N],pwxa[N],pwxb[N],tx[N],ty[N];
void dfs(const int c,const int u,const int v) {
	if(u + v > ans) return;
	if(c > n) return void(ans = u + v);
	bool flag = false;
	for(int i = 1; i <= u; i++) {
		if(dy(pwxa[i] * x[c] * x[c] + pwxb[i] * x[c],y[c])) {
			dfs(c + 1,u,v);
			flag = true;
			break;
		}
	}
	if(!flag) {
		for(int i = 1; i <= v; i++) {
			if(dy(x[c],tx[i])) continue;
			double a = (y[c] * tx[i] - ty[i] * x[c]) / (x[c] * x[c] * tx[i] - tx[i] * tx[i] * x[c]);
			double b = (y[c] - x[c] * x[c] * a) / x[c];
			if(a < 0) {
				pwxa[u + 1] = a;
				pwxb[u + 1] = b;
				double q = tx[i],w = ty[i];
				for(int j = i ; j < v; j++) {
					tx[j] = tx[j + 1];
					ty[j] = ty[j + 1];
				}
				dfs(c + 1,u + 1,v - 1);
				for(int j = v; j > i; j--) {
					tx[j] = tx[j - 1];
					ty[j] = ty[j - 1];
				}
				tx[i] = q;
				ty[i] = w;
			}
		}
		tx[v + 1] = x[c];
		ty[v + 1] = y[c];
		dfs(c + 1,u,v + 1);
	}
}
int main() {
	int t;
	cin >> t;
	while(t--) {
		cin >> n >> m;
		for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];
		ans = 100;
		dfs(1,0,0);
		cout << ans << endl;
	}
	return 0;
}

时间复杂度 \(O(玄学)\)

毕竟是搜索,加点优化,体谅一下时间复杂度。

NOIP2017 列队

暴力模拟

code

#include<bits/stdc++.h>
#define ll long long
#define FOR(i,a,b) for(int i=(a),i##i=(b);i<=i##i;i++)
using namespace std;
const int N = 3e5 + 1;
vector<long long> lis[N];
int n,m,q;
long long cnt = 0;
int main() {
	scanf("%d%d%d",&n,&m,&q);
	FOR(i,1,n) lis[i].push_back(0);
	FOR(i,1,n) FOR(j,1,m) lis[i].push_back(++cnt);
	while(q--) {
		int x,y;
		scanf("%d%d",&x,&y);
		long long num = lis[x][y];
		printf("%lld\n",num);
		lis[x].erase(lis[x].begin() + y);
		lis[x].push_back(0);
		for(int i = x; i < n; i++) lis[i][m] = lis[i+1][m];
		lis[n][m] = num;
	}
	return 0;
}

暂且只会这种写法

得分:40pts

NOIP2018 保卫王国

主要思路:dp,树链剖分

本题暂时还没有写代码(绝对不是因为树链剖分太长了)。

思路只要是树上dp,转移方程是

\[dp_{i,1} = \sum \min{dp_{j,1},dp_{j,0}} + a_i \]

\[dp_{i,0} = \sum{dp_{j,1}} \]

这是 \(O(nm)\) 的朴实算法,最后得分:55pts

然后加个倍增就能过掉了。

posted @ 2021-03-10 16:17  Kamiya-Kina  阅读(122)  评论(0)    收藏  举报