历年NOI第一题解题报告

NOI第一题解题报告

NOI2011 兔农

由于不会正解,所以只打了暴力,最终得分:50pts

暴力模拟斐波那契数列的操作,加个高精就能过了,但是极其耗费码力,所以说还是 __int128 爽。

这一题真没什么好说的,NOI 出这题就是想让人打暴力的,考场上正常人谁写这么复杂的东西。

NOI2012 随机数生成器

主要思想:矩阵快速幂,龟速乘

由于本题中 m 的大小如果硬乘再模很明显就会爆掉 longlong ,所以使用龟速乘法,用加法来代替乘法从而达到不爆 longlong 的目的。

外加本题中的递推关系十分明显,就直接用矩阵快速幂求解,可以十分迅猛的得到答案。

NOI2013 向量内积

主要思想:前缀和

关于一个向量而言,我们只用维护一个前缀和就可以了,而且我们发现该题的模数是 2 和 3 ,所以在一开始的时候就取模再进行加的操作就可以得到答案了,然后 \(O(d)\) 地扫一遍每个维数的前缀,然后扫一遍每个数组就可以了,整体的时间复杂度是 \(O(nd^2)\) 的,足够通过本题。

NOI2014 起床困难综合症

主要思想:拆位dp

其实用不着。

只用维护两个量,一个是 0,一个是 -1,由于 -1 在二进制下的数都是 1,所以说操作就是和 0 是反着来的, 贪心的说,如果说能换成1就换,不能换就不换,然后把每一位都跑一遍,就行了。

NOI2015 程序自动分析

主要思想:并查集,离散化

本题的标号有一点大,所以用离散化来进行查找,先把 1 的都连起来,然后跑 0 的查找,用并查集完成以上操作。

code :

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e5 + 1;
int n,m;
int f[N];
inline void ResetSet(const int s) {for(int i = 1; i <= s; i++) f[i] = i;}
int FindSet(const int x) {return f[x] == x? x : f[x] = FindSet(f[x]);}
int a[N];
struct node{
	int x,y,z;
	bool operator<(const node&tem)const{return z > tem.z;}
}s[N];
inline void input() {
	scanf("%d",&n);
	for(int i = 1; i <= n; i++)scanf("%d%d%d",&s[i].x,&s[i].y,&s[i].z);
	int tot = 0;
	for(int i = 1; i <= n; i++)a[++tot] = s[i].x,a[++tot] = s[i].y;
	sort(a + 1, a + 1 + tot);
	m = unique(a + 1, a + 1 + tot) - a - 1;
}
inline void work() {
	ResetSet(m);
	sort(s + 1, s + 1 + n);
	for(int i = 1; i <= n; i++) {
		s[i].x = lower_bound(a + 1, a + 1 + m,s[i].x) - a;
		s[i].y = lower_bound(a + 1, a + 1 + m,s[i].y) - a;
		int u = FindSet(s[i].x),v = FindSet(s[i].y);
		if(s[i].z == 1) {
			if(u == v) continue;
			f[u] = v;
		}else {
			if(u == v) return void(puts("NO"));
		}
	}
	return void(puts("YES"));
}
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		input();
		work();
		
	}
}

NOI2016 优秀的拆分

主要思想:字符串hash

反正我是用这玩意混过去的,用字符串 hash 来判重,枚举a的长度之后,用二分查找来找到可能的b的可能,然后一起加起来就可以了。

有人说字符串hash跑不过去,我还真跑过去了。

