早春略过芳草地 慵懒的阳光或也有了些许生机
三月 是个有着诗歌一般韵律的日子
每晚的机房 有着与题目的相约的痕迹
和着早雨 在淡抹的春意中小憩
摘选出的 13 道题 是在 衡阳八中 上 做题的痕迹
2015.3.13
Bzoj 3884 上帝与集合的正确用法
f(x) = 2^2^2^2^2^2..^2 Mod x
f(x) = 2^((2^2^2^2^2..^2) Mod Phi(x)+Phi(x)) Mod x=2^(f(Phi(x))+Phi(x))
f(1) = 0
中间运算过程有可能超过 Longint 写了 Log2(n) 的二进制加法 速度上只有 200 Ms + 了
#include <cstdio> #include <algorithm> #include <cmath> using namespace std; #define Rep(i,x,y) for (i=x;i<=y;++i) int p; int Phi(int x) { int i,T=x; Rep(i,2,(int)sqrt(x)) { if (!(x%i)) T=T/i*(i-1); while (!(x%i)) x/=i; } if (x>1) T=T/x*(x-1); return T; } int CalcC(int x,int y,int z) { int T=0; while (y) { if (y&1) T=(T+x)%z; x=(x+x)%z; y>>=1; } return T; } int Calc(int x,int y,int z) { int T=1; while (y) { if (y&1) T=CalcC(T,x,z); x=CalcC(x,x,z); y>>=1; } return T; } int Ask(int p) { if (p==1) return 0; int P=Phi(p); return Calc(2,Ask(P)+P,p); } int main () { int T; scanf("%d",&T); while (T--) { scanf("%d",&p); printf("%d\n",Ask(p)); } return 0; }
Bzoj 2423 [HAOI2010]最长公共子序列
动态规划
记 长度 F[i][j]=max(F[i-1][j],F[i][j-1],F[i-1][j-1]+1 | S[i]==S[j] )
记 方案总数 G[i][j] 分类讨论 S1[i]=S2[j] S1[i]!=S2[j] 讨论!=时注意容斥原理 需要减去之前的一段
注意滚动数组
2015.3.14
Bzoj 2456 mode
抵消思想 mode 一定是可以抵消到最后的
#include <cstdio> using namespace std; #define Rep(i,x,y) for (i=x;i<=y;++i) int x,Mode,n,i,T; int main () { scanf("%d",&n); scanf("%d",&Mode); T=1; Rep(i,1,n-1) { scanf("%d",&x); if (x==Mode) ++T; else --T; if (T<0) Mode=x,T=1; } printf("%d\n",Mode); return 0; }
Bzoj 1264 [AHOI2006]基因匹配Match
注意到恰好出现5次以及数字为[1..n]的排列的特点
F[i]=Max(F[j]|1<=j<i)+1
以树状数组维护 Max(F[j]) 可以降到 O(5*nLog2(5*n)) = O(nLog2n)
#include <cstdio> using namespace std; #define Rep(i,x,y) for (i=x;i<=y;++i) #define _Rep(i,x,y) for (i=x;i>=y;--i) #define Lowbit(x) x&(-x) #define Max 101001 int n,N,S,T,x,C[Max],i,A[Max][6],k,p,F[Max],G[Max]; void Insert(int x,int y) { while (x<=N) { if (y>C[x]) C[x]=y; x+=Lowbit(x); } } int Query(int x) { int T=0; while (x>0) { if (C[x]>T) T=C[x]; x-=Lowbit(x); } return T; } int main () { scanf("%d",&n); N=5 * n; Rep(i,1,N) { scanf("%d",&x); A[x][++G[x]]=i; } Rep(i,1,N) { scanf("%d",&x); _Rep(k,5,1) { p=A[x][k]; T=Query(p-1); if (T+1>F[p]) F[p]=T+1; Insert(p,F[p]); if (F[p]>S) S=F[p]; } } printf("%d",S); return 0; }
2015.3.15
Bzoj 1406 [AHOI2007]密码箱
x<n x^2 Mod n=1 => n | (x+1)(x-1)
n=xy => x | K y | K
所有 [1..n] n 的约数及其倍数 P 满足条件的数的 偏序序列
令 x = k-1 则 x-1 = k-2 Mod (n/P) = 0 可以得到 x = k-1 满足
令 x = k+1 则 x+1 = k+2 Mod (n/P) = 0 可以得到 x = k+1 满足
时间复杂度 O((n/P For Range(P 为 n 约数)) * Log 其) 好像是靠谱的
/*x<n x^2 Mod n=1 => n | (x+1)(x-1)*/ /*n=xy => x | K y | K */ #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <set> using namespace std; #define Rep(i,x,y) for (i=x;i<=y;++i) const int Maxn = 1000001; int N,n,i,A[Maxn],Top,k; set<int> S; int main () { scanf("%d",&n); N=(int)sqrt(n); Rep(i,1,N) if (!(n%i)) A[++Top]=n/i; Rep(i,1,Top) { int x=A[i]; for (k=x;k<=n;k+=x) { if ((k-2)%(n/x)==0) S.insert(k-1); if ((k+2)%(n/x)==0) S.insert(k+1); } } S.insert(1); for (set<int>::iterator it=S.begin(); it!=S.end(); ++it) if (*it<n) printf("%d\n",*it); return 0; }
Bzoj 1050 [HAOI2006]旅行comf
将边表排序
固定一条最短边 Min 将其后的以并查集维护连通性 若联通 则 Max = Its Edge
求得 Max/Min 最小
2015.3.22
Bzoj 3295 [Cqoi2011]动态逆序对
树状数组套线段树
内层的线段树可以打删除标记
询问的时候直接在很多棵线段树上一起走一遍即可
注意其满足区间减法
因此可以 以 AskSeg(R) - AskSeg(L-1) 简化操作
2015.3.24
Bzoj 1816 [Cqoi2010]扑克牌
二分答案
注意 最大值为 Min(m,Mid) m 为总共的数量 Mid 为该次判断最多能用的数量 也就是保证总共不超过每副牌一张
#include <cstdio> #include <algorithm> #include <cstring> #include <cmath> using namespace std; #define Rep(i,x,y) for (i=x;i<=y;++i) const int Maxn = 61; int n,m,L,R,A[Maxn],i,Mid; bool Check(int x) { int i,T=min(m,x); Rep(i,1,n) if (A[i]<x) { T=T-(x-A[i]); if (T<0) return 0; } return 1; } int main () { scanf("%d%d",&n,&m); Rep(i,1,n) scanf("%d",&A[i]); L = 0, R = 1000000000; while (L+1<R) { Mid=(L+R)>>1; if (Check(Mid)) L=Mid; else R=Mid; } if (Check(R)) printf("%d",R); else printf("%d",L); return 0; }
Bzoj 1876 [SDOI2009]SuperGCD
Python 直接辗转相除
A,B=input(),input() x=A%B; while x!=0: A=B B=x x=A%B print B
2015.3.28
Bzoj 1046 [HAOI2007]上升序列
求出以每一个为开头的最长上升子序列
询问时直接贪心
Bzoj 1833 [ZJOI2010]count 数字计数
S[a,b] = S[1,b] - S[1,a-1]
S[1,x] 可以通过数位 Dp 或者分块统计的思想 Log10 接近于常数时间计算
2015.3.29
Bzoj 1052 [HAOI2007]覆盖问题
http://tianyong1995.blog.163.com/blog/static/20772234420127193820630/
二分答案 L
重建矩形 从左上角/右上角/左下角/右下角 开始枚举 判断
时间复杂度 O(8*n*Log2(Inf))
[另外个人感觉可以线段树 + 扫描线 + 贪心的 离线以后扫描 大概可以做到 O(n(Log2n)^2) 以上做法未写过 正确性不保证]
#include <cstdio> #include <algorithm> #include <cstring> #include <cmath> #include <iostream> #include <cstdlib> using namespace std; const int Maxn = 20010 , Inf = 2015032990; #define Rep(i,x,y) for (i=x;i<=y;++i) int n,X[2],Y[2],_X[2],_Y[2],x[Maxn],y[Maxn],__X[2],__Y[2],L,R,Mid,i; bool P[Maxn]; bool Ok(int W) { int S = 0,i,j,p,q,_x,_y,k,_S; Rep(i,0,1) Rep(j,0,1) { S = 0; _x = X[i] , _y = Y[j]; _X[1] = _Y[1] = -Inf ; _X[0] = _Y[0] = Inf; memset(P,0,sizeof(P)); //注意每次枚举第一个正方形时把之前的清零 Rep(k,1,n) if (!P[k] && abs(x[k]-_x)<=W && abs(y[k]-_y)<=W) P[k]=1,++S; else { _X[0] = min(_X[0],x[k]); _X[1] = max(_X[1],x[k]); _Y[0] = min(_Y[0],y[k]); _Y[1]= max(_Y[1],y[k]); } if (S==n) return 1; //printf("Continue"); Rep(p,0,1) Rep(q,0,1) { _S = S; //枚举第二个正方形时在第一个的基础上累加而不是直接累加 _x = _X[p] , _y = _Y[q]; __X[1] = __Y[1] = -Inf ; __X[0] = __Y[0] = Inf; //注意赋初值 Rep(k,1,n) if (!P[k] && abs(x[k]-_x)<=W && abs(y[k]-_y)<=W) ++_S; //注意这里不能标记 P[k] 因为之后还要用 else if (!P[k]){ __X[0] = min(__X[0],x[k]); __X[1] = max(__X[1],x[k]); __Y[0] = min(__Y[0],y[k]); __Y[1]= max(__Y[1],y[k]); } if (_S==n || (__X[1]-__X[0]<=W && __Y[1]-__Y[0]<=W)) return 1; } } return 0; } int main () { scanf("%d",&n); X[1] = Y[1] = -Inf ; X[0] = Y[0] = Inf; Rep(i,1,n) { scanf("%d%d",&x[i],&y[i]); X[0] = min(X[0],x[i]); X[1] = max(X[1],x[i]); Y[0] = min(Y[0],y[i]); Y[1]= max(Y[1],y[i]); } L = 0 , R = Inf; //if (Ok(1)) { printf("YES"); return 0; } while (L+1<R) { Mid = (L+R)>>1; if (Ok(Mid)) R = Mid ; else L = Mid; } if (Ok(L)) printf("%d",L); else printf("%d",R); return 0; }
Bzoj 1068 [SCOI2007]压缩
Problem & Solution & Code:
/*{ (M)abcRdRMxyRz abcabcdabcabcdxyxyz abc abcabc abcabcd abcabcdabcabcd abcabcdabcabcd(M)xy abcabcdabcabcdxyxy abcabcdabcabcdxyxyz M标记当前开始压缩 R重复一遍 abcMabc -> abcMabc abcM c http://blog.csdn.net/qpswwww/article/details/44173265 不妨设整个字符串长度为n。根据题意,我们可以看作在区间 [1,n]之前已经放有一个字母M。那么我们就是要用若干个M和R,将字符串中的循环节括起 来,并使最终压缩后的字符串尽量短,显然这是个划分DP,而且用记忆化搜索写起来简单些。 实际上我们只需要DP出字符串中怎么放M就可以了,如果发现当前dp的区间中不含M,并且左右两半是一样的,那么就在中点处放一个R就行了。因此我们用f[L][R][k]来表示区间 [L,R], k=1表示 [L,R]中有缝隙处放了M(注:不包含 L?1和R+1,并且约定 L?1处一定有个M,或 L?1=0),压 缩后的字符串长度。显然边界条件是f[i][i][0]=1,f[i][i][1]=inf(因为只有一个元素的区间就没有缝隙放M)。那么对于区间 [L,R],如果其中放 了M,那么我们就暴力枚举其中一个M放在哪里,并用分割后的两个区间的状态做转移。如果其中没放M,我们先判断这个区间是否是一个循环节(左右两半的字符串一样),是的话那么这个区间只需要左半部分压缩后的长度+一个R就够了。否则我们就枚举R放在哪里,这样这个R就和前面的L?1和L之间的那 个M对应起来,括起来了一个循环节部分,然后再用这个循环节部分的dp值做转移,R后面的那部分保持压缩之前的样子不变。 说起来比较复杂,不过看代码就很清晰了。注意那个判断循环节的函数,一定要有特判,即这个区间长度是偶数,如果是奇数显然不能把这段字符串分成相等的两半,不特判会WA!!! } */ #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> #include <iostream> #include <cstdlib> using namespace std; const int Maxn =50 , Inf = 2015032990; #define Rep(i,x,y) for (i=x;i<=y;++i) char S[Maxn]; int F[Maxn][Maxn][2]={0},L; bool Ok(int L,int R){ int i; Rep(i,L,(L+R)>>1) if (S[i]!=S[i+((R-L+1)>>1)]) return 0; return 1; } int Dfs(int L,int R,int x) { int T = R-L+1,i; if (T==1) return 1; if (F[L][R][x]) return F[L][R][x]; if (x==1) Rep(i,L,R-1) T = min(T,Dfs(L,i,1) + 1 + Dfs(i+1,R,1)); //+M Rep(i,L,R-1) T = min(T,Dfs(L,i,x) + R-i); //~ +M if (Ok(L,R) && (!((R-L+1)&1))) T=min(T,Dfs(L,(L+R)>>1,0)+1); //+R F[L][R][x] = T; return T; } int main () { scanf("%s",S+1); L = strlen(S+1) ; // printf("%d",Ok(1,4)); cout << Dfs(1,L,1) << endl; return 0; }