历年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 疫情控制
主要思路:倍增,二分答案,贪心
几个显然的贪心结论:
-
每个点都尽量网上爬。
-
如果说该点到了顶端,那么就一定只会下降一个点。
有了这两个结论,我们就可以快乐的做题了,但是问题又来了,如何才能求得答案呢?
题面给了很重要的提示,由于我们要求一个满足条件的最小值,那么显然比这个最小值大的所有答案都是成立的,所以说答案符合单调性,用二分答案来 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,转移方程是
这是 \(O(nm)\) 的朴实算法,最后得分:55pts
然后加个倍增就能过掉了。

浙公网安备 33010602011771号