#include<bits/stdc++.h>
using namespace std;
const int N = 4e4 + 1;
const long long mod = 1e9 + 7;
long long has[N],mo[N],u[N],v[N],ans;
int n;
char ch[N];
inline long long H(int l,int r) {
	long long now = has[l] - has[r] * mo[r - l];
	now %= mod,now += mod, now %= mod;
	return now;
}
inline void work() {
	scanf("%s",ch + 1);
	n = strlen(ch + 1);
	memset(u,0,sizeof(u)),memset(v,0,sizeof(v));
	int l,r,mid,hd,tl,lst,pos;
	has[n + 1] = 0;
	for(int i = n; i >= 1; i--) has[i] = has[i + 1] * 31 + ch[i] - 'a' + 1,has[i] %= mod;
	for(int L = 1; L * 2 <= n; L++) {
		for(int i = L * 2; i <= n; i += L) {
			if(ch[i] != ch[i - L]) continue;
			l = 1,r = L, pos = 0,lst = i - L;
			while(l <= r) {
				mid = l + r >> 1;
				if(H(lst - mid + 1,lst + 1) == H(i - mid + 1,i + 1)) l = mid + 1,pos = mid;
				else r = mid - 1;
			}
			hd = i - pos + 1;
			l = 1,r = L,pos = 0;
			while(l <= r) {
				mid = l + r >> 1;
				if(H(lst,lst + mid) == H(i,i + mid)) l = mid + 1,pos = mid;
				else r = mid - 1;
			}
			tl = i + pos - 1;
			hd = max(hd+L-1,i);
			tl = min(tl,i+L-1);
			if(hd <= tl) {
				u[hd - L * 2 + 1] ++;u[tl + 1 - L * 2 + 1]--;
				v[hd]++,v[tl+1]--;
			}
		}
	}
	ans = 0;
	for(int i = 1; i <= n; i++) u[i] += u[i-1],v[i] += v[i - 1];
	for(int i = 1; i < n; i++) ans += v[i] * u[i + 1];
	printf("%lld\n",ans);
	return;
}
int main() {
	int t;
	scanf("%d",&t);
	mo[0] = 1;
	for(int i = 1 ;i <= 30000; i++) mo[i] = mo[i - 1] * 31 ,mo[i] %= mod;
	while(t--) {
		work();
	}
	return 0;
}

NOI2018 归程

主要思想:kruskal重构树,最短路,LCA

这个玩意搞了我一整个上午,最后发现是 add 函数里面 uv 写了两遍都是 u 连向 v 的,真是心态搞炸了。

code:

#include<bits/stdc++.h>
#define gc getchar()
#define rd read()
using namespace std;
const int N = 4e5 + 1;
const int M = 8e5 + 1;

inline 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 << 1) + (x << 3) + (ch ^ 48),ch = gc;
	return f ? x : -x;
}//complete
int n,m,cnt;
long long q,k,s;

struct node{
	int u,v;
	long long val;
}e[N << 1];
bool cmp(node a,node b){return a.val > b.val;}

int hd[N],nxt[M],to[M],tot;

long long dis[M];//建图 

long long d[N],vis[N],ff[N];//kurskal重构树

priority_queue<pair<long long,int> >Q;//dj 

long long mi[N],val[N]; 

int f[N][23];//倍增 

void add(const int u,const int v,const long long dist) {
	nxt[++tot] = hd[u],dis[tot] = dist,to[tot] = v,hd[u] = tot;
}//complete

inline void dijstra() {
	memset(vis,0,sizeof(vis));
	memset(d,0x3f,sizeof(d));
	d[1] = 0;
	Q.push(make_pair(0,1));
	while(!Q.empty()) {
		int u = Q.top().second;Q.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(int eg = hd[u];eg;eg = nxt[eg]) {
			int v = to[eg];
			if(d[u] + dis[eg] < d[v]) {
				d[v] = d[u] + dis[eg];
				Q.push(make_pair(-d[v],v));
			}
		}
	}
	return ;
}//complete

int FindSet(const int x){return ff[x] == x ? x : ff[x] = FindSet(ff[x]);}
void dfs(const int u) {
	mi[u] = d[u];
	for(int eg = hd[u];eg;eg = nxt[eg]) {
		int v = to[eg];
		f[v][0] = u;
		dfs(v);
		mi[u] = min(mi[u],mi[v]);
	}
	return ;
}

void kruskal(){
	memset(hd,0,sizeof(hd));
	tot = 0;
	sort(e + 1,e + 1 + m,cmp);
	for(int i = 1; i <= n; i++) ff[i] = i;
	for(int i = 1; i <= m; i++) {
		int fu = FindSet(e[i].u),fv = FindSet(e[i].v);
		if(fu != fv) {
			val[++cnt] = e[i].val;
			ff[fu] = ff[fv] = ff[cnt] = cnt;
			add(cnt,fu,0),add(cnt,fv,0);
		}
	}//kruskal重构树建图
	dfs(cnt);
}

