历年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
函数里面 u
和 v
写了两遍都是 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 ,这个就是我不会的了,逃了逃了。