【算法】
————【专题】生成树计数(矩阵树定理)
————【算法专题】卡特兰数(计数数列)
————【专题】数论
————【专题】概率和期望
————【算法专题】后缀自动机SAM
一个总结性博客:Arya__
【开始模板】
标准模板
#include<cstdio> #include<cstring> #include<cctype> #include<cmath> #include<queue> #include<stack> #include<set> #include<vector> #include<algorithm> #define ll long long #define lowbit(x) x&-x using namespace std; int read(){ char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } int min(int a,int b){return a<b?a:b;} int max(int a,int b){return a<b?b:a;} int ab(int x){return x>0?x:-x;} //int MO(int x){return x>=MOD?x-MOD:x;} //void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;} /*------------------------------------------------------------*/ const int inf=0x3f3f3f3f; int n; int main(){ return 0; }
fread(空间)
#include<cstdio> char buf[10000010],*ptr=buf-1; int read()//48~57 { char c=*++ptr;int s=0,t=1; while(c<48||c>57){if(c=='-')t=-1;c=*++ptr;} while(c>=48&&c<=57){s=s*10+c-'0';c=*++ptr;} return s*t; } int main() { fread(buf,1,sizeof(buf),stdin); return 0; }
fread(缓存)
const int M=1e5; char ib[M+7],*ip=ib+M; int G(){ if(ip==ib+M)fread(ip=ib,1,M,stdin)[ib]=0; return *ip++; } int read(){ int x=0,f=1; if(ip<ib+M-100){ while(*ip<48)*ip++=='-'?f=-1:0; while(*ip>47)x=x*10+*ip++-48; }else{ int c=G(); while(c<48)c=='-'?f=-1:0,c=G(); while(c>47)x=x*10+c-48,c=G(); } return x*f; }
【随机/对拍/Linux】
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int seed=123456789;//?? int rnd() {return seed=(seed*1844677ll+1234577)%1000000007;}//%10^9+7 int a[1000010]; void build(int n){ for(int i=1;i<=n;i++)a[i]=i; for(int i=1;i<=n;i++){ int x=rnd()%(n-i+1)+i; swap(a[i],a[x]); } } int main() { for(int T=0;;T++) { FILE*f=fopen("in.txt","w"); /*----------????----------*/ int n=rnd()%100+1; fprintf(f,"%d ",n); /*----------------------------*/ fclose(f);//Important printf("---%d--- ",T); system("cyc1.exe<in.txt>o1");//bat?? system("cyc2.exe<in.txt>o2"); system("fc o1 o2||pause");//linnux?fc??diff pause??./pause ??pause.cpp???{getchar();return 0;}? } //windows????:tasklist taskkill -f -im sth.exe //linux????:killall GUIDE ps -ef return 0; }
Linux常用指令:
编译:g++ cyc.cpp -o cyc -O2 -Wall(将cyc.cpp编译后生成可执行文件cyc)
运行:./cyc
显示路径:pwd
进入文件夹:cd CYC(返回上一层:cd ..)
杀进程:killall GUIDE
进程列表:ps -ef
Linux对拍:(另写并编译文件pause,内容为{getchar();return 0;})
#include<cstdio> #include<algorithm> using namespace std; int seed; int rnd(){return seed=(seed*1844677ll+1234577)%1000000007;} int main(){ for(int T=0;;T++){ FILE*f=fopen("in.txt","w"); int n=rnd()-500000000; fprintf(f,"%d",n); fclose(f); printf("-------%d-------- ",T); system("./cyc<in.txt>o1"); system("./cyc2<in.txt>o2"); system("diff o1 o2||./pause"); } return 0; }
【RMQ】
void RMQ_init() { for(int i=1;i<=n;i++)d[i][0]=a[i]; for(int j=1;(1<<j)<=n;j++) for(int i=1;i+(1<<j)-1<=n;i++) d[i][j]=min(d[i][j-1],d[i+(1<<(j-1))][j-1]); } int RMQ(int L,int R) { int k=0; while((1<<(k+1))<=R-L+1)k++; return min(d[L][k],d[R-(1<<k)+1][k]); }
【离散化】
scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&e[i]),w[++lw]=e[i]; sort(w+1,w+lw+1),lw=unique(w+1,w+lw+1)-w-1; for(i=1;i<=n;i++) e[i]=lower_bound(w+1,w+lw+1,e[i])-w;
【STL】
【STL-set/multiset】
set是集合,multiset才是可重复集合。
set左闭右开,s.begin()返回第一位地址,s.end()返回最后一位之后一位的地址(存储的键值不确定)
直接调用set得到地址,需要[*it](int)或[it->x](struct)转换为键值。
set<int>::iterator it; 定义一个可以存储地址的迭代器,迭代即只能it++或it--。
s.lower_bound()
s.upper_bound()
s.find()
s.erase(k)删除键值为k的元素(也可删除地址),注意在multiset中必须s.erase(s.find(k))才能只删除一个元素。
【STL-优先队列】
(priority_queue)
代码为int重载,结构体重载见下面。
#include<cstdio> #include<algorithm> #include<queue> using namespace std; priority_queue<int>big;//大顶堆 priority_queue<int,vector<int>,greater<int> >small;//小顶堆 struct cmp//重载为从大到小 { bool operator() (const int a,const int b) const {return a<b;}//返回真说明要交换,这与sort中返回真说明不交换是相反的 };//要有分号! priority_queue<int,vector<int>,cmp>q; int main() { int n,u; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&u); q.push(u); } printf("size=%d ",q.size()); while(!q.empty()) { printf("%d ",q.top()); q.pop(); } printf(" "); return 0; }
【STL-vector】动态数组
vector之间可以直接赋值或者作为函数的返回值。
s.push_back(x)
s.pop_back(x)
s[x]
没开O2下速度很慢,慎用!
#include<vector> vector<int>p[maxn]; p[0].clear(); p[0].push_back(x);//从0开始! p[0].pop_back(x); if(!p[0].empty);
【STL-bitset】
用于资瓷长二进制。
bitset<100>s;定义s为100位二进制数。
count计算1的数量。
s=p直接将整数p的二进制赋值给s。
bitset<500>a(0),b(0);//括号赋初值 int number=s.count();//计算1的数量 int number=s.size();//大小 s=0;//整体赋值 s[0]=0; a=b&c;a=b^c;
【STL-permutation】全排列
全排列:指n个数字任意排列,出现的不同数列。
下一个全排列:将所有全排列按照字典序排序后,下一个全排列。
只需要定义比较函数就可以实现全排列,不关注数组内的数字是否相同等。
next_permutation(begin,end),有下一个则返回1并改变数组,否则返回0。
prev_permutaion()同理,上一个排列。
#include<cstdio> #include<algorithm> using namespace std; const int maxn=10; int a[maxn],n; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); do{ for(int j=1;j<=n;j++)printf("%d ",a[j]);puts(""); }while(next_permutation(a+1,a+n+1)); return 0; }
【STL-map】
映射,常用于数组太大开不下,或建立string到int的映射。
map<int,int>s;(第一个相当于下标)
s.count(x)判断下标x是否存在
s[x]=y;赋值
s.erase(x)删除下标x
s.lower_bound(x)
两个元素是first和second
#include<cstdio> #include<algorithm> #include<cstring> #include<map> using namespace std; int n,a[100]; map<int,int>s; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); s.insert(pair<int,int>{i+1,a[i]}); } s.erase(3); for(int i=2;i<=n+1;i++)printf("%d ",s[i]); return 0; }
【STL-string】
长度:size()
输入输出:cin>>,cout<<
比较:字典序(常放进map)
拼接:s=s+'x'
【系统时间】
clock()返回long long(不一定表示毫秒),CLOCKS_PER_SEC值为long long,所以要记得*1.0转double。
1.0*clock()/CLOCKS_PER_SEC单位转为s,类型double,注意*1.0
int t0=clock(); //work... int t1=clock(); printf("%d ",t1-t0); double t2=1.0*clock()/CLOCKS_PER_SEC; //work... double t3=1.0*clock()/CLOCKS_PER_SEC; printf("%.1lf ",t3-t2);
【重载】
在结构体中重载运算符。
重载“<”时,priority_queue恰好相反。
好像重载非比较运算符时后面不加const。
struct cyc{ int num,ord; // cyc(){num=0;}//默认初始化 加上后不能直接赋值 bool operator < (const cyc &x) const {return num<x.num;}//重载运算符 }a[100]; a[++n]=(cyc){0,0};//直接赋值
【二分】
二分答案:构造问题-->判定问题
★遵循左优先原则,即和左比较。
while(l<r) { int mid=(l+r)>>1; if()l=mid+1;else r=mid; //靠左就是最左,靠右就是最右+1 }
是否有等号只是决定要找哪个数字区间。
l=mid+1 r=mid
★会寻找到对应数字区间的偏大区间第一个数字。
这也只是一个在区间临界点的与左比较后作出的决策区别而已。
这种方式有边界问题:即如果序列最后一个数字满足要求,但实际上l-1就无法对应答案。
★解决办法是初始r+1,根据左优先原则这个r+1不可能被访问,但可以解决边界问题。
【三分】
初始左右端点l,r
while
左右端点距离≤5时换成暴力。
x1=l+(r-l)/3,x2=l+(r-l)/3*2;
y1=calc(x1),y2=calc(x2);
★更新更劣一侧端点。(优点可能和劣点同侧,劣点只有一种可能性)
end.
复杂度O(log3n)。
注意,三分依赖于两个三分点的大小比较确定最优解方位,故
如果函数存在平段就不能三分。(除非平段在答案处)
当然也有解决方法,如果整个区间平段就是答案,否则每次任取两个点,如果平段就再取直到不平段为止。或者多换一下初始三分点等。
【二进制原理】
1...k中的数可以由1.2.4...2t.k-2t+1+1(2t+2 > k ≥ 2t+1)组成。
(若1...5,则1.2就可以组成1..3,然后再加个2就可以由2+2,3+2得到4.5。)
原理就是1~$2^t$就可以构成1~$2^{t+1}-1$了,补个差值$k-2^{t+1}+1$就行了。
实现:枚举基数指数k,要分解数字为num,不断num-=k,直到num剩余的部分不再足以支撑一个基数2k时,把剩余的num加进去即可。
从二进制表示理解,由于111+111=1110,所以这种做法的实质是:找到最大的111<=x,再加上附加数x-111。
【哈希】
哈希的主要目的一般都是判重,例如叠加给同样的数字一个附加值,给同样的字符串附加信息。对于一个数字和一个数组,判断相同要全部扫一遍数组,时间上承受不起,或者建成一对一判定的数组,空间上又承受不起,所以找到折中方案:哈希,把数组取模然后做成一些链。
哈希空间换时间(和邻接表一样把真实情况堆进一个数组,用头表记录位置):MOD一般开到点数三倍大小。
<1>first[MOD](头表) <2>tot(总数) <3>e[STATE].from(上一个)state(真实值)num(附带信息)
这三者也是构成邻接表的核心,对于邻接表MOD换成点数,STATE换成边数,再改一下记录的信息而已。邻接表是一个点的邻边组成一条链,哈希表是一个哈希值对应的真实值组成一条链。
操作包括:1.取指定数字的附加值:取模后去对应链找匹配真实值的数字;2.存入指定数字的附加值:取模后去对应链找匹配真实值的数字;3.遍历:直接遍历杂乱的原值数组,这时哈希只是在插入时起了判重作用;4.清空:清空头表first和tot,只有这个操作跟复杂度与MOD大小有关。不过有一种方法:记录时间戳,每次检测到旧时间戳就更新之。
struct HASHMAP{ int first[MOD],tot,from[STATE],state[STATE],f[STATE]; void init()//清空 { tot=0; memset(first,0,sizeof(first)); } void push(int number,int num)//插入 { int x=number%MOD; for(int i=first[x];i;i=from[i]) if(number==state[i]) { f[i]=min(f[i],num); return;//已有,不用加入,直接return } tot++; state[tot]=number; f[tot]=num; from[tot]=first[x]; first[x]=tot; } };
字符串哈希见【算法】字符串。
双hash,其中一个用自然溢出,好像还不错。
【高级搜索方法】
启发式搜索(A*算法)
只考虑起点出发的dijkstra算法,每次将点集中距离起点最短的点加入考虑,到达终点为止。
只考虑终点启发的BFS算法,每次将距离终点估价最短的点加入考虑,到达终点为止。
启发式函数f(n)=h(n)+g(n),g(n)表示从初始结点到任意结点n的代价,h(n)表示从结点n到目标点的启发式评估代价
启发式函数可以控制A*的行为:
1.如果h(n)是0,则只有g(n)起作用,此时A*演变成Dijkstra算法,这保证能找到最短路径。
2.如果h(n)经常都比从n移动到目标的实际代价小(或者相等),则A*保证能找到一条最短路径。h(n)越小,A*扩展的结点越多,运行就得越慢。
解释:h(n)较小时,终点的启发性较差,结点就会四处拓展,速度较慢。h(n)比实际小就不可能影响最短路径的决策。
3.如果h(n)精确地等于从n移动到目标的代价,则A*将会仅仅寻找最佳路径而不扩展别的任何结点。这就是A*求解第k短路的原理。
4.如果h(n)有时比从n移动到目标的实际代价高,则A*不能保证找到一条最短路径,但它运行得更快。
解释:h(n)较大时,终点启发性很强,结点会趋向终点方向拓展。
5.如果h(n)比g(n)大很多,则只有h(n)起作用,A*演变成BFS算法。
A*的核心是估价函数的设计——结合启发式函数和常规函数。
迭代加深搜索(ID算法)
从小到大枚举深度上限maxd,每次执行只考虑深度不超过maxd的结点,相当于强行规定解的深度避免无限度搜索。
只要解的深度有限,就一定能在有限时间枚举到。
IDA*算法(ID+A*)
在迭代加深搜索的基础上,设当前深度为g(n),乐观估价函数为h(n),如果g(n)+h(n)>maxd时应该剪枝,这就是IDA*算法。
【其它算法】
【分数规划】转换为与0的判定问题
【搜索】
剪枝:可行性剪枝,最优性剪枝
可行性剪枝:奇偶性剪枝、剩余和不可达剪枝
【倍增】每次变化规则必须相同并满足结合律!
【折半搜索】Meet-in-the-middle思想的一些应用
寻找a+b+c+d=0,转化为a+b=-(c+d),一边哈希一边枚举。
对于n的选择,分成两个值数组,分别存储前后n/2的数字组合出来的数字,然后两个指针(从大到小和从小到大)顺序考虑合并。
【暴力DFS】
dfs括号内存对应参数。
哈希:排序后做成base进制数判重。
【进制转换】
十进制转二进制 while n>0 do begin inc(num); a[num]=n mod 2; n:=n div 2; end; 二进制转十进制 now=1 for i=1 to num do begin ans:=ans+a[i]*now; now:=now*2; end;
【后缀表达式】
【高精度乘法(high*low)】
x=0; for(int i=1;i<=lens;i++) { x+=sum[i]*num; sum[i]=x%10; x/=10; } while(x>0)sum[++lens]=x%10,x/=10;
【高精度除法(high/low)】
【字符串哈希】
const int MOD=100000007; int Hash(char ch[]) { int s=0; for(int i=0;i<strlen(ch);i++) {s*=27;s+=(ch[i]-'a'+1);s%=MOD;} return s; }
【贪心】【BZOJ】1828: [Usaco2010 Mar]balloc 农场分配(经典贪心)
【指针】
int *a; 定义指向int的指针a
*a 取地址的变量
a-->b 取地址的结构体变量
&a 取变量的地址
【基数排序】
对于每一段:
①memset
②桶加A
③指针数组指向新数组B
④赋值给指针数组A
⑤swap
namespace Qsort{ int *s[300],a[maxn],b[maxn],T[300]; void qsort(int *a,int n){ int *A=a,*B=b,*mp; int mx=256; for(int k=0;k<4;k++){ memset(T,0,sizeof(T));mp=B; for(int i=0;i<n;i++)T[A[i]>>(8*k)&255]++; for(int i=0;i<mx;i++)s[i]=mp,mp+=T[i]; for(int i=0;i<n;i++)*s[A[i]>>(8*k)&255]++=A[i]; swap(A,B); } } }
【RMQ-LCA】例题: 树上的最远点对
dfs序:每个点的入栈出栈序,可以选择记录出栈点或不记录。(方便查询子树,如果记录入栈+1出栈-1则可以维护点到根的路径:星系探索)
欧拉序:中序遍历,每次遍历到就记一次。
欧拉序求LCA:两点区间的深度最小值,用RMQ求解。
欧拉序求子树权值和:查询该点在欧拉序中最后出现的位置的前缀减去第一次出现的位置-1的前缀和。
【哈夫曼树】Huffman Tree
哈夫曼树又称最优构造树,是使树的带点权路径长度和最小的树的形态。
带点权路径(WPL)=点权*深度。
构造方法:每次取点权最小的两个根节点作为左右子树(左小右大)组成新根节(点权为左右之和),多次操作直到只剩一棵树。(类似合并果子)
哈夫曼编码:从根出发左0右1,走到每个字符的二进制就是它的编码。
Huffman Code是电文总长最短的二进制编码方式,将n个字符的出现频率作为点权构造哈夫曼树得到的就是对应的哈夫曼编码。
例题:【CodeForces】700 D. Huffman Coding on Segment 哈夫曼树+莫队+分块
【暂存】
http://www.cnblogs.com/ka200812/archive/2011/09/02/2164404.html
消(tui)遣(fei):http://mp.sohu.com/profile?xpt=c29odWJsb2diaXpAc29odS5jb20=
http://blog.csdn.net/qiankun1993/article/details/6765688
http://blog.csdn.net/kk303/article/details/6692506
【竞赛核心原则】
1.调试:常见难调错误有,[变量类型long long和1ll],[语句顺序颠倒],[初始化]。
细心考虑所有可能的特殊情况,一失足成千古恨。
过了样例,为了保证正确可以手动模拟一下中间过程。
善用静态差错,冷静地逐行分析可以省掉一大堆无谓的调试时间。
不要盯着代码一直看,静态查不出错就必须勤动手,别懒。
敢于重构代码的勇气,有舍才有得,重构的过程也能查出错误。
调试的时候打开O2,可以查错(特别是数组越界)。
2.思考:题目都是简单的,一旦对题目产生畏惧就再也想不出来了,不用害怕最后一题。
看清题意,思路清晰,一步一步转化,不要一团乱麻。
把想到的东西写下来,把可能的公式列出来,可能就会有新的发现。
多写写公式,多进行数学推导。DP多试几种表示。
不要考虑太多奇怪的细节,把握好大局。
别怕常数,别畏畏缩缩,相信自己!1s可以跑4亿纯循环!
想不出来的题目就换个角度想,换个思路。
再想不出来的题目就换个算法,脑洞开大。
二分答案很多时候是打破僵局的重要思路。
3.策略:先写暴力,即稳定得分,又方便对拍。善于对拍,没对拍的题能AC是运气。
切忌恋战,不要犹豫,想不出来赶紧跳,不然追悔莫及。
不会写正解时,假装看错题意,简化算法来骗分。
think twice,code once,动手前一定要把正确性思考清楚。
NOIP和NOI都是部分分比赛,不要太贪心倒是该拿的分没拿稳。
(暴力拿满就能省一,暴力写好就能进队,暴力写全就能碾压。)
(题目是难的,数据是水的,出题人是懒的。你永远不知道你的暴力能搞多少分。)
4.心态:心态平稳,到最后一刻也不要放弃,相信自己。
★珍惜时间!合理分配比赛时间,切忌前松后紧!
考试结束前5分钟改代码大概率是错的,改代码前一定要冷静!
5.命名空间,部分分利器。
namespace Task1{
void solve(){}
}
int main(){
if(check1()){
Task1::solve();
return 0;
}
}
6.补充
自信,相信自己写的没错,好好调。
一直看不出错,说明错在一般不考虑的位置。
不要犹犹豫豫,先写好写的暴力。
能拿的分不丢,不会的分尽量骗 1.会做的subtask不丢分:如果一个subtask你会做,比如遇到了水题,或者某道题你会写30分的小数据,那么请务必确保你能拿到这部分分数。以及,不要嫌分数少而懒得写!30分也是分,10分也是分,在分数拉不开的情况下(如果你水平并不优秀,这就是你总是要面对的情况),10分的差距就决定了你是一等还是二等。 请大家记住:在大多数OI比赛中,只要暴力分全部拿到,就是一等奖。 2.常见有效骗分算法试举例:数据是死的,人是活的,出题人是懒的 * 若干个错误的贪心、动态规划取最优解,一个数据同时卡掉多个错误算法很难 * 暴力算法加奇怪剪枝,出题人不一定想得到你的奇怪剪枝,因此不一定卡的掉 * 基于某些东西的暴力(比如树上的题目和深度有关、和点的度数有关的暴力等) * 代码的常数优化,配合上述优化,进一步增加卡过去的可能性 3.本着“先拿确保能拿到的分数”的原则,建议先把会写的分数都写出来。不会的题目的暴力也都应当写一下,因为如果你最后没做出来,那暴力可以交上去拿朴素分;如果你最后做出来了,也需要暴力来对拍。同时暴力也能为正解提供一些思路。 4.方法/技巧: * 使用草稿纸,有用的性质都可以记录下来,零散的思路也可以画下来,这很有助于思考。 * 尝试加强/放宽题目的条件/约束,得到更特殊的模型。思考特殊模型的解法并尝试推广到原问题。 * 探索是否存在隐藏的性质,尝试从边界、特殊情况开始考虑,猜测、证明性质。 * 是否能换一个思路?从反面思考是否更简单?是否可以利用二分答案之类的技巧进行转化?NOIP的题目对思维深度的要求一般不高,如果想了很久都没想法,可能是你思维方向错了。 * 某某算法是否可能应用于这个问题?逐一确定。(如果你有思维死角,比如总是想不到用某个算法,可以考虑这样) 5.多去上厕所,不要让自己长时间陷入某项工作(思考、调试)中。 原因一是人在长时间思考或调试时会效率下降,而且容易钻牛角尖,陷入错误的思路中。 二是时间也会不知不觉中过的特别快,容易出现类似“卧槽考试只剩一个半小时了还一点想法没有怎么办”这种很糟糕的情况。 因此,如果你想了一段时间却毫无想法/调试了很久找不到问题所在,请务必从中跳出来,换一个思路,切勿陷入其中,白白浪费时间。 6.会做的题不要写错! 不会做的题,要写暴力!要骗分! 不要钻牛角尖,高效利用时间! 无论发生什么,保持心态稳定! 7.低级错误: * 文件名打错/忘记用文件输入/忘记用文件输出/忘记关文件 * 数组开小 * 爆了内存限制 * 运算时爆int,忘记强制转换为long long * 对答案取模的题目有地方忘记取模 * 输出时格式错误 * 输出64位整数时没有用%lld * 提交时交错文件/打错扩展名 8.知识性内容 * 模拟、枚举、爆搜、高精度计算等较基础内容 * 贪心、构造、结论题等比较坑的 * 动态规划及其优化(以前出过单调队列,但会不会出现更难的就不知道了) * 基础数据结构(部分和、简单的线段树等常见的维护方法) * 树论,树形dp,树上的数据结构(lca,倍增祖先,dfs序等) * 图论,最短路,最小生成树等 * 简单数论(逆元、快速幂、素数筛法、中国剩余定理等) * 常见转化方法(二分答案、分治等)、常见技巧(压位等)、推理分析的能力 9.常用的性质证明方法:调整法,对这种“重新排列”某个序列,要求最优化某个东西的题目,可以考虑“某个解如果交换相邻两个元素,什么情况下会变优”或“最优解的元素次序间必须满足什么样的性质,才能使得不管怎么交换都得不到更优的解”,进而发现有用的性质。 放缩法:放宽/加强约束条件。
【技巧】
珍惜时间啊>w<!!!只有4小时QAQ!!!
没有难题,只有复杂题,不要害怕复杂的题目,可能都是简单的算法!!!
不要害怕第四题!!!
1.超级无敌贪心,部分贪心(小数据暴力,大数据贪心)。
2.随机化O(n*m*k),调用clock计算出满足时间上限的最大值,记录下乘积。
3.字符串哈希,哈希判重。哈希↑。
4.随机化算法:爬山、退火。
5.求不出来的量、未知量就枚举二分三分(其实很多时候是正解)。
6.二分将求值问题转化为判定问题。
7.环:最短路遇负环X最长路遇正环X——图论tarjan缩点或网络流消圈
8.奇怪的题可以暴力打表找规律,不怂!敢写敢干!就算找不出规律,
也可以暴力跑表放进代码里(注意长度限制),const int a[1000]={...}。
9.要改写暴力就别犹豫,该造数据/对拍就造数据/对拍。
10.sort复杂度怕过不去就试试基数排序。
11.心态:遇到复杂的题目描述也不用害怕,其实都是我们熟悉的算法,建模出来就没问题了。
注意看数据范围大致考虑一下算法。说不定考虑到最后搜索就是正解呢。
12.一些差值问题、区间问题可以考虑差分。
13.树考虑树型DP
14.奇怪的DP若感觉有依赖关系,要么考虑一动一不动,要么把状态表示出来。
多维DP,求其中一维最优,令另外多-1维转移。
不要一开始就胡思乱想一些奇怪的细节,要把大方向定下来,找出不变的因素,状态表示出来后再想状态转移就不难了。
往往状态表示才是重点!
不要考虑太多奇怪的细节,大局把握好!
我想题最大的缺陷是总是容易陷入一些浮于表面的奇怪细节,自以为是该题的特点,大局观较差。
15.没事打表找规律
16.先写好写的暴力,然后出正解也可以对拍。
17.时间戳避开频繁的初始化,多组数据也是如此,dfn。直接memset有超时的风险。
18.搜索都可以尝试记忆化。
19.依赖关系:背包||闭合图||树型DP||图论
20.精度大一点(不T),总是好的>_<
首先可以发现如果平均值是已知的那么其实所有边的边权就可以直接计算出来了. ( ((((一些数对另一个数的差)的平方)的和)的最小值)一定在这个数是这些数的平均值时取到) 所以可以枚举平均值然后直接计算出当前的最小生成树即为一组解. 然后设f(x)为在mid = x时所得到的答案. 发现f(x)的函数图像是一个波形函数,不过周期很长. 所以三分一次得到正确解的可能性很大. 所以随机左右端点多三分几次就好啦QAQ...
21.难以实现的功能真的可以尝试直接枚举!
22.小心看漏条件和读错题,反复看清楚和想清楚再下手,切莫冲动,一旦下手切莫拖延!
23.随机大法好:限时随机化 贪心出奇迹 贪心代替DP转移 预处理然后随机化O1算出答案求min或max
24.小范围暴力大范围高分水法。
25.状态压缩往往是提高效率行之有效的办法,如把30个bool压成int。
26.对于无环连通块,点数-连接数=1。
27.忽略题目条件,钻数据漏洞,考虑缺陷算法。
28.调和级数:枚举公差,1~n中等差数列枚举,复杂度n log n
29.暴力:双向搜索,中间相遇,meet in the middle,两边搜,中间合并。
30.差分思想,(x,y)=(1,y)-(1,x)
还有一种在L打+1,在R+1打-1,维护前缀和即可知道左闭右开区间信息。
31.预处理,或者记忆化,都是避免算重复提高效率。
32.线段树出奇迹,有神奇的左右子树性质,每次询问修改都会访问到对应子区间。
只需要记录答案对应变量,维护好左右合并的情况,就有很多题都能用线段树做啦。
33.最大最小数字--->字典序--->保证不升序或不降序!越前越极!无脑前先极导致了不升或不降。
34.离线出奇迹。
while别写成if。。。
35.二分就是二分答案,当答案具有单调正确性时就可以二分。
36.静态差错好东西呀!
37.无向无环图就是树!!!
38.求移动区间最值,使用单调队列优化。
39.注意设置初值,特别是long long。当有调试输出的时候,没有初值的元素就可能被随意给初值。
40.贪心算法通常可以加入反悔措施来保证正确性和复杂度,网络流也是这样的思路(反向边)。
41.使用set时常常是multiset!!!别用错了!!!很难查出错的!!!
42.套路:模2=取反=异或
43.序列交换问题:整体挪位用树状数组维护,整体前移就从1到n依次处理(开邻接表),剩下的位置不会变化。
44.取模有减法的时候一定要+MOD(即使是大-小,取模也会出现负数),取模有除法的时候一定要求逆元。
45.二分就是二分答案,答案判定具有单调性。
46.单调队列:移动区间最值。
47.经典贪心:【BZOJ】1828: [Usaco2010 Mar]balloc 农场分配(经典贪心)
48.背包方案统计:g[i][j]=0/1表示从上面转移来(不选)或从左上转移来(选)。
一维排序后使用背包CF864 problemE
49.多点距离用总距离减去坐标距离和,如【UOJ】#49.铀仓库
50.用加法构成数字时,可以考虑所有数字都由二进制分组构成。
51.树上路径统计:在LCA处,每个点和前面的子节点统计后加入。
52.位运算和二进制一定要考虑拆位。
53.看到16一定要想到状压和3^n枚举子集。
54.异或可以差分,xor(i~j)=xor(j)-xor(i-1)。
55.树上路径差分:x到根+y到根-lca(x,y)到根+fa[lca(x,y)]到根。
所以树上路径题目除了树链剖分,还可以考虑差分离线。
56.给定最短路图的题目,往往要建完全图,然后再考虑删边或选边。
57.可持久化权值线段树(主席树)用于快速解决区间权值询问问题。(区间第k大等)
58.线段树合并用于解决一堆数组的合并,通过简化表示同时解决时间和空间不够的问题。
59.树上最短和次短问题可以转化为最短和子节点最短再并起来的问题。
60.看到区间操作就要想着化成2个单点操作。
61.随机数据下的序列,LIS的期望长度为log n。考虑将序列按顺序加入二叉查找树,LIS就是最右一条链,深度期望Log n。
【零碎の东西】知识&&错误
★linux下取模可以为负。
★记得开最大警报+O2优化
★记得开long long,换%lld。或者乘1ll临时转方便取模等。
★long long=1ll*int*int
★记得取模
★long long和struct要初始化!
★过了样例一定要细心+耐心地检查代码
while不要写成if
★while不要写成if!常常检查不出来!
多组数据时,碰到要初始化的变量马上回去初始化。
多个输出该换行记得换行
long long(9*10^18)范围10^18(1后18个0)及以下
unsigned
const int inf=0x3f3f3f3f;
const long long inf=1ll<<60;
循环队列要while(head!=tail)
lower_bound(a+1,a+n+1,x)-a 找到a数组中第一个≥x的数的位置
upper_bound(a+1,a+n+1,x)-a 找到a数组中第一个≤x的数的位置
tot=unique(a+1,a+tot+1)-a-1;去重并得到新数组元素个数tot
100000007(>10^8)为素数,1和7中间夹7个0
985003为素数
memcpy(载体,来源,个数),起始位置可以通过在载体和来源处±1(改变指针指向位置)来改变。
个数:int类型*4,ll类型*8,char类型*1
-O2优化(比赛或测评一般都会开)会对未定义行为任意操作以达到优化的目的
所以会卡掉数组越界、变量未定义初值、子程序无返回值等。
平方操作-->斜率优化?
任意底的log,都可以用log(x)/log(底)凑出来
cmath-log(x)返回e为底x的对数,exp(x)返回e的x次方。
exp(log(x))==x(±eps)
scanf("%c %c",c1,c2)中间加空格可以一直跳到下一个有效字符(即空格和‘ ’)
要取模的题减法记得+mod。
位运算最快,所以在二进制操作中可以用||和^代替+和-。
int有0~31位(1<<x,x=0...31),最高位(31位)是符号位,正数为0,负数为1。
long long有0~63位,最高位(63位)是符号位。
1<<32开始不合法,必须写为1ll<<32(即long long)。
或:(运算)‘l’ (条件)‘ll’
for i--
#define int long long int==signed(!=unsigned)
[byte]bool-1 int-4 long long-8 1byte==8bit
可输出sizeof(类型)表示单位大小,sizeof(变量/数组)表示其大小
struct一般会将其所有元素对齐至占位最大元素的大小,具体要用sizeof测定
比赛时数组不能开到太临界(128->110),堆栈大概10w~100w层,占几M空间。
double占8字节(long long),long double占16字节(据说较慢)。
浮点数都是从最高位开始算有效数字位的,有效数字:double为15~16位,long double为18~19位。
中间不要取模不然相减会出错。
scanf返回读入数据项数(字符数组也算1),未成功读入返回0,失败或文件结束EOF返回-1(EOF=-1)。
‘~’表示含符号位的按位取反操作,实际效果是0和-1,1和-2,2和-3,正数与其相反数-1配对。
'^1'和1异或可以将对应二进制位取反,和0异或则不动。
1ll* 取模临时转long long
unsigned long long 输出占位符:%llu 强转:1ull*
一般出现奇怪的数字情况很可能就是数组越界。
在树上使用邻接表一定要记得判断父亲。
优先队列删除:优化dijkstra那种做法
if(~a)...~(-1)=0
二进制枚举子集的子集(不包括空集):for(int i=st;i;i=(i-1)&st) 每次减1然后&掉多余的不在其位的1。
枚举总集所有集合再枚举子集,复杂度O(3^n),每个点只有三种可能(子集+子集,非+非,子+非)
使用long long时要1ll<<x!!!
线段树要开4倍空间!
RE:除0,访问界外,数组开小。
开long long记得快读改返回值为ll。
多关键字double快排的cmp要这么写return fabs(a.x-b.x)>eps?a.x<b.x:a.y<b.y;
eps不要调太小,调到合适的值,给自己的精度误差一点空间。1e-10炸了,1e-6过了看情况从1e-3~1e-13调(不排除1e-1)
复杂度为n log n时,离散化尽量写O(n)的,O(n log n)只在需要额外的二分查询时才写。
STL使用时后面都带括号。
set<int>::iterator it;
时刻检查数组大小,特别是要开双倍数组的时候。
STL容器左闭右开,失败就跑到end处。
末尾0的个数看2*5的数量,小学奥数233
int转double时要注意1.0*的地方要全面。
边表用tot,其他地方不要重!!!
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa)
实数类型没有等于!!!
奇怪问题都是空间开小!
无向图不要e[i].v!=fa。
快速取模:ans=(ans+1ll*x*y)%MOD等价于ans=mod(ans+mod(1ll*x*y)),mod中对MOD^2取模,最后再取模MOD。
求解二次方程的时候要注意判断A=0的退化一次情况,否则除0。
2的逆元是(MOD+1)/2。
边maxm无脑开两倍。
需要频繁memset的时候记得把数组空间开小,时间戳和垃圾回收都能取代memset以加速。
分清楚[&]和[|]。
树链剖分记得调用两个dfs!!!写了dfs2要记得调用!!!
树链剖分find时比较的是顶点深度!!!
注意常见错误——变量操作顺序,先运算最后赋值。
结构体整体赋值的时候,注意变量设置顺序问题。
操作顺序!语句操作顺序!先dfs后操作!
变量名不要起大小写,不要起容易混淆的变量名。
3个inf相加会爆inf!
枚举变量名ijk尤其注意。
as是iostream的关键字,pow是cmath的关键字。
注意常用名字,多个地方不要使用同一vis等。
给的位置不一定有序,记得排序。
树上差分一定要u+1,v+1,lca(u,v)-2,不能再lca处加。
Dijkstra如果TLE肯定是堆开反了。
初始化0 1 2和1 2 3的区别。
2的逆元:inv(2,MOD)=(MOD+1)/2。
make 0 no influence:数据结构中为了方便常常用0表示空节点,这时候所有修改操作都要注意不能修改到0的值,使其对全局不影响。
输入取摸!!!
局部变量和全局变量的致命问题。(WC)
调代码多动手输出。
基排数组的值域不能用n,用mx。
【其它】
【卡常】
1.取模如果是加法用子程序MOD比直接取模快三倍。
2.4亿的纯循环刚好1s。
3.剪枝大法好,但要注意不要剪过头。
//4.全部函数名前面加inline,有益无害。
★5.手写max,手写min,dev C++编译器的max和min慢到极点。
6.线段树常数巨大QAQ
查询最小值可以query剪枝。
区间操作复杂度O(4*log n)(无关区间大小),单点快于区间。
从头开始查比从中间开始查更快。
★7.用fread快速读入!
//8.减少传参多开全局减小常数。
//9.结构体不要太大,尽量用数组。
wys课件 底层优化
【初赛】前面题目快速过只写一眼题,把程序补全和看程序写结果完成后再回来慢慢推。
1.NP!=P
P问题:可以多项式时间内求解的问题
NP问题:可以多项式时间内验证解的问题
NP-Hard问题:所有NP问题都可以在多项式时间内归约为NP-Hard问题。
NPC问题:是NP-Hard问题,也是NP问题。(所有NP问题都可以多项式时间归约到此问题,同时此问题可以多项式判解)
也就是解决了一个NPC问题就解决了所有NP问题(即NP=P)
著名的有SAT、TSP(旅行商)、哈密顿回路、背包问题等。(所有判定性问题都可以构造成SAT问题)
关系图:简单理解 NP, P, NP-complete和NP-Hard
NP-hard也存在不是npc的部分,这部分即使np=p也不能多项式时间判解或验证解,如找一个完美的女朋友。
2.电子管-晶体管-集成电路-大规模集成电路-生物光量子计算机
计算机硬件:计算器、控制器、存储器、输入设备、输出设备
①中央处理器(CPU)由运算器、控制器和一些寄存器组成。
②存储器:内存(主存、缓存)和外存
缓存:速度快,容量小,位于CPU和主存之间。
主存:只读存储器(ROM)不因断电丢失信息、随机存储器(RAM)因断电丢失。
外存:硬盘、软盘、光盘、U盘
③总线:各部件的公共通道,多条导线。
3.信息计量:1Byte(字节)=8bit(位,比特)。
4.运算中全部转化为补码运算。
原码:1符号位(0正1负)+7数字位
反码:正数同,负数反码符号位1+数字位按位取反。
补码:正数同,负数为反码+1。
5.面向对象的编程语言:C++,C#,Java,Visual Basic
6.Internet服务与协议:
IP地址分配协议:IPv6协议
地址:URL
网络体系:(物理层-数据链路层-网络层)-运输层-(会话层-表示层-应用层)
超文本传输协议:HTTP
电子邮件协议:SMTP发送,POP3接收
远程登陆协议:Telnet
文件传输协议:FTP
以上都属于TCP/IP协议簇,属于应用层协议。
传输层:传输控制协议TCP
端口是TCP和UDP与应用程序打交道的访问点。
7.网络体系
国家顶级域名:cn(中国)、us(美国)、uk(英国)
国际顶级域名:int
通用顶级域名:com、net、edu
网络体系:OSI体系和TCP/IP体系,传输方式是顶到底到底到顶。
8.二进制小数转十进制:num*2^(-x),小数部分和整数一样都是乘权。
十进制转二进制小数:每次乘2后取整数位。(只有2^(-x)的小数才能转二进制)
【神奇の博客们】
百度:HEOI 2017 游记&&省选 总结
http://paste.ubuntu.net/ 贴代码
http://blog.csdn.net/ACdreamers
http://orzwzh.com/
http://www.jkxing.net/post/jiu-ri-oi/2016-09-20
http://blog.csdn.net/zxy_snow/article/category/780782
http://www.xysay.com/
http://lib.csdn.net/base/datastructure 算法与数据结构知识库(基础?)
http://www.cnblogs.com/daoluanxiaozi/
很强的blog:http://www.cnblogs.com/pony1993/
http://www.matrix67.com/
OI blog:http://www.cnblogs.com/suishiguang/
http://www.cnblogs.com/iwtwiioi
http://blog.csdn.net/PhilipsWeng/article/category/2473431 (weng wen tao?)
http://blog.csdn.net/PoPoQQQ 超稳
http://www.cnblogs.com/kuangbin/ 总结超好
liu_runda 衡水高二大佬
过去的2015-claris
http://www.cnblogs.com/lidaxin/ 封面换图dalao
时间并不会因为你的迷茫和迟疑而停留,就在你看这篇文章的同时,不知道有多少人在冥思苦想,在为算法废寝忘食,不知道有多少人在狂热地拍着代码,不知道又有多少提交一遍又一遍地刷新着OJ的status页面……
没有谁生来就是神牛,而千里之行,始于足下!
https://www.bilibili.com/blackboard/activity-Miku10th.html MIKU!
AC. 梦想 好多友链 http://www.acyume.com/archives/456
D_Double's Journey实用技术博客。vim