inline void input() {
	memset(hd,0,sizeof(hd)),tot = 1;
	memset(f,0,sizeof(f));
	memset(mi,0x3f,sizeof(mi));
	n = rd,m = rd,cnt = n;
	for(int i = 1; i <= m; i++) {
		int u = rd,v = rd,dis = rd,hi = rd;
		add(u,v,dis),add(v,u,dis);
		e[i].u = u,e[i].v = v,e[i].val = hi;
	}
	dijstra();
	kruskal();
	for(int i = 1; (1 << i) <= cnt; i++)
	for(int u = 1; u <= cnt; u++) 
	f[u][i] = f[f[u][i-1]][i-1];
	q = rd,k = rd,s = rd;
}

inline void work() {
	long long lst = 0;
	while(q--) {
		int vi = rd,pi = rd;
		vi = (vi + k * lst - 1) % n + 1;
		pi = (pi + k * lst) % (s + 1);
		for(int i = 22; i >= 0; i--) {
			if(f[vi][i] && val[f[vi][i]] > pi) vi = f[vi][i];
		}
		printf("%lld\n",mi[vi]);
		lst = mi[vi];
	}
}
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		input();
		work();
	}
	return 0;
}

NOI2019 回家路线

这道题,乃是 NOI 近几年来最水的水题,爆搜随便加个剪枝就能过去,这波啊,这波是出题人的锅。

直接暴力从第一个点搜到最后一个点,如果说能连在一起就连在一起,然后用 calc 算一下贡献就可以了。

当然这个题有一个剪枝,就是因为时间比较少,所以可以用记忆化搜索,先记录当前时间下最少需要多少烦躁值,然后往后面硬搜就可以了。

code :

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
template<typename T>inline void cmin(T&x,T y) {x = (x > y ? y : x);}
const int N = 1e5 + 1;
const int M = 2e5 + 1;
const int T = 1e3 + 1;
int A,B,C;
int n,m;
int ans = 1e9 + 7;
int tim[N][T];
struct node{
	int to,st,ed;
	node() {}
	node(const int v,const int sd,const int et) {to = v,st = sd,ed = et;}
	bool operator<(const node&tem) const{return st > tem.st;}
};
vector<node> e[N];
inline void input(void) {
	scanf("%d%d%d%d%d",&n,&m,&A,&B,&C);
	for(int i = 1; i <= m; i++) {
		if(i <= n) memset(tim[i],0x3f,sizeof(tim[i]));
		int x,y,p,q;
		scanf("%d%d%d%d",&x,&y,&p,&q);
		e[x].push_back(node(y,p,q));
	}
	if(n > m) for(int i = m + 1; i <= n; i++) {
		memset(tim[i],0x3f,sizeof(tim[i]));
	}
}
inline void dfs(const int t,const int now,const int val) {
	if(val > tim[now][t]) return;//剪枝1
	if(val + t > ans) return;//剪枝2
	if(now == n) return void(cmin(ans,t + val));//边界条件
	tim[now][t] = val;
	for(int i = 0; i < (int)e[now].size(); i++) {//不敢用auto的痛
		int v = e[now][i].to;
		if(e[now][i].st < t) break;//剪枝3
		int wit = e[now][i].st - t;
		int sum = A * wit * wit + B * wit + C;
		dfs(e[now][i].ed,v,val + sum);
	}
	return;
}
inline void work(void) {
	for(int i = 1; i <= n; i++)sort(e[i].begin(),e[i].end());
	dfs(0,1,0);
	cout << ans << endl;
}
int main(void) {
	input();
	work();
	return 0;
}

NOI2020 美食家

该题我只想出来了 \(O(nmt)\) 的暴力,所以说得分只有40pts,然后听正解是矩阵快速幂优化 dp ,这个就是我不会的了,逃了逃了。

posted @ 2021-03-10 16:14  Kamiya-Kina  阅读(125)  评论(0编辑  收藏  举报