【2019.10.6】备战NOIP2018模拟赛7
总结
我绝对是今天没睡醒/没带脑子来考的试。
T1:这种题主要错在不经常做。大方向是对的,往数学方面想,因为显然其他算法实现不了。推出来了模数的部分,但没想到最多加到2,应该可以考虑到的,因为加数为0~12显然实现不了。
(“一道非常签到的暴搜题”。签到题我都没做出来..活该做题少吗。)
T2:思路完全正确,被我给打炸了。
(没睡醒的证明...感觉做过,想了想唯一的解释就是我没睡醒。)
T3:“数论和数据结构的好题”。没时间看过了过了。
那么怎么解决没带脑子来考试的问题呢?-前天晚上休息好,平时打题认真打。要是实在没带脑子,先休息会儿/洗把脸/走一走清醒下,效率绝对比强撑着高(属实下策,没得办法)。
考试第三题没时间看,暴力分都拿不到。-3h30min的考试的时间分配要想好。一、二题最多各花40min~1h,那么就要建立在平时大量的练习基础上。第三题则是靠先拿暴力分,建立在平时做难题的基础上。
总的来说这套题还是蛮好的,感觉很复古(?),考的内容大多基础且很久没在模拟考中碰到了,今后刷题注意一些基础题+提高题的混合练习。
后缀数组(zzx,1s,32MB)
【题目描述】
xyz1048576正在玩一个关于矩阵的游戏。
一个n*m的矩阵,矩阵中每个数都是[1,12]内的整数。你可以执行下列两个操作任意多次:
(1)指定一行,将该行所有数字+1。
(2)指定一列,将该列所有数字+1。
(3)如果执行完上述操作之后,矩阵中某个数变成了3,6,9,12其中的某一个,我们认为这个数是稳的。
给定初始矩阵,求出任意执行操作之后稳数的最多个数。
【输入格式】
第一行包含两个正整数n,m。
接下来n行,每行m个数,描述这个矩阵。
【输出格式】
一个整数,表示答案。
【输入样例1】
3 3
1 2 3
3 2 4
1 2 1
【输出样例1】
7
【输入样例2】
5 5
2 4 6 8 10
1 2 3 4 5
3 4 5 6 7
7 8 9 10 11
5 10 12 3 7
【输出样例2】
20
【数据规模及约定】
对于10%的数据,n,m≤2。
对于20%的数据,n,m≤5。
对于100%的数据,n,m≤10。
题解:
有两个关键要想通。
1.每一行(或者列,为方便,下文全部考虑成行,不影响结果)加上的数只可能是0~2。因为如加数果大于等于3的话,结果减去3仍为非负整数,3的倍数的个数不变,小于12的数的个数只会更多或不变。
2.怎么使3,6,9,12(3的倍数)可能地多呢?显然两个数x,y可以同时变成3,6,9,12之一的条件是x≡y%3。所以暴力枚举行的加数,再统计行最多出现次数即可。所以出现的最多次数为max(%3余0的数的个数,%3余1的数的个数,%3余2的数的个数)
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define For(i,l,r) for(int i=l;i<=r;i++) using namespace std; const int M=14; int n,m,a[M][M],ans,delta[M],cnt[M],ntc[M]; inline int read(){ int f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } inline void dfs(int dep){ if(dep==n+1){ int sum=0; For(i,1,m){ For(j,1,12) cnt[j]=0; For(j,0,2) ntc[j]=0; For(j,1,n) if(a[j][i]+delta[j]<=12) cnt[a[j][i]+delta[j]]++; For(j,1,12) ntc[j%3]+=cnt[j]; int num=0; For(j,0,2) num=max(num,ntc[j]); sum+=num; } ans=max(ans,sum); return; } delta[dep]=0;dfs(dep+1); delta[dep]=1;dfs(dep+1); delta[dep]=2;dfs(dep+1); } int main(){ freopen("zzx.in","r",stdin); freopen("zzx.out","w",stdout); n=read(),m=read(); For(i,1,n) For(j,1,m) a[i][j]=read(); dfs(1); printf("%d",ans); return 0; }
后缀树(ysy,1s,32MB)
【题目描述】
infinite32768正在研究一个关于排列的问题。
给定一个长度为偶数的排列p,你每次可以选取p排列中相邻的两个元素,假如分别是x和y,那么把x和y加入一个序列q的末尾,并将x和y从排列p中删除。重复上述过程,直到p中没有元素,显然最终q序列也是一个排列。例如p=(1,3,2,4),第一次可以选取3,2这两个元素加入q,并且从p中删除,此时p=(1,4),第二次可以选取1,4这两个元素加入q,此时p为空,q=(3,2,1,4)。观察上述过程,最终q序列可能会有很多方案,请你输出所有可能方案中,q的字典序最大的。
字典序的比较方式如下,假设有两个长度同为n的序列a,b。我们找到最大的t,使得∀i≤t,a[i]=b[i]。之后比较a[t+1]与b[t+1],如果a[t+1]<b[t+1],我们就认为a的字典序比b小,反之亦然。
【输入格式】
第一行包含一个正整数 n。第二行 n 个数,表示排列 p。
【输出格式】
一行 n 个数,表示字典序最大的序列 q。
【输入样例1】
4
3 1 4 2
【输出样例1】
4 2 3 1
【输入样例2】
6
6 5 4 1 3 2
【输出样例2】
6 5 4 1 3 2
【数据规模及约定】
对于20%的数据,n≤10。
对于60%的数据,n≤1,000。
对于100%的数据,n≤100,000。
题解(懒得打因为真的很简单):
字典序显然可以贪心取。
贪心法则:连续n/2次,每次考虑p中剩下的数的最大值x和次大值y,如果x不是p的最后一个元素,则把x和x在p中的下一个元素依次加入q的末尾。
如果x是p的最后一个元素,则y一定不是p的最后一个元素。这时候把y和y在p中的下一个元素依次加入q的末尾。
可以使用优先队列动态维护p中剩下数的最大值。
Q:如何求得x在p中的下一个元素呢?
A:从左到右把p建成链表,删除元素时改变指针即可。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define For(i,l,r) for(int i=l;i<=r;i++) #define ll long long #define ri register int using namespace std; const int M=1e5+5; int n,m,a[M],pre[M],nxt[M],ans[M]; priority_queue<int> pq,del; inline void maintain(){ while(!pq.empty()&&!del.empty()&&(pq.top()==del.top())) pq.pop(),del.pop(); } inline int read(){ int f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } int main(){ freopen("ysy.in","r",stdin); freopen("ysy.out","w",stdout); n=read(); For(i,1,n) a[i]=read(); For(i,1,n-1) pre[nxt[a[i]]=a[i+1]]=a[i];//新写法get√ For(i,1,n) pq.push(i); For(i,1,n>>1){ maintain(); int p=pq.top();pq.pop(); maintain(); int q=pq.top();pq.pop(); if(nxt[p]){ pq.push(q);q=nxt[p]; if(pre[p]) nxt[pre[p]]=nxt[q]; if(nxt[q]) pre[nxt[q]]=pre[p]; del.push(q); ans[++m]=p;ans[++m]=q; } else{ pq.push(p);p=q;q=nxt[p]; if(pre[p]) nxt[pre[p]]=nxt[q]; if(nxt[q]) pre[nxt[q]]=pre[p]; del.push(q); ans[++m]=p;ans[++m]=q; } } For(i,1,n) printf("%d ",ans[i]); return 0; }